Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Maßnahmen zum Speicherverbrauch minimieren (https://www.delphipraxis.net/185886-massnahmen-zum-speicherverbrauch-minimieren.html)

stahli 15. Jul 2015 12:02

Maßnahmen zum Speicherverbrauch minimieren
 
Ich entwickle unter XE3 (VCL) ein Projekt, das viele dynamische Objekte mit unterschiedlichen Eigenschaften erzeugt (zum großen Teil verschachtelte Listen als Untereigenschaften).

Listen sind z.B. TList<IMyInterface> und TList<IMyNamedObject> sowie einige TStringLists.
Die NamedObjekte haben eine Eigenschaft Name, die letztlich einen String enthält.

Jedes Businessobjekt hat Listen als Eigenschaften, die wieder unterschiedliche Unterobjekte bzw. Interfaces verwalten.

Aktuell kann ich in einer 32bit-Anwendung unter Debugging knapp 100.000 Businessobjekte erzeugen, bis ich ein out of Memory erhalte.

Welche Maßnahmen wären sinnvoll, den Speicherbedarf der Anwendung runter zu schrauben (sowohl den der Anwendung selbst als auch den der Businessobjekte)?


Aktuell benutze ich einfach String für die Speicherung der Businessobjekt-Namen. Das werde ich auf Ansistring oder Shortstring umstellen können.

Macht es Sinn, auf generische Listen (und TComparer) zu verzichten? Generisch verwende ich nur Listen in der Anwendung und könnte auch auf einfache Listen umstellen (dadurch wären ja nur einige Castings mehr notwendig).

Die neue RTTI brauche ich nicht. Spart das Speicherplatz, wenn man die deaktiviert? Was kann man sonst noch deaktivieren?

Eine Datenbankanbindung und auch eine 64bit-Version ist natürlich auch vorgesehen, dennoch möchte ich den Speicherverbrauch der Anwendung nach Möglichkeit drosseln.

Sir Rufo 15. Jul 2015 12:29

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Die von dir angesprochenen Lösungen verlagern das Problem aber nur ein Stück weiter nach hinten. Dann ist nicht erst bei 100.000 Schluss, sondern bei 150.000 (nur ein geschätztes Beispiel).

Wenn der Speicher-Platz nicht reicht, dann kann man das z.B. mit dem Proxy-Pattern lösen.

Dabei hat man dann nicht mehr das fette BO im SPeicher, sondern eben einen (schmalen) Stellvertreter, der die Daten dann bei Bedarf nachlädt. Möglich wäre auch ein entsprechendes Caching der Daten, die nur für eine bestimmte Zeit im Speicher vorgehalten werden und nach einem Zeitpunkt X wieder aus dem SPeicher geräumt werden. Auch das schafft Platz.

stahli 15. Jul 2015 12:39

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Ja, das habe ich mit meinem letzten Satz anzudeuten versucht (mit Datenbankanbindung meinte ich letztlich einen ORM (bzw. Manager falls ich mich doch noch für NoSQL entscheide), der sich auch um die Lebenszeit der Objekte kümmert).

Mich würde aktuell erst einmal interessieren, welche Maßnahmen man versuchen sollte, um auf 150T oder 200T mögliche Objekte zu kommen.

Z.B. eben durch Ersetzen von String durch AnsiString (wo möglich), rausschmeißen von RTTI oder Generics, oder was sonst noch...?

Der schöne Günther 15. Jul 2015 12:48

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Ich bin da kein Profi, aber Dinge wie RTTI sind doch einmalig vielleicht 5MB Binärcode der drin ist oder nicht. Da wächst doch nichts mit jeder zusätzlich erstellten Instanz.

Auch (dumme Frage, aber trotzdem): Ist dein Speicherverbrauch wirklich so hoch dass man an 32-Bit-Grenzen kommt? Nicht dass du einfach nur ein riesiges Feld allokieren willst und so ein großer Block am Stück ist einfach nicht frei.*


Ansonsten: Das ist doch ähnlich wie wenn jemand Millionen an Zeilen in einer Memo darstellen will und sich dann wundert dass irgendwann nichts mehr geht. Wozu brauchst du hundert tausende von den Dingern gleichzeitig im Speicher? Ich würde als erstes schauen dass man mittels lazy-loading wirklich erst dicke Brocken in den Speicher schaufelt wenn es wirklich so weit ist und darauf achten dass es nicht erst bei Anwendungsende wieder freigegeben wird ;-)


* Oder kümmert sich Windows da mit irgendwelchen schlauen Paging und MMF-Geschichten drum? Mann, Betriebssysteme war eine der langweiligsten Vorlesungen die ich je hatte. Nicht viel behalten :|

