Autor |
Beitrag |
Xion
      

Beiträge: 1952
Erhaltene Danke: 128
Windows XP
Delphi (2005, SmartInspect), SQL, Lua, Java (Eclipse), C++ (Visual Studio 2010, Qt Creator), Python (Blender), Prolog (SWIProlog), Haskell (ghci)
|
Verfasst: Mi 26.01.11 17:21
Hi,
folgende Situation (vereinfacht):
Delphi-Quelltext 1: 2: 3: 4: 5:
| type TDataPack=record Name: String; end;
List: array of TDataPack; |
Wenn ich jetzt das array nach und nach vergrößere und fülle, steigt mein RAM-Verbrauch stark an (im Taskmanager guck ich). Bei 40.000 Strings bin ich bereits in einer Region von 350MB und dann krieg ich eine "Out of memory" Exception.
Um rauszukriegen was das ist, hab ich das array von vornherein mit 1Mio Einträgen initialisiert und überall einen elend langen String reingeschrieben -> 150MB verbrauch. Kaum fängt mein Programm an und ersetzt dort jetzt die langen Strings durch kurze, steigt der Verbrauch rasch an. Das fand ich schonmal sehr seltsam. Dafür gab es so keine "Out of memory" Exception, aber ich hab ihn dann bei 0.5GB doch abgebrochen
Mehr oder weniger durch Zufall hab ich jetzt gemerkt:
Die Lösung ist, denn String gleich mit fester länge zu definieren:
Delphi-Quelltext 1: 2: 3:
| type TDataPack=record Name: String[255]; end; |
So bleibt der RAM-Verbrauch sehr stabil.
Was ich jetzt garnicht verstehe: Warum?
Ich habe jeweils jede Zelle nur 1x beschrieben. Wie kann es da zu einem so extremen RAM Anstieg kommen? Wenn der Effekt wäre, dass der Platz zu groß ist und jetzt in der array-struktur platz leer wäre, also lagert Delphi es aus und vergisst den großen Platz frei zu geben... Aber dann würde sich der Verbrauch ja maximal verdoppeln, und der Effekt wäre minimal wenn ich ein 150 Zeichen langen String durch einen 5 Zeichen langen ersetze. Und ich habe niemals nie einen größeren String reingeschrieben, als ich bei der Initialisierung hatte. Die 255 Zeichen ist viel zu viel für mich, doch brauch ich viel weniger Speicher
Wäre toll wenn mir das einer erklären kann ;) Moderiert von Narses: Topic aus Delphi Language (Object-Pascal) / CLX verschoben am Mi 26.01.2011 um 16:37
_________________ a broken heart is like a broken window - it'll never heal
In einem gut regierten Land ist Armut eine Schande, in einem schlecht regierten Reichtum. (Konfuzius)
|
|
jaenicke
      
Beiträge: 19314
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Mi 26.01.11 17:37
Dann hast du wahrscheinlich eine alte Delphiversion, oder? (Stimmt die im Profil?) Es gab da bis Delphi 2005 einige solcher Probleme, die aber ab Delphi 2006 sukzessive behoben wurden. Bei XE kann ich so etwas nicht reproduzieren, das Thema hatte ich schonmal.
Wie sieht denn dein Testcode aus?
|
|
Horst_H
      
Beiträge: 1654
Erhaltene Danke: 244
WIN10,PuppyLinux
FreePascal,Lazarus
|
Verfasst: Mi 26.01.11 17:45
Hallo,
Bei der Verarbeitung von Ansistrings kann man viele Zwischenergebnisse als "Speicherleichen" zurücklassen, weil die Referenzzählung nicht dahintergekommen ist, das Du diese nicht mehr verwendest.
Aber shortstring sind immer uniquestrings. Temporäre in Funktionen werden also immer nach verlassen gelöscht.
Die Auslagereung der Verarbeitung in eine Prozedur kann helfen:
www.delphi-forum.de/viewtopic.php?t=51490
Gruß Horst
Für diesen Beitrag haben gedankt: Xion
|
|
Xion 
      

