Entwickler-Ecke
Algorithmen, Optimierung und Assembler - Eine Klasse, verschiedene Arrays (Vererbung oder case of?)
cbs - Do 03.04.08 13:16
Titel: Eine Klasse, verschiedene Arrays (Vererbung oder case of?)
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:
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); TWert = Variant;
TInt8Arr = array of ShortInt;
TFloat4Arr = array of Double;
TDaten = class(TObject) protected fDatentyp: TDatentyp; function GetWert(Index: Integer): TWert; virtual; abstract; procedure SetWert(Index: Integer; const Value: TWert); virtual; abstract; public property Wert[Index: Integer]: TWert read GetWert write SetWert; default; procedure WertHinzufuegen(Wert: Variant); virtual; abstract; 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
constructor TDaten.Create(Datentyp: TDatentyp); begin fDatentyp:= Datentyp; end;
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).
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); 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
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 - 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 - 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 - 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
Delete - Fr 04.04.08 09:12
Stellt sich mir die Frage, warum man so viele Datensätze im Speicher halten sollte.
cbs - 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 - 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.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!