hoika 15. Jul 2015 12:56

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Hallo,
die Umstellung auf ShortString bringt wohl gar nichts, wird wohl eher größer:
ein String[50] mit "123" verbraucht mehr Speicher als ein String mit "123".

1.
Ich würde für Tests einfach mal ein BusinessObject mit String und dann mit AnsiString erzeugen,
es nicht freigeben und mit FastMM4 prüfen, wer mehr Speicher verbraucht.

2.
Vielleicht hast du ja ach einfach auch nur ein Speicherleck.

3.
Ausserdem solltest du prüfen, ob du wirklich 100.000 Objekte im Speicher haben musst.

4.
Du könntest auch etwa an deiner Datenstruktur verändern, indem globale Objekte nur einmal geladen werden
und die anderen Objekte direkte Pointer darauf haben.

Unter 32-bit kann man halt "nur" 2 GB Speicher benutzen, mit Tricks 3 GB.


Heiko

HeZa 15. Jul 2015 13:19

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Hallo Stali,

du kannst deine Objekte einmal darauf hin untersuchen, wieviele Varianten einer Klasse benötigt werden. Vielleicht hast du Objekte, die zwar millionenfach referenziert werden, aber von denen es nur 1000 verschiedene Varianten gibt.

Dann brauchst du für so eine Klasse nur tausend Objekte in einem Pool zu erzeugen und holst daraus die Referenz auf die benötigte Variante über eine Funktion/Factory Methode.

Ich versuche mal ein Beispiel:
Du lädst 100.000 Aufträge mit durchschnittlich 100 Positionen, jede Position hat ein Artikel-Objekt (das vielleicht auch noch sehr umfangreich ist). Dann würdest ein simpler Ansatz 10.000.000 Artikel-Objekte erzeugen und den Positionen zuordnen. Es gibt vielleicht aber nur 10.000 verschieden Artikel. Dann könntest du durch den oben beschriebenen Ansatz 9.990.000 Objekte sparen.

Diesen und auch die Vorschläge Sir Rufo findest Du im Buch
Design Patterns: Entwurfsmuster als Elemente wiederverwendbarer objektorientierter Software

hanvas 15. Jul 2015 13:22

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von stahli (Beitrag 1308741)
Mich würde aktuell erst einmal interessieren, welche Maßnahmen man versuchen sollte, um auf 150T oder 200T mögliche Objekte zu kommen.

Z.B. eben durch Ersetzen von String durch AnsiString (wo möglich), rausschmeißen von RTTI oder Generics, oder was sonst noch...?

Zum einen könntest Du nachsehen ob es in deinem Objekt lineare Abhängigkeiten gibt, also Eigenschaften die sich aus anderen Eigenschaften errechnen lassen. Auf diese Eigenschaften könntest Du verzichten.

Dann wäre es eine Überlegung ob Du immer das ganze BO im Speicher halten müsstest oder nur einen bestimmten Teil (sozusagen den Primärindex) dann könntest Du dein BO in mehrere Happen aufteilen die je nach Bedarf geladen werden.
Den Zugriff auf nicht geladene Properties müsstest Du dann natürlich über Methoden implementieren und darauf achten das diese nicht! published sind.

Beispiel (aus dem Handgelenk, aber ich denke das Prinzip wird klar )

Code:

type TBOLazyDataType = record
                          a,b,c,d : Double;
     end;

     pBOLazyDataType = ^TBOLazyDataType;

     TBObject = class(TObject)
     private
      FIdentifier : Integer;
      LazyData : pBOLazyDataType;
     protected
       function getLazyDataByIdentifier ( ident : Integer ) : pBOLazyDataType;
       function getLazyA : Double;
       procedure setLazyA(aValue : Double);
     public
       Constructor Create;    
     property
      Lazy_A : Double
        read getLazyA write SetLazyA;
     end;


...

Constructor TBObject.Create;    
begin
 ...
 LazyData := nil;
end;

function TBObject.getLazyA : Double;
begin
 if not Assigned(LazyA) then
    LazyA := getLazyDataByIdentifier(Identifier);
 result := LazyA.A;
end;

procedure TBObject.setLazyA(aValue : Double);
begin
 if not Assigned(LazyA) then
    LazyA := getLazyDataByIdentifier(Identifier);
 LazyA.A := aValue;
end;
Außerdem könntest Du nach dem gleichen Prinzip überlegen ob bestimmte BOs bestimmte Eigenschaften bzw. Werte teilen, das ist häufig bei hyrarchischen Daten der Fall die dann nur einmal tatsächlich vorkommen müssten und ansonsten über eine Referenz abgebildet werden.

