Autor Beitrag
Xion
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
EE-Maler
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)
BeitragVerfasst: Mi 26.01.11 17:21 
Hi,

folgende Situation (vereinfacht):
ausblenden 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:

ausblenden 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 :roll:

Wäre toll wenn mir das einer erklären kann ;)


Moderiert von user profile iconNarses: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19314
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1654
Erhaltene Danke: 244

WIN10,PuppyLinux
FreePascal,Lazarus
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
EE-Maler
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)
BeitragVerfasst: Mi 26.01.11 18:18 
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
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.

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Wie sieht denn dein Testcode aus?

ausblenden 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);

  //die Tabelle ist also nach außen hin leer, die Strings bleiben aber gespeichert (damit ich lange Strings durch kurze ersetzen kann ;)
end;


ausblenden 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.


user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Die Auslagereung der Verarbeitung in eine Prozedur kann helfen:
www.delphi-forum.de/viewtopic.php?t=51490

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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 414
Erhaltene Danke: 23



BeitragVerfasst: Mi 26.01.11 18:23 
ausblenden 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
EE-Maler
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)
BeitragVerfasst: Mi 26.01.11 18:27 
user profile iconTastaro hat folgendes geschrieben Zum zitierten Posting springen:
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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19314
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mi 26.01.11 21:34 
Ich meinte jetzt eher den Code dazu:
user profile iconXion hat folgendes geschrieben Zum zitierten Posting springen:
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 :P) geschrieben habe oder an der Delphiversion. ;-)
Xion Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
EE-Maler
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)
BeitragVerfasst: Mi 26.01.11 23:07 
Es war zwar die betreffende Stelle, aber jetzt hab ich das ganze nochmal "extrahiert":


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:
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:
ausblenden 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19314
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19314
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
EE-Maler
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)
BeitragVerfasst: Do 27.01.11 10:46 
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
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...

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
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 :zustimm:

_________________
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
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8548
Erhaltene Danke: 477

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Do 27.01.11 10:53 
user profile iconXion hat folgendes geschrieben Zum zitierten Posting springen:
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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
EE-Maler
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)
BeitragVerfasst: Do 27.01.11 10:55 
Wieder was gelernt :mahn:

_________________
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)