Autor Beitrag
cbs
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 207
Erhaltene Danke: 1



BeitragVerfasst: Do 03.04.08 13:16 
Tag auch

Ich möchte in einer Klasse verschiedene Array's verwalten, welche gemeinsam verwendete Methoden bereitstellt um die Daten zu manipulieren. Pro Objekt ist jedoch nur eines dieser Array's relevant, welches lege ich bereits beim erzeugen des Objektes fest. Das zu verwendene Array wird also nicht mehr gewechselt, sobald das Objekt erzeugt wurde.

Nun meine Frage. Wie realisiere ich am günstigsten diese Klasse, vorallem im Hinblick darauf, das ich später mit sehr vielen dieser Objekte arbeiten werde (Geschwindigkeit).

Ich habe mir dazu zwei Möglichkeiten ausgedacht.

1. Klasse mit virtuelle Methoden

Eine Klasse die Methoden zum Bearbeiten der Array's virtuell implementiert und diese dann in Nachfahrenklassen mit dem jeweiligen korrekten Datentyp überschrieben werden. Wobei nur die Nachfahrenklasse das eigentliche Array enthält.

Erzeugt wird dann nurnoch die entsprechende Nachfahrenklasse. Verwendet wird jedoch nurnoch die Elternklasse mit den virtuellen Methoden (damit spare ich mir dann die abfrage, welchen datentyp die Klasse denn nun tatsächlich enthält)

Bsp:

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
type
  TDatentyp = (dtInt8, dtFloat4); // etc.

  TWert = Variant;

  TInt8Arr = array of ShortInt;

  TFloat4Arr = array of Double;

  TDaten = class(TObject)
  protected
    fDatentyp: TDatentyp;
    function GetWert(Index: Integer): TWert; virtualabstract;
    procedure SetWert(Index: Integer; const Value: TWert); virtualabstract;
  public
    property Wert[Index: Integer]: TWert read GetWert write SetWert; default;
    procedure WertHinzufuegen(Wert: Variant); virtualabstract;
    constructor Create(Datentyp: TDatentyp);
  end;

  TDatenInt8 = class(TDaten)
  private
    fArr: TInt8Arr;
  protected
    function GetWert(Index: Integer): TWert; override;
    procedure SetWert(Index: Integer; const Value: TWert); override;
  public
    procedure WertHinzufuegen(Wert: Variant); override;
  end;

  TDatenFloat4 = class(TDaten)
  private
    fArr: TFloat4Arr;
  protected
    function GetWert(Index: Integer): TWert; override;
    procedure SetWert(Index: Integer; const Value: TWert); override;
  public
    procedure WertHinzufuegen(Wert: Variant); override;
  end;

implementation

{ TDaten }

constructor TDaten.Create(Datentyp: TDatentyp);
begin
  fDatentyp:= Datentyp;
end;

{ TDatenInt8 }

function TDatenInt8.GetWert(Index: Integer): TWert;
begin
  Result:= fArr[Index];
end;

procedure TDatenInt8.SetWert(Index: Integer; const Value: TWert);
begin
  fArr[Index]:= Value;
end;

procedure TDatenInt8.WertHinzufuegen(Wert: Variant);
begin
  SetLength(fArr, Length(fArr) + 1);
  fArr[Length(fArr) - 1]:= Wert;
end;


2. Array über case of auswählen

Die zweite Möglichkeit würde alle Array's in einer Klasse vereinen. Die je nach Datentyp über eine case of abfrage das entsprechende Array benutzt (und auch füllt, denn solange die anderen Array leer sind, dürften sie kein Speicherplatz verbrauchen).

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
  TDatentyp = (dtInt8, dtFloat4); // etc.

  TWert = Variant;

  TInt8Arr = array of ShortInt;

  TFloat4Arr = array of Double;

  TDaten = class(TObject)
  private
    fDatentyp: TDatentyp;
    fInt8Arr: TInt8Arr;
    fFloat4Arr: TFloat4Arr;
    function GetWert(Index: Integer): TWert;
    procedure SetWert(Index: Integer; const Value: TWert);
  public
    property Wert[Index: Integer]: TWert read GetWert write SetWert; default;
    procedure WertHinzufuegen(Wert: Variant); 
    constructor Create(Datentyp: TDatentyp);
  end;

implementation

{ TDaten }

constructor TDaten.Create(Datentyp: TDatentyp);
begin
  fDatentyp:= Datentyp;
end;

function TDaten.GetWert(Index: Integer): TWert;
begin
  case fDatentyp of
    dtInt8: Result:= fInt8Arr[Index];
    dtFloat4: Result:= fFloat4Arr[Index];
  end;
end;