Und letztendlich könntest Du einfach mal nachsehen ob die irgendwo eine Implementierung eines Baumes rumliegen hast der nur Teile seines Datenbestandes im Speicher hält, falls so was nicht auf Deiner Platte ist könntest Du ja mal hier nachsehen : http://synopse.info/fossil/wiki?name=Big+Table

cu Ha-Jö

cu Ha-Jö

Mavarik 15. Jul 2015 14:03

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von hoika (Beitrag 1308744)
die Umstellung auf ShortString bringt wohl gar nichts, wird wohl eher größer:
ein String[50] mit "123" verbraucht mehr Speicher als ein String mit "123".

Wie kommst Du den da rauf?

Union 15. Jul 2015 14:09

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von Mavarik (Beitrag 1308752)
Zitat:

Zitat von hoika (Beitrag 1308744)
die Umstellung auf ShortString bringt wohl gar nichts, wird wohl eher größer:
ein String[50] mit "123" verbraucht mehr Speicher als ein String mit "123".

Wie kommst Du den da rauf?

Vielleicht weil es so ist?
Zitat:

Zitat von DokWiki
While the length of a ShortString can change dynamically, *its memory is a statically allocated 256 bytes*; the first byte stores the length of the string, and the remaining 255 bytes are available for characters


Mavarik 15. Jul 2015 14:12

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von stahli (Beitrag 1308741)
Mich würde aktuell erst einmal interessieren, welche Maßnahmen man versuchen sollte, um auf 150T oder 200T mögliche Objekte zu kommen.

Musst Du die den alle im Speicher halten?

Abgesehen davon ist das doch gar nix oder sind die Objecte so groß?

Sagen wir mal Du nimmst dir 1GB RAM... Und willst da 200.000 Objecte unter bringen...
Dann kann jedes Object 5,2 MB groß sein...

Mavarik

Mavarik 15. Jul 2015 14:14

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von Union (Beitrag 1308753)
Zitat:

Zitat von Mavarik (Beitrag 1308752)
Zitat:

Zitat von hoika (Beitrag 1308744)
die Umstellung auf ShortString bringt wohl gar nichts, wird wohl eher größer:
ein String[50] mit "123" verbraucht mehr Speicher als ein String mit "123".

Wie kommst Du den da rauf?

Vielleicht weil es so ist?
Zitat:

Zitat von DokWiki
While the length of a ShortString can change dynamically, *its memory is a statically allocated 256 bytes*; the first byte stores the length of the string, and the remaining 255 bytes are available for characters


LOL

Daher ja STRING[5] oder was auch immer... Und der braucht auch nur 6 Byte

Union 15. Jul 2015 14:19

AW: Maßnahmen zum Speicherverbrauch minimieren
 
JEDER Shortstring wird mit 256 Byte alloziiert. BELEGT werden davon dann nur 6, das ist richtig. Eine sehr schöne Erläuterung findet sich hier oder in der offiziellen Dokumentation.

Mavarik 15. Jul 2015 14:38

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von Union (Beitrag 1308756)
JEDER Shortstring wird mit 256 Byte alloziiert. BELEGT werden davon dann nur 6, das ist richtig. Eine sehr schöne Erläuterung findet sich hier oder in der offiziellen Dokumentation.

Ja dann mal aus der offiziellen Docu:

Zitat:

Zitat von docwiki.embarcadero.com

Hier wird die Variable MyString mit einer maximalen Länge von 100 Zeichen erstellt. Dies entspricht den folgenden Deklarationen:
Delphi-Quellcode:
type CString = string[100];
var MyString: CString;
Bei Variablen, die auf diese Weise deklariert werden, wird dem Typ nur so viel Speicherplatz zugewiesen, wie für die angegebene Länge plus ein Byte erforderlich ist. In diesem Beispiel belegt MyString 101 Byte

Die Frage ist eigentlich wie oft sind die Strings mit welcher länge gefüllt...
Wenn ich einen String[80] definiere aber immer nur "ABC" rein schreibe ist das natürlich quatsch...
Dann ist der Ansistring besser... Aber spätestens wenn ich die AnsiStrings in einem Record habe, klappen schon keine Zuweisungen des Records mehr... keine Moves, keine Blockwrites... (Oder für die "neuen": kein Streamwrite in einem Rutsch)

Union 15. Jul 2015 14:46

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Mit dem Umweg über einen eigenen Typen geht es tatsächlich, da hast Du vollkommen Recht. Nur muss man dann für jede mögliche Maximallänge eine Typdefinition erstellen - um das gleiche Ergebnis zu erreichen wie mit einem (P)Ansistring. Ich denke, ein Lazy-Loading Pattern in Verbindung mit einem eigenen GC sollte mehr Erfolg versprechen.