Beiträge: 1952
Erhaltene Danke: 128
Windows XP
Delphi (2005, SmartInspect), SQL, Lua, Java (Eclipse), C++ (Visual Studio 2010, Qt Creator), Python (Blender), Prolog (SWIProlog), Haskell (ghci)
|
Verfasst: Mi 26.01.11 18:18
jaenicke hat folgendes geschrieben : | Dann hast du wahrscheinlich eine alte Delphiversion, oder? (Stimmt die im Profil?) Es gab da bis Delphi 2005 einige solcher Probleme |
Hab Delphi 2005, ja.
jaenicke hat folgendes geschrieben : | Wie sieht denn dein Testcode aus? |
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| procedure TForm1.FormCreate(Sender: TObject); var a: integer; begin HashTable:=THashTable.Create(1000001); for A:= 0 to HashTable.TableSize-1 do HashTable.Insert(A,'1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890'); for A:= 0 to HashTable.TableSize-1 do HashTable.delete(A);
end; |
In einem Timer 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
| var WebData, SearchedWord: String; A: integer; begin WebData:=http.Get('URL'); for A:= 0 to 99 do begin WebData:=Copy(WebData, Pos('ba',WebData) , MaxInt); WebData:=Copy(WebData, Pos('q=',WebData)+2 , MaxInt); SearchedWord:=AnsiLowerCase( UTF8ToANSi(httpdecode ( Copy(WebData, 1, Pos('\',WebData)-1 )) ) ); SearchedWord:=randomChar+randomChar+randomChar+randomChar+randomChar+randomChar; HashTable.Insert(GlobalWordCount,SearchedWord); GlobalWordCount:=GlobalWordCount+1; SearchedWord:=''; WebData:=''; end; end; |
Obwohl ich alles wegwerfe und dem SearchedWord einfach nur ne hand voll beliebige Chars zuweise, tritt der Effekt ein.
Lass ich aber die mit + markierten Zeilen weg, dann tritt der Effekt NICHT ein, dann sinkt sogar der Speicherverbrauch (es werden ja die langen durch kurze Strings ersetzt)...dabei tun die garnichts.
Hat auf Anhieb nichts gebracht. Muss ich nochmal später testen.
_________________ a broken heart is like a broken window - it'll never heal
In einem gut regierten Land ist Armut eine Schande, in einem schlecht regierten Reichtum. (Konfuzius)
|
|
Tastaro
      
Beiträge: 414
Erhaltene Danke: 23
|
Verfasst: Mi 26.01.11 18:23
Delphi-Quelltext 1:
| HashTable.Insert(A,'1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890'); |
Das sollte doch eigentlich immer den gleichen Hashwert erzeugen. Demzufolge wird deinen Hashtabelle nicht mit langen Strings vollgeschrieben, sondern nur Einträge bei dem einen Hashwert erzeugt.
Beste Grüße
|
|
Xion 
      

Beiträge: 1952
Erhaltene Danke: 128
Windows XP
Delphi (2005, SmartInspect), SQL, Lua, Java (Eclipse), C++ (Visual Studio 2010, Qt Creator), Python (Blender), Prolog (SWIProlog), Haskell (ghci)
|
Verfasst: Mi 26.01.11 18:27
Tastaro hat folgendes geschrieben : | Das sollte doch eigentlich immer den gleichen Hashwert erzeugen. |
Hehe, das ist genau der Grund warum ich nicht mit Code angefangen hab, da kommen wir vom hundertsten ins tausendste.
Dazu: Die HashTable hab ich selbst programmiert und ich weiß wie sie funktioniert. Der erste Wert ist der Key, der zweite nur ein String dazu. Den String in einen Integer "hashen" mach ich vorher. Die Hashtabelle ist nur dafür da, integer in eine Tabelle zu klatschen und zu suchen usw. (Wörterbuch: Es gehört zu dem key immer ein String. Mit dem String wird aber nichts getan, außer ihn zu speichern)
Der Code stimmt. Der funktioniert so, und es werden auch IMMER lange durch kurze Strings ersetzt.
_________________ a broken heart is like a broken window - it'll never heal
In einem gut regierten Land ist Armut eine Schande, in einem schlecht regierten Reichtum. (Konfuzius)
|
|
jaenicke
      
Beiträge: 19314
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Mi 26.01.11 21:34
Ich meinte jetzt eher den Code dazu: Xion hat folgendes geschrieben : | Um rauszukriegen was das ist, hab ich das array von vornherein mit 1Mio Einträgen initialisiert und überall einen elend langen String reingeschrieben -> 150MB verbrauch. Kaum fängt mein Programm an und ersetzt dort jetzt die langen Strings durch kurze, steigt der Verbrauch rasch an. |
Denn wie gesagt, unter XE kann ich sowas nicht reproduzieren, deshalb wäre es interessant, ob das daran liegt, dass ich den Code anders (besser  ) geschrieben habe oder an der Delphiversion. 
|
|
Xion 
      

Beiträge: 1952
Erhaltene Danke: 128
Windows XP
Delphi (2005, SmartInspect), SQL, Lua, Java (Eclipse), C++ (Visual Studio 2010, Qt Creator), Python (Blender), Prolog (SWIProlog), Haskell (ghci)
|
Verfasst: Mi 26.01.11 23:07
Es war zwar die betreffende Stelle, aber jetzt hab ich das ganze nochmal "extrahiert":
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:
| type TDataPack=record Name: String; Filled: boolean; OrderedNext: integer; end;
var Form1: TForm1; S: array of TDataPack; http: TidHTTP; BackUpWebData:String;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject); var a: integer; begin randomize; http:=TidHTTP.Create;
SetLength(S,499999); for A:= 0 to Length(S)-1 do S[A].Name:='Ford und Arthur beschlossen, sich einfach zurück zu lehnen und entsetzt zu sein.';
end;
function randomChar: Char; var x: integer; begin x:=ord('z')-ord('a'); Result:=chr(random(x)+ord('a')); end;
procedure TForm1.Timer1Timer(Sender: TObject); var WebData, SearchedWord: String; A, WordIndex: integer;
begin
if (BackupWebData='') then begin WebData:=http.Get('http://www.sieglin.de/arne/primzahlen.html'); BackupWebData:=WebData; end; for A:= 0 to 99 do begin WebData:=BackupWebData;
WebData:=Copy(WebData, Pos(' ',WebData) , MaxInt); SearchedWord:=randomChar+randomChar+randomChar+randomChar+randomChar+randomChar;
WordIndex:=random(Length(S)); S[WordIndex].Name:=SearchedWord; S[WordIndex].Filled:=True;
WebData:='';
end; end; |
Die mit + angemerkte Zeile entscheidet über Speicher Leak ja/nein.
Im Anhang ist das Projekt nochmal als ganzes.
PS: So ist das Leck auch weg:
Delphi-Quelltext 1: 2: 3: 4: 5:
| type TDataPack=record Name: String[255]; Filled: boolean; OrderedNext: integer; end; |
Einloggen, um Attachments anzusehen!
_________________ a broken heart is like a broken window - it'll never heal
In einem gut regierten Land ist Armut eine Schande, in einem schlecht regierten Reichtum. (Konfuzius)
|
|
jaenicke
      
Beiträge: 19314
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Do 27.01.11 00:11
Falls du Turbo Delphi hast, nimm einfach das, das Problem tritt ab der Version nicht mehr auf.
In den älteren Versionen tritt das Problem auf, ich habe es mit Delphi 7 und 2005 getestet. Aber u.a. der dort noch verwendete Speichermanager hatte eben diverse Probleme.
Du kannst es mit FastMM versuchen. Falls es nur ein Problem des Speichermanagers ist, könnte das helfen. Aber ich vermute tatsächlich ein Problem mit der Referenzzählung und dann hilft das nichts.
Was mir gerade auffällt: Du benutzt den String als Parameter und Rückgabewert für Copy. Der String, der der Rückgabewert ist, wird als zusätzlicher Parameter an Copy übergeben. Da das hier die selbe Variable ist, kann das das Problem sein, wenn die alten Delphiversionen dies nicht korrekt umsetzen.
Schonmal versucht BackupWebData direkt an Copy zu übergeben? (Ich probiere es gleich und schaue in den Assemblercode.)
// EDIT: Bringt leider nichts.
Zuletzt bearbeitet von jaenicke am Do 27.01.11 00:21, insgesamt 1-mal bearbeitet
Für diesen Beitrag haben gedankt: Xion
|
|
Martok
      
Beiträge: 3661
Erhaltene Danke: 604
Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
|
Verfasst: Do 27.01.11 00:16
Ich hatte ein ähnliches Problem mit einem Datenbanksystem unter Delphi 7.
FastMM hat das Problem vollständig behoben, von steigend ~150MB ist der Speicherverbrauch auf 6MB konstant gefallen.
_________________ "The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
Für diesen Beitrag haben gedankt: Xion
|
|
jaenicke
      
Beiträge: 19314
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Do 27.01.11 00:26
Ja, jetzt habe ich es getestet. In diesem Fall genügt es tatsächlich den Speichermanager durch FastMM zu ersetzen. Dann tritt das Problem nicht mehr auf.
(Was im Grunde auch logisch ist, da mit Delphi 2006 FastMM integriert wurde, deshalb klappt es ab da auch direkt.)
Für diesen Beitrag haben gedankt: Xion
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Do 27.01.11 01:24
Der alte Speichermanager von Delphi hatte auch in Hinblick auf die Fragmentierung des Speichers arge Probleme. Daher sollte man (nicht nur aus Performance-Gründen) bei bekannter Länge eines dynamischen Arrays diese direkt setzen, bzw. größere Sprünge machen und "Präalloziieren".
Zum Speicherverbrauch:
Array of T: Length*SizeOf(T) + 4
T=Record ... End: Summe der Einzelfelder zzgl. Alignment (meist 4 Bytes)
String: (Länge+1) + 8 + Alignment (meist 4 Byte) + 4 Bytes Zeiger
Bei 1 Mio Einträgen mit Strings mit jeweils Länge 121 sind das also:
4 + 1000000*SizeOf(121+1+8+2+4) = 4+1000000*136=136000004 = 136MB+4Byte 
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
Für diesen Beitrag haben gedankt: Xion
|
|
Xion 
      

Beiträge: 1952
Erhaltene Danke: 128
Windows XP
Delphi (2005, SmartInspect), SQL, Lua, Java (Eclipse), C++ (Visual Studio 2010, Qt Creator), Python (Blender), Prolog (SWIProlog), Haskell (ghci)
|
Verfasst: Do 27.01.11 10:46
jaenicke hat folgendes geschrieben : | Du kannst es mit FastMM versuchen. Falls es nur ein Problem des Speichermanagers ist, könnte das helfen. |
Jo, das scheint zu klappen. Cool find ich ja, dass er mir beim Beenden des Programms noch sagt, wo ich überall noch Speicherleichen habe  Ich dachte immer, globale Variablen (wie das http: TIdhttp) werden automatisch gelöscht...hmm, eigentlich müssten sie das auch...
BenBE hat folgendes geschrieben : | Daher sollte man (nicht nur aus Performance-Gründen) bei bekannter Länge eines dynamischen Arrays diese direkt setzen, bzw. größere Sprünge machen und "Präalloziieren". |
Das hat ja nichts gebracht  Ich habe es ja am Anfang auf utopisch groß gesetzt und er hat trotzdem zusätlich Speicher gefressen, dabei war bereits mehr als genug alloziiert.
Danke für die Berechnung 
_________________ a broken heart is like a broken window - it'll never heal
In einem gut regierten Land ist Armut eine Schande, in einem schlecht regierten Reichtum. (Konfuzius)
|
|
Gausi
      
Beiträge: 8548
Erhaltene Danke: 477
Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
|
Verfasst: Do 27.01.11 10:53
Xion hat folgendes geschrieben : | Ich dachte immer, globale Variablen (wie das http: TIdhttp) werden automatisch gelöscht...hmm, eigentlich müssten sie das auch... |
Nein. Was du selber erzeugst, muss du auch explizit selber wieder freigeben.
_________________ We are, we were and will not be.
Für diesen Beitrag haben gedankt: Xion
|
|
Xion 
      

Beiträge: 1952
Erhaltene Danke: 128
Windows XP
Delphi (2005, SmartInspect), SQL, Lua, Java (Eclipse), C++ (Visual Studio 2010, Qt Creator), Python (Blender), Prolog (SWIProlog), Haskell (ghci)
|
Verfasst: Do 27.01.11 10:55
Wieder was gelernt 
_________________ a broken heart is like a broken window - it'll never heal
In einem gut regierten Land ist Armut eine Schande, in einem schlecht regierten Reichtum. (Konfuzius)
|
|
|