![]() |
Mein Programm ist Arbeitsspeicher hungrig..
Hey,
ich habe eine TList mit ca. 41.000 Einträgen. Jeder dieser Einträge besteht aus 12 Booleans und einem String (maximal 5 Zeichen). Und genau hier entsteht mein Problem. Ich speichere die Daten total hässlich im Textformal ab (bsp: Toll;1;0;1;0;1;0;..). Die Datei ist gerade mal ~1MB groß. Wird das ganze aber in den Arbeitsspeicher geladen, nimmt es riesige Ausmaße an und ist 100MB groß. Woran liegt das denn bitte? |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Wie sieht die Datenstruktur eines Eintrages aus?
|
AW: Mein Programm ist Arbeitsspeicher hungrig..
Zitat:
Ich darf auch mal :stupid:. Ne, mal im Ernst: Ohne Code und die Kenntnis der Datenstruktur wird da keiner etwas dazu sagen können. MfG Dalai |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Achso klar, dachte das reicht:
Delphi-Quellcode:
IDListEx: TList<TTeUpdateDBIDState>;
und TTeUpdateDBIDState = record id: String; AccountBindOnUse: Boolean; AccountBound: Boolean; HideSuffix: Boolean; MonsterOnly: Boolean; NoMysticForge: Boolean; NoSalvage: Boolean; NoSell: Boolean; NotUpgradeable: Boolean; NoUnderwater: Boolean; SoulbindOnAcquire: Boolean; SoulBindOnUse: Boolean; Unique: Boolean; end; wobei der String zwischen 1 und 5 Zeichen schwankt, aber nie größer ist. Die Liste wird dann während der Laufzeit gefüllt und hat am and ~41.000 Einträge was 100MB Arbeitsspeicher bedeutet |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Und wie genau liest du die Daten ein?
MfG Dalai |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Kann ich nicht nachvollziehen
Delphi-Quellcode:
program dp_185142;
{$APPTYPE CONSOLE} {$R *.res} uses System.Generics.Collections, System.SysUtils; type TTeUpdateDBIDState = record id: string; AccountBindOnUse: Boolean; AccountBound: Boolean; HideSuffix: Boolean; MonsterOnly: Boolean; NoMysticForge: Boolean; NoSalvage: Boolean; NoSell: Boolean; NotUpgradeable: Boolean; NoUnderwater: Boolean; SoulbindOnAcquire: Boolean; SoulBindOnUse: Boolean; Unique: Boolean; end; procedure Test; var IDListEx: TList<TTeUpdateDBIDState>; LItem: TTeUpdateDBIDState; begin // TaskManager -> 588KB IDListEx := TList<TTeUpdateDBIDState>.Create; try while IDListEx.Count < 41000 do begin LItem.id := ( IDListEx.Count + 1 ).ToString; IDListEx.Add( LItem ); end; finally // TaskManager -> 2892KB IDListEx.Free; end; end; begin try Test; except on E: Exception do Writeln( E.ClassName, ': ', E.Message ); end; // TaskManager -> 1864KB ReadLn; end. |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Okay bin ratlos. Da habt ihr meinen Code..
Delphi-Quellcode:
Aufgerufen wird die GetIDInformation-Funktion..
unit TeUpdateDB;
interface uses System.Generics.Collections,IdHTTP, System.Threading, IdIOHandler, IdIOHandlerSocket, IdIOHandlerStack, IdSSL, IdSSLOpenSSL, System.Classes; type TTeUpdateDBStatus = record id: Integer; current,max: Integer; end; TTeUpdateDBIDState = record id: String; AccountBindOnUse: Boolean; AccountBound: Boolean; HideSuffix: Boolean; MonsterOnly: Boolean; NoMysticForge: Boolean; NoSalvage: Boolean; NoSell: Boolean; NotUpgradeable: Boolean; NoUnderwater: Boolean; SoulbindOnAcquire: Boolean; SoulBindOnUse: Boolean; Unique: Boolean; end; type TTeUpdateDB = class(TObject) private IDList: TStringList; IDListEx: TList<TTeUpdateDBIDState>; IDListTask: ITask; procedure AddToIDListEx(sl: TStringList;fstart,fend:Integer); procedure BuiltIDList; procedure BuiltIDListEx(fstart, fend: Integer); function CountEntries(s: String): Integer; procedure SplitEntries(sl: TStringList; s: String); function BToStr(b:Boolean): String; public state: TTeUpdateDBStatus; constructor Create; procedure GetIDInformation; procedure SaveIDListEx(p: String); end; implementation uses System.SysUtils, Vcl.Dialogs; const maxidrequ = 200; constructor TTeUpdateDB.Create; begin IDList := TStringList.Create; IDListEx := TList<TTeUpdateDBIDState>.Create; state.id := -2; end; procedure TTeUpdateDB.GetIDInformation; begin IDListTask := TTask.Create(procedure() var max,fstart,fend: Integer; begin BuiltIDList; //debugging state.id := 0; max := IDList.Count -1; state.max := max; fstart := 0; fend := -1; IDListEx.Clear; while fend <> max do begin fstart := fend + 1; fend := fstart + (maxidrequ-1); state.current := fstart; if fend > max then fend := max; if fstart > fend then break; BuiltIDListEx(fstart,fend); end; //debugging SaveIDListEx('test.dat'); end); IDListTask.Start; end; procedure TTeUpdateDB.SaveIDListEx(p: string); var sl:TStringList; i: Integer; begin sl := TStringList.Create; for i := 0 to IDListEx.Count-1 do begin sl.Add(IDListEx[i].id + ';' + BToStr(IDListEx[i].AccountBindOnUse) + ';' + BToStr(IDListEx[i].AccountBound) + ';' + BToStr(IDListEx[i].HideSuffix) + ';' + BToStr(IDListEx[i].MonsterOnly) + ';' + BToStr(IDListEx[i].NoMysticForge) + ';' + BToStr(IDListEx[i].NoSalvage) + ';' + BToStr(IDListEx[i].NoSell) + ';' + BToStr(IDListEx[i].NotUpgradeable) + ';' + BToStr(IDListEx[i].NoUnderwater) + ';' + BToStr(IDListEx[i].SoulbindOnAcquire) + ';' + BToStr(IDListEx[i].SoulBindOnUse) + ';' + BToStr(IDListEx[i].Unique)); end; sl.SaveToFile(p,TEncoding.UTF8); end; //########################################################################################################### procedure TTeUpdateDB.BuiltIDList; var http: TIdHttp; ssl: TIdSSLIOHandlerSocketOpenSSL; buffer: String; begin IDList.Clear; http := TIdHTTP.Create; ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil); http.IOHandler := ssl; buffer := http.Get('https://url.de/items'); buffer := StringReplace(buffer,'[','',[]); buffer := StringReplace(buffer,']','',[]); IDList.StrictDelimiter := True; IDList.Delimiter := ','; IDList.DelimitedText := buffer; end; procedure TTeUpdateDB.BuiltIDListEx(fstart,fend: Integer); var http: TIdHttp; ssl: TIdSSLIOHandlerSocketOpenSSL; buffer: String; ids: String; i: Integer; sl: TStringList; begin http := TIdHTTP.Create; ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil); http.IOHandler := ssl; sl := TStringList.Create; try ids := ''; for i := fstart to fend do begin if i <> fend then ids := ids + IDList[i] + ',' else ids := ids + IDList[i]; end; buffer := http.Get('https://url.de/items?ids=' + ids); SplitEntries(sl,buffer); //debugging if sl.Count <> CountEntries(buffer) then state.id := -1; AddToIDListEx(sl,fstart,fend); finally sl.Free; end; end; procedure TTeUpdateDB.AddToIDListEx(sl: TStringList; fstart: Integer; fend: Integer); var i: Integer; d: TTeUpdateDBIDState; pf: Integer; begin for i := 0 to sl.Count -1 do begin d.id := IDList[fstart+i]; pf := Pos('"flags":',sl[i]); if pf = 0 then begin d.AccountBindOnUse := false; d.AccountBound := false; d.HideSuffix := false; d.MonsterOnly := false; d.NoMysticForge := false; d.NoSalvage := false; d.NoSell := false; d.NotUpgradeable := false; d.NoUnderwater := false; d.SoulbindOnAcquire := false; d.SoulBindOnUse := false; d.Unique := false; end else begin d.AccountBindOnUse := (Pos('"AccountBindOnUse"',sl[i],pf) <> 0); d.AccountBound := (Pos('"AccountBound"',sl[i],pf) <> 0); d.HideSuffix := (Pos('"HideSuffix"',sl[i],pf) <> 0); d.MonsterOnly := (Pos('"MonsterOnly"',sl[i],pf) <> 0); d.NoMysticForge := (Pos('"NoMysticForge"',sl[i],pf) <> 0); d.NoSalvage := (Pos('"NoSalvage"',sl[i],pf) <> 0); d.NoSell := (Pos('"NoSell"',sl[i],pf) <> 0); d.NotUpgradeable := (Pos('"NotUpgradeable"',sl[i],pf) <> 0); d.NoUnderwater := (Pos('"NoUnderwater"',sl[i],pf) <> 0); d.SoulbindOnAcquire := (Pos('"SoulbindOnAcquire"',sl[i],pf) <> 0); d.SoulBindOnUse := (Pos('"SoulBindOnUse"',sl[i],pf) <> 0); d.Unique := (Pos('"Unique"',sl[i],pf) <> 0); end; IDListEx.Add(d); end; end; function TTeUpdateDB.CountEntries(s: String): Integer; var p: Integer; begin p := 1; result := 0; while p <> 0 do begin p := Pos('{"name":',s,p+1); if p <> 0 then Inc(result); end; end; procedure TTeUpdateDB.SplitEntries(sl: TStringList; s: String); var p, pp: Integer; b: Boolean; begin sl.Clear; b := true; p := 0; while b do begin p := Pos('{"name":',s,p+1); //1.Item pp := Pos('{"name":',s,p+1); //2.Item if pp = 0 then begin b := false; pp := Length(s); end else begin pp := pp - 1; end; sl.Add(Copy(s,p,pp-p)); end; end; function TTeUpdateDB.BToStr(b: Boolean): String; begin if b then result := '1' else result := '0'; end; end. |
AW: Mein Programm ist Arbeitsspeicher hungrig..
41.000 * 16 Record-Bytes = 656.000 Byte (TList legt das in einem Block in den RAM ... bei 64 KB-Speicherblöcken macht das 10,0098 Blöcke = aufgerundet 11*64 = 720.896 Bytes)
+ 41.000 * (4 Längen-Bytes + 2 CharSize-Bytes + 2 CodepageBytes + 2*2 abschließende #0-Bytes + 5*2 Unicode-Bytes) = 22 ... FastMM wird das vermutlich im 32er-Block ablegen = 1.312.000 Bytes effektiv also 2 MB (in Delphi ab Version 2009) und dazu kommt dann noch die VCL, usw. aber jetzt kommt es noch darauf an, was du eigentlich soonst noch machst, vorallem mit den Strings, wie du die Liste befüllst (mit oder einer passenden Capacity) und wie die deine Speicherverwaltung aussieht, was du für eine Delphiversion benutzt uvm. Wenn das wirklich so viel wird dann liegt das vermutlich an dir und eventuell einer wunderschönen Speicherfragmentierung. Was sagt denn z.B. GetHeapStatus, GetMemoryManagerState, wieviel es wirklich ist? Und wie sieht es mit Speicherlecks aus? (ReportMemoryLeaksOnShutdown) [edit] Deine code hab ich mir jetzt nicht angesehn (ist auch schon spät) |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Das lässt sich viel besser speichern:
EinString : array [0..4] of ANSIChar; // 5 Bytes * 41000 = 205 kB BooleanWerte : uses System.Classes.TBits.Bits var Bits: TBits; I: Byte; begin { Create a a bit set. } Bits := TBits.Create; { Set the size of the bit set. } Bits.Size := 12; // 12 BooleanWerte 61,5 kB ------------------------------------------SUMME : 266,5 kB ![]() |
AW: Mein Programm ist Arbeitsspeicher hungrig..
schaut euch mal bitte meine Code an. Das ist alles. Was ist daran falsch, bzw wieso wird so viel Speicher verbraucht? Die Strings die heruntergeladen werden sind schon relativ groß ~1-2MB allerdings sollten die ja im Grunde keine Rolle spielen, da sie nur lokal sind und dann nicht im speicher bleiben.. Also woran kann das liegen?
|
AW: Mein Programm ist Arbeitsspeicher hungrig..
Da werden einige Variablen Objekte (Krümelkackermode: "Instanz einer Klasse") erzeugt, aber nicht wieder freigegeben, vor allem in den Funktionen BuiltIDList und BuiltIDListEx. Da letztere noch dazu in einer Schleife x-fach aufgerufen wird, dengeln dann mehrere Objekte von http und ssl im Speicher rum -> Speicherleck. Dagegen hilft einerseits konsequentes Einhalten von Coding-Richtlinien* und andererseits ReportMemoryLeaksOnShutdown oder andere Tools zum Finden von Speicherlecks.
[EDIT] Da gibt's sogar noch weitere Objekte in den anderen Funktionen, die nicht wieder freigegeben werden. [/QUOTE] *) Man muss sich einfach selbst zwingen, sauber zu schreiben, d.h. sofort beim Erzeugen eines Objekts das Freigeben gleich mit hinschreiben, bevor man sich an den eigentlichen Code zum Verwenden des Objekts macht. Mal ein Beispiel: erst dieses Rumpfgerüst schreiben:
Delphi-Quellcode:
und dann erst im
objekt:= TKlasse.Create;
try // Code finally objekt.Free end;
Delphi-Quellcode:
-Block den eigentlichen Code hinzufügen. So gehe ich jedenfalls vor.
try
MfG Dalai PS: Übrigens ist der Name BuiltIDList falsch, es müsste BuildIDList heißen, denn das Teil wird erst aufgebaut und wurde nicht bereits erzeugt (built = Vergangenheitsform von build). |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Entwickelst du für die Mobile Plattforms oder warum gibts du da einige Sachen nicht frei?
Prüfe deinen Code - überall wo du Instanzen erzeugst musst du die irgendwann auch wieder frei geben. Das sehe ich aber nicht bei allen Instanzen ... |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Ja das ist korrekt. Werde das nochmal überarbeiten. Danke
Vielen Dank. Es lag an den vielen Instanzen von IDHttp und dem SSL Teil. Funktioniert jetzt bestens mit 5MB RAM-Verbrauch. Records und Variablen müssen nicht freigegeben werden oder? |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Zitat:
Delphi-Quellcode:
Und Objekte, die man erzeugt, muss man auch wieder freigeben.
var sl: TStringList;
MfG Dalai |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Zitat:
|
AW: Mein Programm ist Arbeitsspeicher hungrig..
Hallo,
ich würde noch packend Record schreiben. Für Speicherlecks werfe ich noch FastMM4 rein.. Heiko |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Packed wird hier nichts ändern und bei aktiv genutzten Records im Speicher ist wowas es etwas ungünstiger.
Vorner der 4 Byte Pointer und dahinter eine gute Anzahl an Bytes, womit am Ende alles praktisch schon gepackt ist, da keine Leerstellen entstehen und es sind sogar so viele Bytes (Boolean), daß der Record voll ist. (vielfaches von 4) Und dann ist das Array mit den Records eh nichtmal 2 MB groß. Wie hier welche entdeckt haben, liegt das Problem ja ganz wo anders. (Speicherlecks) |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Zitat:
{$A1} Und NATÜRLICH keinen Logstring nehmen.... Wie oft steht da der gleiche String drin? |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Bytes sind nie Aligned, bzw. immer an 1 ausgerichtet, egal ob mit oder ohne Packed.
{$ALIGN} gibt nur die maximale Ausrichtung an und kleineren Typen werden immer an ihrer eigenen Größe ausgerichtet. Zitat:
Die Strings werden alle "neu" erzeugt (teilweise aus einem Anderen rauskopiert) und sind demnach immer unique. (gibt standardmäßig "leider" keinen Algo, der das zusammenfasst) |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Zitat:
|
AW: Mein Programm ist Arbeitsspeicher hungrig..
Zitat:
Delphi-Quellcode:
type
A = packed Record // Sizeof = 5 S : String; B1 : byte; End; B = Record // Sizeof = 8 S : String; B1 : byte; End; |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Ja, aber in seinem Record gibt es "zufällig" genau ein Vielfaches von 4, an Bytes/Booleans.
Und wie gesagt, die Liste mit allen Records selber ist grade mal 0,7 MB groß winzig. Wobei die gesamten 100 MB nun eigentlich auch nicht wirklich soooooo viel sind. :angle: PS: In XE8 hat man ja grade noch den "Spaß", daß generische Listen mit SizeOf(T) > 4 kaputt sind. |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Zitat:
|
AW: Mein Programm ist Arbeitsspeicher hungrig..
![]() Einen Hotfix gab es inzwischen zwar, aber da wurde diesbezüglich nichts behoben. |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Zitat:
|
AW: Mein Programm ist Arbeitsspeicher hungrig..
Mavarik ist jetzt ein super Beispiel, finde ich: Woher hätte er das eigentlich wissen sollen?
Das weiß man doch nur wenn man den halben Tag in der Delphi-Praxis oder StackOverflow hängt. Oder Marco Cantus Google Plus-Beiträge belauert. Aber sonst? Außer einem Google+-Post von Marco habe ich von Embarcadero noch überhaupt nichts in der Richtung gesehen. Oder wenn man sich fragt warum sich die Anwendung seit der testweisen Migration auf XE8 so komisch verhält. Dann weiß man es nach einer halben Stunde vielleicht auch ;-) |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Von den Speicherlecks abgesehen:
Wenn du 12 booleans aus jedem Record platzsparend speichern willst, dann machst du einen Aufzähltyp für die zwölf Variablen und speicherst die 12 boolean Werte in einer Variable von Typ set of Aufzähltyp. Dann brauchen die 12 Booleans zusammen nur zwei Bytes. Und den 5 byte langen String kannst du in einem array[1..5] of AnsiChar stecken (mit Null-Bytes auffüllen, wenn der string kürzer ist) - ausser natürlich, die Strings können unicode Zeichen beinhalten, die nicht in 8 bit darstellbar sind. Dann braucht jeder deiner 41000 Einträge genau 7 bytes komplett inklusive der Strings. Wenn du die Variablen als properties der Klasse deklarierst und entsprechende getter und setter schreibst, ist das völlig transparent, nur natürlich zur Laufzeit etwas langsamer als eine native Speicherung der Daten. |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Delphi-Quellcode:
Aber vorher sollten sowieso die Speicherlecks behoben werden, bevor man veruscht die läppischen 2 MB auf 280 KB, bzw. 320 KB mit ShortString oder 480 KB mit WideChar zu schrumpfen. :roll:
record
S: String[5]; // oder Array of Char/AnsiChar/WideChar W: Word; procedure Setter(Value: Boolean); // if Value then W := W or (1 shl Index) else ... function Getter: Boolean; // Result := W and (1 shl Index) <> 0; property W1: Boolean index 0 read Getter write Setter; property W2: Boolean index 1 read Getter write Setter; property W3: Boolean index 2 read Getter write Setter; ... end; |
AW: Mein Programm ist Arbeitsspeicher hungrig..
Wie war das mit gleichen Strings?
|
AW: Mein Programm ist Arbeitsspeicher hungrig..
Bei einem ShortString/CharArray ist das egal, da eh jeder seinen eigenen Speicher hat.
Ansonsten kann man gern sonstwie durch das Array laufen und das zusammenfassen (also nur bei LongStrings)
Delphi-Quellcode:
Ein CharArray mit 5 Bytes/AnsiChars wird insgesamt aber vermutlich dennoch weniger Speicher brauchen, als due zusammengefassten LongStrings.
for i := Low(A) to High(A) - 1 do
for j := i + 1 to High(A) do if A[i].S = A[j].S then A[i].S := A[j].S; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:48 Uhr. |
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 by Thomas Breitkreuz