stahli 15. Jul 2015 14:51

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Liste der Anhänge anzeigen (Anzahl: 1)
Danke für Eure Hilfe!

Ich bin auch etwas überrascht, dass der Speicherbedarf so hoch ist.
Mit etwas einfacheren Objekten konnte ich 3-4 Mio Stück erzeugen.
Wenn allerdings jedes Objekt wieder n Unterobjekte verwaltet, dann reduziert das natürlich die Anzahl der Hauptobjekte.

Mit Unterobjekten sind dann sowohl die Verwaltungsobjekte gemeint, die Businessobjektreferenzen verwalten, als auch die benötigten untergeordneten Businessobjekte selbst.

Businessobjekte werden von einer Factory erzeugt und zentral verwaltet. Die Factory gibt dann nur Interfaces auf diese Objekte heraus.
Eine Objektinstanz wird nur neu erstellt, wenn es noch keine entsprechende gibt. Sonst wird das existierende Objekt wiederverwendet.

Memoryleaks habe ich nicht (bis auf 4 kleine Indy-Klassen - scheint wohl ein Bug zu sein, der aber jetzt hier nicht wirklich relevant ist).

Anbei mal ein Screenshot eines veralteten MemLogs. Die StringLists habe ich inzwischen schon entfernt. Das erhöhte die Objektanzahl schon mal von 70T auf knapp 100T.

Dass die RTTI-Entfernung nur eine einmalige Auswirkung hätte ist mir klar. Ich weiß nur nicht, wie viel das ausmacht und was es noch für Optionen gäbe.

Die Info mit dem ShortString ist hilfreich. So weit war ich noch nicht vorgedrungen. In der Hilfe sah es irgendwie so aus, als wären ShortString und WideString identisch.
AnsiString für die Namenbezeichner dürfte aber sicher was bringen, denke ich.

Ich werde also mal Stück für Stück schauen, was ich an meinen Klassen optimieren kann.
So einen richtigen Brüller, der hier mal schnell zu einer großen Entlastung führt, habt Ihr offenbar auch nicht zur Hand.

Dass mein Manager später den Speicherverbrauch überwachen und Objektdaten wegschreiben und die Objekte wieder auflösen muss ist klar.
Aber um so optimierter die Objekte verwaltet werden um so mehr können im Speicher gehalten werden und um so performanter kann das Ganze laufen.

Ich werde also mal die Details prüfen, wie String zu AnsiString oder Listenobjekte erst instanziieren, wenn ein Unterobjekt hinzugefügt werden soll etc...

Mavarik 15. Jul 2015 14:58

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von Mavarik (Beitrag 1308754)
Sagen wir mal Du nimmst dir 1GB RAM... Und willst da 200.000 Objecte unter bringen...
Dann kann jedes Object 5,2 MB groß sein...

Gelesen?

Kann es sein, dass Du dich mit deinen Zeigerliste ein wenig zu Tode verwaltest?

Wenn Du mal verrätst was Du speichern willst, können wir sicherlich besser helfen...

stahli 15. Jul 2015 15:09

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Das schaue ich mir jetzt mal an...

hstreicher 15. Jul 2015 16:15

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von Mavarik (Beitrag 1308754)
Sagen wir mal Du nimmst dir 1GB RAM... Und willst da 200.000 Objecte unter bringen...
Dann kann jedes Object 5,2 MB groß sein...


5,2 KB , sonst wirds 1 TB Ram

Dejan Vu 16. Jul 2015 06:42

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von hstreicher (Beitrag 1308767)
Zitat:

Zitat von Mavarik (Beitrag 1308754)
Sagen wir mal Du nimmst dir 1GB RAM... Und willst da 200.000 Objecte unter bringen...
Dann kann jedes Object 5,2 MB groß sein...


5,2 KB , sonst wirds 1 TB Ram

LOL

Wäre es nicht denkbar, das Klassen an sich mehr Speicherplatz verbraten, als z.B. Records?

Davon unabhängig solltest Du einfach mal messen, wie groß ein Businessobjekt inklusive aller Unterobjekte ist. Dazu kannst Du dir ja einfach eine Funktion 'Size' für alle Klassen implementieren:

Delphi-Quellcode:
Function TMyObject.Size : Integer;
begin
  Result := SizeOf(FMyDouble)+StringSize(FMyString);
  For item in FMyObjectList do
    inc(Result, item.Size);
end;
Dann hast Du schon einmal den Nettoverbrauch an Daten. Das solltest Du in der Factory machen, d.h. die Factory führt darüber Buch, wie viel Bytes es bisher produziert hat.

Wäre doch mal interessant, ob wirklich die Objekte das Problem sind, oder nicht etwas anderes.