procedure TDaten.SetWert(Index: Integer; const Value: TWert);
begin
  case fDatentyp of
    dtInt8: fInt8Arr[Index]:= Value;
    dtFloat4: fFloat4Arr[Index]:= Value;
  end;
end;

procedure TDaten.WertHinzufuegen(Wert: Variant);
begin
  case fDatentyp of
    dtInt8: begin
      SetLength(fInt8Arr, Length(fInt8Arr) + 1);
      fInt8Arr[Length(fInt8Arr) - 1]:= Wert;
    end;
    dtFloat4: begin
      SetLength(fFloat4Arr, Length(fFloat4Arr) + 1);
      fFloat4Arr[Length(fFloat4Arr) - 1]:= Wert;
    end;
  end;
end;


Da ich mich mit den interna von Vererbung etc. nicht wirklich auskenne, kann ich nicht beurteilen welche methode besser geeignet ist.
Ich habe auch irgendwo mal geselsen das eine case of abfrage relativ langsam ist.
Zu bedenken wäre auch, das es nicht bei diesen beiden beispiel Datentypen bleibt.

Deshalb meine Frage, welche methode ist am besten, bzw. gibt es alternativen?


mfg

cbs
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4798
Erhaltene Danke: 1059

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Do 03.04.08 14:25 
Warum verwendest du denn nicht gleich Variant Arrays? (weil im Endeffekt baust du das ja auch nur nach)
Oder willst du auch noch Nicht-Standarddatentypen unterstützen?
cbs Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 207
Erhaltene Danke: 1



BeitragVerfasst: Do 03.04.08 15:07 
Ich spare dadurch einfach eine Menge Arbeitspeicher, vorallem wenn die Daten in die Millionen gehen.

Ein Variant beansprucht 16 Byte Speicher. Ein LongInt nur 4 Byte, ein ShortInt sogar nur 1 Byte etc.

Speichere ich 10.000.000 Datensätze sind das bei Variant schon ca. 150 MB, bei LongInt nur ca. 38 MB.
Habe ich jedoch nicht nur ein solches Objekt sonder gleich 10, sind das schon ca. 1.5 GB bzw. 381 MB. Ich meine, das ist schon ein unterschied :wink:

Ich benutze Variant also nur für meine "universellen" getter und setter methoden.
cbs Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 207
Erhaltene Danke: 1



BeitragVerfasst: Fr 04.04.08 08:32 
Die Frage hat sich erledigt. Ich habe mir einfach ein Testprogramm geschrieben um die Geschwindigkeiten zu ermitteln.

Zugrunde lagen 5 "Array Objekte" mit je 35.000.000 Datensätze und je 14 möglichen Datentypen pro Objekt.

Die erste Methode war demnach beim Lesen um ca. 35% schneller, beim schreiben immerhin noch ca. 6%.

Case of hat also verloren.

mfg

cbs
Luckie
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Fr 04.04.08 09:12 
Stellt sich mir die Frage, warum man so viele Datensätze im Speicher halten sollte.
cbs Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 207
Erhaltene Danke: 1



BeitragVerfasst: Fr 04.04.08 10:23 
Das war aber nicht meine Frage.

Meine Frage war, welche der beiden Methoden mehr Sinn macht.

Meine Lösung ist, das eine Abfrage über case of des Datentyps deutlich langsammer ist, als eine Klasse über der ich das Problem mit Vererbung löse.

Als nicht Informatiker war mir das leider nicht klar.

Sollte jemand eine ganz andere Idee zu dem Problem haben, bin ich natürlich dankbar für Vorschläge.

mfg

cbs
busybyte
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 40



BeitragVerfasst: Fr 04.04.08 12:00 
Denkanstoss:
1.Klassen mit read write sind schnell und werden oft in Grafikanwendungen verwendet z.B. DelphiX
2.Setlength ist sehr langsam und sollte vermieden werden bzw. nicht nur um 1 sondern wenn möglich um einen
grösseren Wert erhöht werden,wobei die setlength/Length (wenn möglich) am Ende auf den tatsächlichen Wert korrigiert wird.
3.Ein Versuch wäre auch mit Pointer bzw. TList/TStringlist zu arbeiten was aber etwas Erfahrung voraussetzt.
Pointer sind sehr schnell.
Am schnellsten geht es (für Experten) direkt in Assembler,was aber kaum noch jemand beherscht,ich auch nicht.
4.Ein Thread beschleunigt das ganze dann nochmals (siehe 5).
5.Die grafische Anzeige der Daten mit Refresh,Paint oder auch nur Application.Processmessages ist die Megabremse überhaupt
ungeeignet sind deswegen Sprünge von Methode zu Methode (Procedure/Function) da hier synchronisiert wird.