Wie sehen deine Objekte denn in etwa aus? Müssen es Namen (String) sein, um ein Objekt zu identifizieren? Tut es ein integer nicht auch?

stahli 16. Jul 2015 09:10

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Ich habe gestern einfach mal String durch AnsiString ersetzt.

Nach Studium der Hilfe http://docwiki.embarcadero.com/RADSt..._in_RAD_Studio
war zu erwarten, dass es nicht sehr viel ausmachen wird. Und so ist es auch.
Statt 92539 Hauptobjekten konnte ich 97059 erzeugen.

Wie bereits beschrieben, sind das die Hauptobjekte, denen diverse Eigenschaften, Listen und Referenzzeiger zugeordnet sind.
Wirkliche Objekte werden viel mehr erzeugt.

Auf die Namen kann ich nicht verzichten. Aber ich könnte einen platzsparendere Stringtyp wählen. Ich weiß nur noch nicht welchen.


Ich habe gestern mal versucht, mit AQTime Pro den Speicherbedarf einer Methode zu profilen, in der eines meiner Hauptobjekte erzeugt wird.
Im Gegensatz zu dem Performance-Profile ist das aber beim Allocation-Profiler scheinbar so nicht möglich. Zumindest habe ich das gestern nicht hingekriegt.
Also werde ich das mal (wie von Dejan Vu vorgeschlagen) von meiner Factory protokollieren lassen oder das Projekt entfrachten und nur ein Mainobject erzeugen und dieses mit AQTime analysieren.

Über eine schrittweises Debuggen ist mir erst mal nichts falsches aufgefallen. Es sind halt recht viele Untereigenschaften und ich werde schauen müssen, was ich dort optimieren kann.

Ich werde mich also mal weiter Stück für Stück voran tasten...

Sir Rufo 16. Jul 2015 09:40

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Ich habe da so eine vage Vermutung, was deinen immensen Speicherbedarf verursacht.

Kannst du mal das Interface von so einem BO zeigen und wie du auf eine Eigenschaft zugreifst?

Ich vermute nämlich, dass du für jede BO Instanz die Eigenschafts-Namen als String im Speicher hast ...

stahli 16. Jul 2015 10:06

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Worauf willst Du raus?

Ich habe z.B.

Delphi-Quellcode:
IMyInf = interface
...
property Name: string read get_Name write set_Name
...
Die Klasse hält dann natürlich noch ein fName: String bzw. ein Objekt, das einen String verwaltet.

Bei Bedarf durchsuche ich eine Liste nach einem Interface mit einem bestimmten Namen. Wenn das einmal gefunden ist arbeite ich nur noch direkt mit dem Interface.

Beziehungen zwischen Objekten/Interfaces werden nicht über Namen-Strings abgebildet.

Ich kann gern mal einen Auszug raussuchen, aber es wäre gut zu wissen, wo Du ein Problem vermutest...

Sir Rufo 16. Jul 2015 10:22

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Ich vermute, dass genau diese Strings, die den Namen für was auch immer definieren, dir den Speicher vollmüllen.

Das kann ich aber erst sehen, wenn ich sehe, wie du denn mit diesen Namen in deinem BO umgehst

BUG 16. Jul 2015 10:36

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Im Prinzip: Hast du Strings (außer einem eindeutigem Namen), die du für jedes BO anlegst?
SirRufo will vermutlich auf so etwas wie Eigenschafts/Spalten-Namen hinaus, die bei vielen Objekten gleich wären.

Sir Rufo 16. Jul 2015 10:38

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von BUG (Beitrag 1308837)
Im Prinzip: Hast du Strings (außer einem eindeutigem Namen), die du für jedes BO anlegst?
SirRufo will vermutlich auf so etwas wie Eigenschafts/Spalten-Namen hinaus, die bei vielen Objekten gleich wären.

:thumb: Das ist meine Vermutung

jaenicke 16. Jul 2015 10:40

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Wie hoch ist denn der Speicherverbrauch (z.B. auch einfach im Taskmanager, muss nicht so genau sein), wenn du die Out-Of-Memory Exception bekommst?

Wir hatten das gleiche Problem und haben daraufhin auf 64-Bit umgestellt, da es bei unserer Anwendung mit Speicher sparen nicht gereicht hätte. Dort konnte man aber einen Riesenunterschied zu dem Out-Of-Memory der Delphi IDE sehen:
Bei uns kam der Speicherverbrauch auf ca. 1,7-1,8 GiB hoch bevor es zum Problem kam.
Bei der Delphi IDE hingegen kommt (vermutlich durch ungünstiges Speichermanagement?) der Fehler schon bei ca. 1 GiB.

Wodurch wir Speicher sparen konnten war z.B. die Verwendung von TDictionary statt TStringList für die Zuordnung zwischen Strings und anderen Daten, da dadurch nur noch ein Hash im Speicher bleibt statt des ganzen Strings.

BUG 16. Jul 2015 10:52

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von jaenicke (Beitrag 1308839)
da dadurch nur noch ein Hash im Speicher bleibt statt des ganzen Strings.

Das halte ich für ein Gerücht: dann müsste die Hash-Funktion ja eindeutig sein und das Dictionary ließe sich nicht aufzählen.

stahli 16. Jul 2015 11:35

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von BUG (Beitrag 1308845)
Zitat:

Zitat von jaenicke (Beitrag 1308839)
da dadurch nur noch ein Hash im Speicher bleibt statt des ganzen Strings.

Das halte ich für ein Gerücht: dann müsste die Hash-Funktion ja eindeutig sein und das Dictionary ließe sich nicht aufzählen.

Das denke ich auch. Hash-Werte lassen sich ja nicht in einen Ursprungs-String zurück wandeln. Unterschiedliche Strings können den gleichen Hash-Wert haben.

Bei Video2Brain gibt es ein gutes Tutorial zu Datenstrukturen: https://www.video2brain.com/de/video...atenstrukturen
Ist so ein kleiner 3h-Ersatz für ein 3jähriges Studium. ;-)
Ich fand es interessant, wenn auch nicht alles ganz neu war.


@jaenicke

Aber ansonsten scheint Euer Problem zu meinem zu passen. Ich muss die Speichergrößen nochmal zusammentragen. Mit und ohne Delphi sowie als Debug und Release. Mache ich mal heute Abend.
Unter 64 Bit gab es die Probleme natürlich nicht. Ich hatte da mal ein paar Mio Mainobjekte versucht. Aber ich will natürlich dennoch das Ganze möglichst optimieren.


@Sir + Bug

Ja, die Richtung kommt schon hin. Eigenschaften von Objekten werden mit Namen bezeichnet (z.B. "FirstName"). Dem wird dann ein Wert zugeordnet (z.B. "Sir").
Wenn es viele Objekte gibt, die eine Eigenschaft "FirstName" haben wird der String häufig verwendet.

Nun hatte ich aber in der Hilfe (http://docwiki.embarcadero.com/RADSt...e/String-Typen) gelesen (oder es so verstanden), dass Delphi "FirstName" nur einmal erzeugt und künftig Referenzen darauf benutzt. Ändert eine Referenz ihren Wert in "Test" wird der Teststring neu angelegt und der RefCounter von "FirstName" reduziert. Gibt es keine Referenz mehr auf "FirstName" wird der Speicherplatz freigegeben. Ist das richtig?

Ich hatte auch schon überlegt (genauer gesagt ist das auch aus einem Projektinternen Grund ernsthafte Absicht ;-)), in den Eigenschaften statt auf einen Namen-String auf ein Namen-Objekt zu verweisen, das einen String enthält. Dann würden nur noch Pointer verbraucht werden und das Namensobjekt könnte beliebig oft referenziert werden.
Wenn die vorherige Annahme mit den String-Referenzen stimmt, würde das aber am Gesamtverbrauch nicht mehr viel ausmachen.

BUG 16. Jul 2015 11:57

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von stahli (Beitrag 1308861)
Nun hatte ich aber in der Hilfe (http://docwiki.embarcadero.com/RADSt...e/String-Typen) gelesen (oder es so verstanden), dass Delphi "FirstName" nur einmal erzeugt und künftig Referenzen darauf benutzt. Ändert eine Referenz ihren Wert in "Test" wird der Teststring neu angelegt und der RefCounter von "FirstName" reduziert. Gibt es keine Referenz mehr auf "FirstName" wird der Speicherplatz freigegeben. Ist das richtig?

Ja. Aber Achtung: Erzeugst du zweimal dynamisch den gleichen String, belegt der auch zweimal Speicher.
Ich halte es für besser, einen Stringimplementierung zu benutzen, die sicherstellt, dass Strings mit gleichem Inhalt ihren Speicher teilen. Niemand garantiert, das Strings in Delphi für immer Referenz-gezählt bleiben.

Sir Rufo 16. Jul 2015 12:26

AW: Maßnahmen zum Speicherverbrauch minimieren
 
@stahli

Deine Annahme mit der String-Speicherverwaltung ist leider nicht korrekt, wie dir diese kleine Anwendung zeigen wird, wenn du dabei den Task-Manager beobachtest.

Hier wird auch immer der gleiche String-Wert zugewiesen, aber auf unterschiedliche Art und Weise mit eben einer unterschiedlichen Speicherauslastung, obwohl effektiv gesehen in allen Instanzen, der gleiche Wert enthalten ist.
Delphi-Quellcode:
unit Form.MainForm;

interface

uses
  System.Generics.Collections,
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TFoo = class
  private
    FValue: string;
  public
    property Value: string read FValue write FValue;
  end;

  TForm1 = class( TForm )
    Timer1: TTimer;
    TimerEnabledCheckBox: TCheckBox;
    FillCachedValuePropertyCheckBox: TCheckBox;
    procedure TimerEnabledCheckBoxClick( Sender: TObject );
    procedure Timer1Timer( Sender: TObject );
    procedure FormShow( Sender: TObject );
    procedure FormActivate( Sender: TObject );
    procedure FillCachedValuePropertyCheckBoxClick( Sender: TObject );
  private
    FValue: string;
    FFooList: TObjectList<TFoo>;
    function GenerateValue: string;
    function GetValue: string;
    procedure PresentValues;
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
  end;

var
  Form1: TForm1;

implementation

uses
  System.StrUtils;

{$R *.dfm}
{ TForm1 }

procedure TForm1.AfterConstruction;
begin
  inherited;
  FFooList := TObjectList<TFoo>.Create( True );
  FValue := GenerateValue;
end;

procedure TForm1.BeforeDestruction;
begin
  FreeAndNil( FFooList );
  inherited;
end;

procedure TForm1.TimerEnabledCheckBoxClick( Sender: TObject );
begin
  Timer1.Enabled := TimerEnabledCheckBox.Checked;
end;

procedure TForm1.FillCachedValuePropertyCheckBoxClick( Sender: TObject );
begin
  FFooList.Clear;
  PresentValues;
end;

procedure TForm1.FormActivate( Sender: TObject );
begin
  PresentValues;
end;

procedure TForm1.FormShow( Sender: TObject );
begin
  PresentValues;
end;

function TForm1.GenerateValue: string;
begin
  Result := DupeString( 'Test', 1000 );
end;

function TForm1.GetValue: string;
begin
  if FillCachedValuePropertyCheckBox.Checked then
    Result := FValue
  else
    Result := GenerateValue;
end;

procedure TForm1.PresentValues;
begin
  TimerEnabledCheckBox.Checked := Timer1.Enabled;
  TimerEnabledCheckBox.Caption := string.Format( 'FFooList.Count = %d', [ FFooList.Count ] );
end;

procedure TForm1.Timer1Timer( Sender: TObject );
var
  LFoo: TFoo;
  LIdx: Integer;
begin
  for LIdx := 1 to 100 do
  begin
    LFoo := TFoo.Create;
    try
      LFoo.Value := GetValue;
      FFooList.Add( LFoo );
      LFoo := nil;
    finally
      LFoo.Free;
    end;
  end;
  PresentValues;
end;

end.

jaenicke 16. Jul 2015 18:19

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von BUG (Beitrag 1308845)
Zitat:

Zitat von jaenicke (Beitrag 1308839)
da dadurch nur noch ein Hash im Speicher bleibt statt des ganzen Strings.

Das halte ich für ein Gerücht: dann müsste die Hash-Funktion ja eindeutig sein und das Dictionary ließe sich nicht aufzählen.

Ja, mit der internen Hashfunktion ist das so. Aber man kann auch problemlos eigene Hashwerte berechnen und die in das Dictionary legen. ;-)

stahli 16. Jul 2015 18:39

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Dann wäre das letztlich ein eindeutiger Schlüssel bzw. Index für einen bestimmten Eintrag - oder?
Ansonsten würden Hashkollisionen möglich sein oder in der Hashtabelle viele Plätze unbelegt bleiben müssen.
Kannst Du Euren Ansatz noch etwas erklären?

Sir Rufo 16. Jul 2015 18:51

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Für bekannte und beschränkte Eingabemengen können auch perfekte (im Sinne von kollisionsfreie) Hashfunktionen gefunden werden.
Halte ich also für ein Mär, wenn jemand behauptet er hat ein Hash-Algorithmus, der einen Wert aus einer beschränkten Menge liefert und damit auch unbeschränkte Eingabemengen kollisionsfrei hashen kann.

Ein Dictionary unterteilt die Key-Menge in Buckets, wo alle Elemente hineinkommen, die den gleichen Hashwert haben und such dann nur noch in diesem Bucket nach dem passenden.

jaenicke 16. Jul 2015 18:55

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Naja, der Fall ist etwas speziell, in dem Fall war eine Hashfunktion machbar, weil die Strings bestimmte Bedingungen erfüllen. Teile davon konnten in die dahinterliegenden Objekte zusammengefasst werden (stell es dir wie einen Index vor), und bestimmte Teile sind ansonsten eindeutig.

Sir Rufo 16. Jul 2015 19:04

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von jaenicke (Beitrag 1308940)
Naja, der Fall ist etwas speziell, in dem Fall war eine Hashfunktion machbar, weil die Strings bestimmte Bedingungen erfüllen. Teile davon konnten in die dahinterliegenden Objekte zusammengefasst werden (stell es dir wie einen Index vor), und bestimmte Teile sind ansonsten eindeutig.

Also liegt dort ein bestimmte und beschränkte Eingabemenge vor ... sag das doch gleich ;)

stahli 16. Jul 2015 22:24

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Aaalsooo...

Unter 32bit habe ich mal 4 Varianten versucht, bis Out of Memory kam:

Debug, IDE, 97.059, 1.946,2 MB
Debug, EXE, 97.125, 1.947,8 MB

Release, IDE, 97.498, 1.955,0 MB
Release, EXE, 97.498, 1.955,2 MB

Also ob Debug oder Release bzw. unter IDE-Kontrolle oder nicht spielt keine Rolle.



Unter 64bit (Debug, IDE) habe ich mal 600.000 Mainobjekte erzeugt.
Bei ca. 5GB hat Windows scheinbar immer Daten ausgelagert und dann wieder weitere Objekte erzeugt.
Keine Ahnung, was da genau passiert.
Zumindest sollte man den Speicherverbrauch wohl nicht so hoch treiben, da die Anwendung sonst ausgebremst wird.
(Alles nur mein erster Eindruck.)


Dann bin ich zurück auf 32bit und habe die gleichen Strings durch Referenzen auf existierende Interfaces ersetzt und dachte, das würde eine deutlich höhere Anzahl von MainObjekten ermöglichen. Das Gegenteil war der Fall.
Es waren nur noch 92.539!?

Kann sein, dass es heute zu spät für mich ist. Das schaue ich mir am WE noch näher an.


Aber ich habe mir auch mal die gesamten Datenobjekte ausgeben lassen
92.480 Mainobjekte -> 1.942.058 Gesamtobjekte
Die Mainobjekte haben also durchschnittlich 20 Unterobjekte und insgesamt sind es fast 2Mio.


Das sind also schon ein paar, aber die Speicherbedarfsreduzierung bleibt auf jeden Fall ein Ziel von mir.


Dann bin ich zurück zu den Strings von vorher (alte Sicherung verwendet).
Die Objekte blieben bei 92540->1943318 obwohl es am Anfang ja 97.000 waren.


Nach einem PC-Neustart waren es noch weniger:
91348->1918214


Also die Gründe für diese Schwankungen kann ich mir nicht erklären.
Vielleicht muss man einfach damit leben.
Warum die Änderung auf Objektreferenzen statt den Strings nichts brachte oder sogar kontraproduktiv war, ist mir rätselhaft.
(Ein Fehler meinerseits kann hier ggf. allerdings auch möglich sein.)

Sir Rufo 16. Jul 2015 22:30

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Tja, ich habe dir gezeigt wie man den Speicherbedarf bei vielen gleichen Strings groß macht oder klein hält.

Da du nicht zeigen möchtest, wie du das konkret umsetzt, wirst du dich darum selber kümmern müssen.

Eins kann ich aber sagen:

Das mit den Interfaces und den Strings ist von hinten nach vorn dreimal gedreht und einmal hochgeworfen. Ein String wird doch schon quasi wie ein Interface behandelt (siehe mein Beispiel). Und du bastelst da nochmal ein Interface drum herum und das anscheinend auch noch falsch ...

stahli 16. Jul 2015 22:32

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Ein Auszug wir schwierig und durch das ganze Projekt wirst Du Dich nicht wühlen wollen.
Ich nehme mir am WE dafür Zeit.

Sir Rufo 16. Jul 2015 22:37

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von stahli (Beitrag 1308954)
Ein Auszug wir schwierig und durch das ganze Projekt wirst Du Dich nicht wühlen wollen.
Ich nehme mir am WE dafür Zeit.

Weil das ganze Projekt auch völlig unnötig ist :roll:

Es geht doch nur um das BO und die Liste mit den Eigenschaften und die Stelle, wo so ein BO erzeugt wird.
Da ist der Speicherfresser. Warum sollte ich mir etwas anschauen, wo du bunte Striche malst?

BUG 16. Jul 2015 22:42

AW: Maßnahmen zum Speicherverbrauch minimieren
 
Zitat:

Zitat von Sir Rufo
Und du bastelst da nochmal ein Interface drum herum und das anscheinend auch noch falsch ...
Warum sollte ich mir etwas anschauen, wo du bunte Striche malst?

Da ist aber jemand angefressen :mrgreen:


Alle Zeitangaben in WEZ +1. Es ist jetzt 22:02 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz