dyn array kopieren mit a:=b und b dann nil setzen? [gelöst]

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
laz847
Beiträge: 114
Registriert: Mi 18. Jun 2014, 16:39

dyn array kopieren mit a:=b und b dann nil setzen? [gelöst]

Beitrag von laz847 »

Hallo zusammen! In meiner Software (leider mehr als 10000 Zeilen) entsteht sporadisch eine Access Violation, die suche ich aber dazu später mehr. Bei der Suche stellten sich mir nun weitere Fragen. Ich muss dazu sagen das ich mit Threads, Pointern und Memory Mapped Files arbeite, die Fehlersuche + debuggen ist teilweise schwierig. Vereinfacht dargestellt erstmal grundlegende Fragen:

Code: Alles auswählen

type 
tdaten   = record; // (shortstrings, byte, ints, doubles >> also nur zahlen und 2 shortstring)
tarr_dat = array of tdaten;
 
var
globA : array of tarr_dat; // mein globales 2 dim. dynamisches array, wird im hauptprogramm 1 x beim Create resized auf z.b. SetLength(globA , 40 , 0);
 
procedure TEvoThread.Execute;
var
 
 tmp : tarr_dat;
 
 _pos : Byte;
 
begin
 
 _pos := getpos(tid); // pos für diesen thread, bereich 0..39, vereinfacht dargestellt eigentlich läuft hier ein Syncronize
 
 while(w1) do begin
 
  tmp := nil; // (erstes) vorher SetLength(tmp,0)
 
  while(w2) do begin 
   [.......] //  tmp[]  wird resized und neu befüllt
  end;
 
  if(Length(globA[_pos]) <> Length(tmp)) then globA[_pos] := nil; // oder immer nil setzen?
 
  globA[_pos] := tmp;
 
  tmp := nil; // (zweites) eigentlich unnötig, nur für Frage 4
 
 end;
 
end;
Wie gesagt der eigentliche Code ist viel komplexer ich versuche das hier vereinfacht darzustellen, um Euch nicht mit soviel Quelltext zu plagen.

1. Ich habe vorher SetLength(0) verwendet, allerdings finden sich gegenteilge Aussagen darüber ob SetLength(0) auch wirklich den Speicher komplett freigibt? Bzw. bleiben da wohl paar Bytes übrig, in meinem Fall taktet das alle 100 ms, da sollen keine Bytes übrig bleiben?

2. array := nil soll schneller sein als SetLength(0), mir kommts genau andersrum vor? Was ist richtig nil, SetLength(), Finalize()?

3. Wie man sehen kann wird globA[_pos] := nil; gesetzt, danach globA[_pos] := tmp; die Referenz kopiert, ohne vorab SetLength auf globA[_pos] anzuwenden. Ich wollte eigentlich nur mal testen ob das geht und es geht, gibts dazu Bedenken? Muss ich vorab SetLength() verwenden? Wäre es sinnvoller immer globA[_pos] := nil; zu setzen vor dem kopieren?

4. tmp := nil; (zweites) >> Wäre ja eigentlich unnötig da tmp beim verlassen der routine eh freigegeben wird. Ich habe jedoch gelesen (daher die Frage), dass dieses nil dafür sorgt das globA[_pos] dann ebenfalls nil sein soll??? Ist es aber nicht und genau das will ich auch so, in meinem globalen Array sollen ja die Daten vorhanden bleiben. Meiner Meinung nach wird die Referenz kopiert und tmp "genilt", globA[_pos] enthält dann die einzige Referenz, wenn ich dann später globA[_pos] := nil setze dürfte alles freigegeben sein oder? Was wäre wenn ich SetLength(tmp,0) an dieser Stelle mache? Liege ich richtig das ich dann Copy() nutzen müsste, da sonst ebenfalls Length(globA[_pos]) = 0 ist??

5. if A and B are dynamic arrays, never write A := B; Quelle "http://delphiblog.twodesk.com/dynamic-arrays-how-they-work-and-how-to-use-them"
In meinem Fall kopiere ich die Referenz eines eindimensionales dyn. Array in die 2. Dimension eines zweidimensionalen Arrays. Why not? Wäre globA[_pos] := Copy(tmp); besser / schneller / langsamer?

6. Das globale Array wird durch 10 verschiedene Threads befüllt, jeder Thread befüllt immer den selben Speicherplatz. Die Anzahl also Length(tmp) variiert. Nur die Threads dürfen in globA schreiben, nil setzen oder Änderungen vornehmen. Ich muss aber im Hauptprogramm nach jedem Thread(s)durchlauf das Array lesen. Ist der schreibende (thread) / lesende (main) Zugriff problematisch?

Fettes Danke !!!!!! ;)
Zuletzt geändert von laz847 am So 5. Apr 2015, 17:42, insgesamt 9-mal geändert.

Socke
Lazarusforum e. V.
Beiträge: 3177
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: dyn array kopieren mit a:=b und b dann nil setzen?

Beitrag von Socke »

laz847 hat geschrieben:1. Ich habe vorher SetLength(0) verwendet, allerdings finden sich gegenteilge Aussagen darüber ob SetLength(0) auch wirklich den Speicher komplett freigibt? Bzw. bleiben da wohl paar Bytes übrig, in meinem Fall taktet das alle 100 ms, da sollen keine Bytes übrig bleiben?
Sagt dir heaptrc etwas? Falls nicht, ganz schnell informieren und verwenden!
laz847 hat geschrieben:3. Wie man sehen kann wird globA[_pos] := nil; gesetzt, danach globA[_pos] := tmp; die Referenz kopiert, ohne vorab SetLength auf globA[_pos] anzuwenden. Ich wollte eigentlich nur mal testen ob das geht und es geht, gibts dazu Bedenken? Muss ich vorab SetLength() verwenden? Wäre es sinnvoller immer globA[_pos] := nil; zu setzen vor dem kopieren?
Dynamische Arrays sind automtatisch referenzgezählt (wie Strings, aber ohne CopyOnWrite). Daraus folgt:
  • Den bisherigen Array globA[_pos] manuell zu leeren ist überflüssig, da es ggf. automatisch durchgeführt wird.
  • Wenn du SetLength(globA[_pos], X) aufrufst, wird zuerst der Array erstellt, dann aber wieder freigegeben.
laz847 hat geschrieben:4. tmp := nil; (zweites) >> Wäre ja eigentlich unnötig da tmp beim verlassen der routine eh freigegeben wird. Ich habe jedoch gelesen (daher die Frage), dass dieses nil dafür sorgt das globA[_pos] dann ebenfalls nil sein soll??? Ist es aber nicht und genau das will ich auch so, in meinem globalen Array sollen ja die Daten vorhanden bleiben. Meiner Meinung nach wird die Referenz kopiert und tmp "genilt", globA[_pos] enthält dann die einzige Referenz, wenn ich dann später globA[_pos] := nil setze dürfte alles freigegeben sein oder? Was wäre wenn ich SetLength(tmp,0) an dieser Stelle mache? Liege ich richtig das ich dann Copy() nutzen müsste, da sonst ebenfalls Length(globA[_pos]) = 0 ist??
Wie geschrieben, sind dynamsiche Arrays referenzgezählt. Beispiel:

Code: Alles auswählen

type tarr = array of byte;
var
  a1: tarr;
  a2: tarr;
begin
  // Zu Beginn: Anzahl Referenzen = 0
  SetLength(a1, 10); // Anzahl Referenzen = 1
  a2 := a1; // Anzahl Referenzen = 2
  a1 := nil; // Anzahl Referenzen = 1
  a2 := nil; // Anzahl Referenzen = 0, Speicher wird freigeben.
end;
laz847 hat geschrieben:5. if A and B are dynamic arrays, never write A := B; Quelle "http://delphiblog.twodesk.com/dynamic-arrays-how-they-work-and-how-to-use-them"
In meinem Fall kopiere ich die Referenz eines eindimensionales dyn. Array in die 2. Dimension eines zweidimensionalen Arrays. Why not? Wäre globA[_pos] := Copy(tmp); besser / schneller / langsamer?
Solange man weiß, wass passiert, ist A := B vollkommen in Ordnung. Wichtig zu wissen ist, dass A[0] ist äquivalent zu B[0] - das heißt, in beiden Fällen bearbeitet man die selbe Stelle im Speicher. SetLength(B,x) ändert aber nur die Variable B, nicht aber den Array, auf den A zeigt.
Beispiel:

Code: Alles auswählen

program Project1;
type
  tarr = array of byte;
var
  a1: tarr;
 
procedure dosth;
var
  a2: tarr;
begin
  a2 := a1;  // a1 und a2 zeigen auf den selben Speicher
  setlength(a2,12);  // Length(a2) = 12, Length(a1) = 10
end;
 
begin
  SetLength(a1,10);  // Length(a1) = 10
  dosth;
end.
laz847 hat geschrieben:6. Das globale Array wird über die xyz() durch 10 verschiedene Threads befüllt, jeder Thread befüllt immer den selben Speicherplatz. Die Anzahl der nötigen Speicherplätze variiert, ebenso variiert Length(tmp) trotzdem werden immer nur 10 gleichzeitig benötigt. Nur die Threads dürfen in globA schreiben, nil setzen oder Änderungen vornehmen. Ich muss aber im Hauptprogramm nach jedem Thread(s)durchlauf das Array lesen. Ist der schreibende (thread) / lesende (main) Zugriff problematisch?
Das kann problematisch werden, siehe oben. Sofern die Threads sich nur den Array holen und Daten hineinschreiben, ist alles in Ordnung. Ändern Sie die Länge "ihres" Arrays, müssen Sie ihren neuen Array wieder dem globalen Array bekannt machen.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

laz847
Beiträge: 114
Registriert: Mi 18. Jun 2014, 16:39

Re: dyn array kopieren mit a:=b und b dann nil setzen?

Beitrag von laz847 »

Cool, recht vielen Dank für die umfassenden Erklärungen und sogar mit Beispielen :mrgreen: ! Danke!
Sagt dir heaptrc etwas? Falls nicht, ganz schnell informieren und verwenden!
Ja :D!! Wo du es sagst, es steht noch oben in den uses, aber auskommentiert und ich weiß jetzt auch wieder warum. Da ich beim Beenden selbst alles leere, lösche und freigebe war da nie etwas was aus meinem Code stammte. Trotzdem sprang heaptrc immer beim Beenden an, weil es in irgendwelchem anderen Code Leaks gefunden hatte, es war irgendwas von Lazarus irgendwelche Komponenten die ich nicht alle ändern mag und ich musste das Lazarus neu kompilieren mit -glh um überhaupt zu sehen das es gar nicht in meinem Code war :evil:. Aber funktioniert das auch während ein Thread loopt? Geht das zur Laufzeit? Ich kann doch nur beim Beenden schauen ob alles ok ist, ist es aber weil meine Routine alles freigibt?
Den bisherigen Array globA[_pos] manuell zu leeren ist überflüssig, da es ggf. automatisch durchgeführt wird.
Auch wenn sich die Länge immer wieder +1, -5, +6, -10 ändert? Der Überhang wird beim verkleinern automatisch freigegeben? Length(tmp) kann z.B. jetzt 100 sein und beim nächsten Durchlauf nur 90, es kann mehr oder weniger sein als beim letzten Update des globA[_pos]. Da wird nix resized, nix freigegeben? Das wirkt irgendwie so schlampig, wenn ich das nem Freund (der nur PHP kennt) zeige, versteht der nicht warum das überhaupt geht :mrgreen:.
Wenn du SetLength(globA[_pos], X) aufrufst, wird zuerst der Array erstellt, dann aber wieder freigegeben.
Ohh ich hoffe ich verstehe Dich richtig, ich meinte ob ich vor dem "globA[_pos] := tmp;" ein SetLength(globA[_pos] , Length(tmp)) machen muss, also nach dem "then globA[_pos] := nil;"
Wie geschrieben, sind dynamsiche Arrays referenzgezählt.
Ok, bedeutet ich kann tmp := nil nach dem kopieren verwenden und mein globA[_pos] wird davon nicht mehr beeinflußt. Wenn ich dann bei Bedarf oder beim Beenden der Hauptanwendung globA := nil setze wird der Speicher freigegeben, das hab ich so. Ich weiß der gibt das beim Beenden frei, ich hab dafür aber ne eigene Routine um alles besser nachvollziehen zu können.
Solange man weiß, wass passiert, ist A := B vollkommen in Ordnung. Wichtig zu wissen ist, dass A[0] ist äquivalent zu B[0] - das heißt, in beiden Fällen bearbeitet man die selbe Stelle im Speicher. SetLength(B,x) ändert aber nur die Variable B, nicht aber den Array, auf den A zeigt.
Genau das, dass A äquivalent zu B ist, will ich ich ja, tmp dient nur als temporäres Array dessen Kopie an einem bestimmten Speicherplatz im globalen Array abgelegt wird. Danach wird tmp erneut verwendet und nach den Berechnungen wird wieder das globale Array geupdatet.
Das kann problematisch werden, siehe oben. Sofern die Threads sich nur den Array holen und Daten hineinschreiben, ist alles in Ordnung. Ändern Sie die Länge "ihres" Arrays, müssen Sie ihren neuen Array wieder dem globalen Array bekannt machen.
Dann sollte das so ok sein, ich verwende in den Threads das tmp Array (function xyz() ist der Thread.Execute :D) und kopiere dann am Ende des Thread.Execute tmp auf seinen Platz im globA[sein-platz]. Die Bekanntmachung ist das globA[_pos] := tmp;

Und nochmals danke ;)

edit : heaptrc infos entfernt >> evtl. ein bug in meiner Umgebung
Zuletzt geändert von laz847 am Mo 30. Mär 2015, 17:31, insgesamt 2-mal geändert.

Michl
Beiträge: 2511
Registriert: Di 19. Jun 2012, 12:54

Re: dyn array kopieren mit a:=b und b dann nil setzen?

Beitrag von Michl »

laz847 hat geschrieben:Trotzdem sprang heaptrc immer beim Beenden an, weil es in irgendwelchem anderen Code Leaks gefunden hatte, es war irgendwas von Lazarus irgendwelche Komponenten die ich nicht alle ändern mag und ich musste das Lazarus neu kompilieren mit -glh um überhaupt zu sehen das es gar nicht in meinem Code war :evil:.
Sei bitte vorsichtig mit solchen Behauptungen. Es wäre zumindest wichtig, wenn du schon ein Memoryleak bei einer Lazaruskomponente findest, dieser auf den Grund zu gehen, diese auf ein Minimalbsp. zu reduzieren und ein Bugreport zu erstellen (nur so funktioniert das Prinzip OpenSource).
Bei mir haben sich solche Memoryleak oft auf eigene Fehler zurückführen lassen. Größere Probleme kenn ich hauptsächlich nur bei der Verwendung von externen Libraries.

Wenn ich das richtig lese, machst du lt. dem Stacktrace ja irgendwas mit einem SpinEdit (ich weiss nicht, ob du evtl. welche dynamisch erstellst). Evtl. kannst du diesen Fehler mal auf ein Minimum reduzieren?! Da ich scheinbar eine andere Lazarusversion als du benutze, stimmen die Zeilennummern mit der Wincontrol.inc bei Dir nicht überein, evtl. war es auch ein bekannter Fehler, der im Trunc schon behoben ist?!

laz847 hat geschrieben:2. array := nil soll schneller sein als SetLength(0), mir kommts genau andersrum vor?
Das kann sein. Es werden bei der Allocation des Speichers ganze Blöcke reserviert. Diese Blöcke werden solange im Betriebssystem nicht wieder freigegeben, bis sie tatsächlich nicht wieder benötigt werden. K.A., ob das Prinzip bei der Zuweisung array:=Nil, ausgehebelt wird (müsste man mal testen).

Bsp:

Code: Alles auswählen

var
  a: Array of Integer;
begin
  SetLength(a, 100);
  SetLength(a, 0);
  SetLength(a, 50); 
Hier wird im Regelfall immer wieder der gleiche Speicher vom Betriebssystem zur Verfügung gestellt. Erst bei einem z.B. SetLength(a, 1000) wird ein neuer Speicher angefordert, da der benötigte Speicherplatz nicht mehr in dem bisherigen zur Verfügung gestellten passt.

Ich konnte mal in einem Programm eine erhebliche Geschwindigkeitssteigerung erzielen, indem ich zuerst den vermutlich maximal genutzten Speicher reserviert hatte, diesen dann sofort wieder auf Null gesetzt hatte und dann erst die Routine, die den Speicher dynamisch angefort hatte, gestartet hatte.

Das gleiche gilt übrigens auch für Strings. Es wird vom Betriebssystem mehr Speicher (IMHO ca. doppelt so viel) für einen String zur Verfügung gestellt, als er im Moment der ersten Zuweisung benötigt. Damit können z.B. Zeichen an den String angehangen werden, ohne daß dieser bei jeder Anfügung sofort komplett an eine neue Speicherstelle verschoben werden muss.

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection;  

Socke
Lazarusforum e. V.
Beiträge: 3177
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: dyn array kopieren mit a:=b und b dann nil setzen?

Beitrag von Socke »

laz847 hat geschrieben:
Den bisherigen Array globA[_pos] manuell zu leeren ist überflüssig, da es ggf. automatisch durchgeführt wird.
Auch wenn sich die Länge immer wieder +1, -5, +6, -10 ändert? Der Überhang wird beim verkleinern automatisch freigegeben? Length(tmp) kann z.B. jetzt 100 sein und beim nächsten Durchlauf nur 90, es kann mehr oder weniger sein als beim letzten Update des globA[_pos]. Da wird nix resized, nix freigegeben? Das wirkt irgendwie so schlampig, wenn ich das nem Freund (der nur PHP kennt) zeige, versteht der nicht warum das überhaupt geht :mrgreen:.
Solange du SetLength() verwendest, wird der gesamte Speicher des Arrays automatisch verwaltet (gilt natürlich nicht für die Daten, die in dem Array gespeicher werden!).

Code: Alles auswählen

type tarr = array of byte;
var
  a: tarr;
  b: tarr;
begin
  SetLength(a, 10); // Speicher wird reserviert
  SetLength(b, 5);  // Speicher wird reserviert
  a := b; // Urpsrüngliches a wird freigegeben und zeigt danach auf den Array b
end;
Die Funktionsweise ist in etwa (bezogen auf den Code in diesem Beitrag):
Eine Array-Variable zeigt nach SetLength() auf einen Speicherplatz mit Verwaltungsdaten. Dort wird ein Referenzzähler (Anzahl der Variablen, die auf diesen Speicherplatz zeigen) und ein weiterer Zeiger gespeichert. Dieser weitere Zeige zeigt auf die eigentlichen Daten (Nutzdaten), die im Array gespeichert werden.
Bei der Zuweisung a := b passiert folgendes:
  • Der Referenzzähler von a wird um eins vermindert, da die Referenz a wegfällt.
  • Da der Referenzzähler auf 0 sinkt, werden die Nutzdaten und die Verwatlungsdaten freigegeben.
  • Die Variable a soll auf die Verwaltungsdaten von b zeigen. Daher wird dort der Referenzzähler um eins erhöht.
Sobald eine Array-Variable ihre Gültigkeit verliert, tritt sinngemäß der gleiche Ablauf wie bei der Zuweisung a := nil ein.
Die Gültigkeit hängt davon ab, wo die Variable definiert ist. Bei globalen Variablen ist dies das Ende des Programms, bei Threadvariablen ist es das Ende des Threads, bei lokalen Variablen das Ende der Funktion/Prozedur/Methode und bei Klassen bzw. Records der Zeitpunkt, sobald diese freigegeben werden.

Die gesamte Verwaltung wird vom Compiler beim Übersetzen des Quelltextes in dein Programm eingefügt. Trifft der Compiler z.B. auf das Ende einer Funktion, sucht er dort nach lokalen Variablen für dynamischen Arrays. Für jede Variable wird dann Code eingefügt um den Referenzzähler zu vermindern und ggf. den Speicher freizugeben.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

laz847
Beiträge: 114
Registriert: Mi 18. Jun 2014, 16:39

Re: dyn array kopieren mit a:=b und b dann nil setzen?

Beitrag von laz847 »

Sei bitte vorsichtig mit solchen Behauptungen. Es wäre zumindest wichtig, wenn du schon ein Memoryleak bei einer Lazaruskomponente findest, dieser auf den Grund zu gehen, diese auf ein Minimalbsp. zu reduzieren und ein Bugreport zu erstellen. Wenn ich das richtig lese, machst du lt. dem Stacktrace ja irgendwas mit einem SpinEdit (ich weiss nicht, ob du evtl. welche dynamisch erstellst)
Ich sag nur was da steht, win32wscontrols.pp, win32wsspin.pp usw. sind ja nicht von mir und wenn heaptrc da ein Leak anzeigt, wenn ich meine Software nur starte und ohne eine Funktion aufzurufen sofort wieder beende, liegt das nahe oder ? Ich nutze SpinEdits, diese werden aber nicht dynamisch erzeugt, es liegen 4 auf der Form. Die Namen und Zeilenummern wurden erst sichtbar nachdem ich Lazarus (1.2.4 32bit) mit -glh komplett (Aufräumen) neu kompiliert habe.

Edit: Thema heaptrc entfernt da evtl. ein Bug in meiner Umgebung verantwortlich sein könnte
Diese Blöcke werden solange im Betriebssystem nicht wieder freigegeben, bis sie tatsächlich nicht wieder benötigt werden.
Darum gehts ja bei meiner Frage, jetzt sagst Du damit das SetLength(a,0) zwar auf 0 setzt aber den Speicher nicht freigibt oder? Und was passiert wenn ich in einer while(true) Setlength(a, 1 oder 10 oder 8 oder 2 usw.) setze? Bleiben da Fragemente im Ram übrig die erst beim Beenden oder Verlassen der Routine freigegeben werden? Wenn ich jetzt aber ohne zu beenden 10 Tage lang in der while bleibe steigt dann durch ständiges Verkleinern und Vergrössern der Rambedarf an oder wird immer nur soviel verwendet wie aktuell benötigt wird?
Erst bei einem z.B. SetLength(a, 1000) wird ein neuer Speicher angefordert, da der benötigte Speicherplatz nicht mehr in dem bisherigen zur Verfügung gestellten passt.
Und was genau passiert mit dem "bisherigen" wenn ich das jetzt in einer Schleife immer wieder ändere? (mal grösser mal kleiner)

Socke sagt ja : "Da der Referenzzähler auf 0 sinkt, werden die Nutzdaten und die Verwatlungsdaten freigegeben."
Sobald eine Array-Variable ihre Gültigkeit verliert, tritt sinngemäß der gleiche Ablauf wie bei der Zuweisung a := nil ein.
Genau, was ist aber wenn das Array in eine Schleife immer wieder vergrössert und verkleinert wird somit a := nil in dieser Zeit nie zutrifft? Wenn ich dich (socke) jetzt richtig verstanden habe wird freigegeben, da keine Referenz mehr da ist.
Solange du SetLength() verwendest, wird der gesamte Speicher des Arrays automatisch verwaltet (gilt natürlich nicht für die Daten, die in dem Array gespeicher werden!).

Mach ich ja eben NICHT, deswegen schrieb ich ja ich hab mich gewundert das das überhaupt so geht:

Code: Alles auswählen

while(w1) do begin
 
  tmp := nil; // (erstes) vorher SetLength(tmp,0)
 
  while(w2) do begin 
   [.......] //  tmp[]  wird resized und neu befüllt
  end;
 
  // if(Length(globA[_pos]) <> Length(tmp)) then globA[_pos] := nil; // oder immer nil setzen?
 
  globA[_pos] := tmp;
 
 end;
Wenn ich das globA[_pos] := nil; da noch wie angeraten rausnehme, muss ich sicher sein das da kein Datenmüll übrigbleibt. Klar beim Beenden oder verlassen der Routine wird der Speicher freigegeben, was passiert aber wenn w1 tagelang läuft und sich Lenght(tmp) dauernd ändert?
Zuletzt geändert von laz847 am Do 2. Apr 2015, 23:54, insgesamt 3-mal geändert.

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2805
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: dyn array kopieren mit a:=b und b dann nil setzen?

Beitrag von m.fuchs »

laz847 hat geschrieben:Ich sag nur was da steht, win32wscontrols.pp, win32wsspin.pp usw. sind ja nicht von mir und wenn heaptrc da ein Leak anzeigt, wenn ich meine Software nur starte und ohne eine Funktion aufzurufen sofort wieder beende, liegt das nahe oder ?
Es liegt durchaus nahe, ist aber meistens falsch. Erzeuge mal ein neues Projekt mit nur einem leeren Formula und packe folgendes in das OnCreate des Forms:

Code: Alles auswählen

procedure TForm1.FormCreate(Sender: TObject);
var
  o: TObject;
begin
  o := TObject.Create;
end;
Dann das Ganze mal mit HeapTrc starten. DU bekommst jede Menge Meldungen die dich auf Probleme mit dem LCL-Framework schließen lassen.
Kommentier dann die Zeile in der FormCreate-Methode aus und starte noch einmal. Nun sind die Meldungen weg.

Das liegt daran, dass die der Stacktrace angezeigt wird, wo auch die zurückliegenden Aufrufe vorhanden sind, die zu dem Problem geführt haben. Im obigen Beispiel ist das LCL-Framework insofern daran beteiligt, dass es die FormCreate-Methode beim Erzeugen des Formulars aufgerufen hat.
Der Schuldige ist trotzdem der Entwickler, weil er seinen eigenen Müll nicht weggeräumt hat.

Ich würde einen guten Whiskey verwetten, dass es bei deinem Problem auch so ist.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

laz847
Beiträge: 114
Registriert: Mi 18. Jun 2014, 16:39

Re: dyn array kopieren mit a:=b und b dann nil setzen?

Beitrag von laz847 »

Es liegt durchaus nahe, ist aber meistens falsch. Erzeuge mal ein neues Projekt mit nur einem leeren Formula und packe folgendes in das OnCreate des Forms:
Habe ich doch (so ähnlich) :oops: ??
habe jetzt mal die 1.2.6 32bit installiert, ein leeres Projekt erstellt und einen Spinedit draufgepackt. Dann Lazarus mit -gh -gl -glh kompiliert, im Projekt heaptr in die uses und SetHeapTraceOutput('heap.trc'); ins Create, in den Projekteinstellungen -gl aktiviert.
Die 2 Ausgaben oben (1x Text, 1 x Bild), stammen aus einem leeren Testprogramm das nur einen Spinedit enthält. Kurios ist das 2 unterschiedliche Ausgaben generiert werden, je nachdem ob ich heaptrc in den Projekteinstellungen aktiviere oder es in die uses (als Erstes) schreibe.
Der Schuldige ist trotzdem der Entwickler, weil er seinen eigenen Müll nicht weggeräumt hat.
Logisch, wenn ich aber auf meiner Form nur 1 Spinedit ohne weiteren Code habe?

HEAPTRC >>> Ich habe dazu jetzt aber mal (weils zu weit von der eigentlichen Frage entfernt ist) eine seperaten Thread eröffnet : http://www.lazarusforum.de/viewtopic.php?f=10&t=8646

PS: Berlin ist nicht so weit weg - aber ich trink seit Jahren keinen Whiskey mehr lach :D
Zuletzt geändert von laz847 am Mo 30. Mär 2015, 17:19, insgesamt 1-mal geändert.

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2805
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: dyn array kopieren mit a:=b und b dann nil setzen?

Beitrag von m.fuchs »

Es könnte natürlich auch deinem Setup mit Wine liegen. Ich habe das hier mal nachgebaut (leeres Form + SpinEdit und keinerlei Code), da läuft es ohne Memoryleak. Sieht also nicht nach einem Lazaruseigenem Problem aus.
Disclaimer: Allerdings habe hier die 1.4RC2 am Laufen. Es könnte natürlich sein, dass der Bug (wenn es ihn gab) dort behoben ist.
Kannst du dir diese Version mal ziehen und testweise das Programm damit starten?
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Michl
Beiträge: 2511
Registriert: Di 19. Jun 2012, 12:54

Re: dyn array kopieren mit a:=b und b dann nil setzen?

Beitrag von Michl »

laz847 hat geschrieben:
Diese Blöcke werden solange im Betriebssystem nicht wieder freigegeben, bis sie tatsächlich nicht wieder benötigt werden.
Darum gehts ja bei meiner Frage, jetzt sagst Du damit das SetLength(a,0) zwar auf 0 setzt aber den Speicher nicht freigibt oder? Und was passiert wenn ich in einer while(true) Setlength(a, 1 oder 10 oder 8 oder 2 usw.) setze? Bleiben da Fragemente im Ram übrig die erst beim Beenden oder Verlassen der Routine freigegeben werden? Wenn ich jetzt aber ohne zu beenden 10 Tage lang in der while bleibe steigt dann durch ständiges Verkleinern und Vergrössern der Rambedarf an oder wird immer nur soviel verwendet wie aktuell benötigt wird?
Darüber zerbrich dir mal nicht den Kopf, das macht das Betriebssystem intern und kannst du, wenn überhaupt, nur schwer von Lazarus/FPC aus steuern. Wenn du es schaffst keine Speicherleichen zu erzeugen, wird der RAM-Verbrauch dem entsprechen, was du anforderst. Ansonsten hat Socke es ja eindeutig erklärt, dass von Lazarus/FPC-Seite aus mit einem SetLength(a, 0) kein Speicher mehr für die Daten reserviert ist.

PS: Ich habe hier auch einen Rechner als "Server" laufen, auf dem ein mit Lazarus entwickeltes Programm, welches auch sehr individuell Speicher benötigt, manchmal mehrere Wochen ohne Probleme läuft (eigentlich ununterbrochen, wenn da nicht immer irgendwelche Updates von mir wären :mrgreen: )

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection;  

laz847
Beiträge: 114
Registriert: Mi 18. Jun 2014, 16:39

Re: dyn array kopieren mit a:=b und b dann nil setzen?

Beitrag von laz847 »

@micha
Ok danke das teste ich dann mal ausserhalb von Qubes, nicht das da Xen oder Qubes noch die Finger im Spiel haben.

@Michl
Das Problem ist, das sich die Aussagen wiedersprechen, noch dazu verwende ich aktuell im Code gar kein SetLength() und der Ramverbrauch scheint stabil zu sein.
das macht das Betriebssystem intern und kannst du, wenn überhaupt, nur schwer von Lazarus/FPC aus steuern.
Na eben doch, mit a := nil oder nicht? Genau darum gings ja anfänglich in meiner Frage, gibt SetLength(a ,0) den Speicher SOFORT frei, wie a := nil? Oder wird nur auf 0 gesetzt und der Speicher wird beim Beenden Verlassen des Scopes freigegeben?

Michl sagt :
Es werden bei der Allocation des Speichers ganze Blöcke reserviert. Diese Blöcke werden solange im Betriebssystem nicht wieder freigegeben, bis sie tatsächlich nicht wieder benötigt werden. // Hier sagst Du das der Speicher reserviert bleibt - obwohl mit SetLength auf 0 gesetzt?
Ich konnte mal in einem Programm eine erhebliche Geschwindigkeitssteigerung erzielen, indem ich zuerst den vermutlich maximal genutzten Speicher reserviert hatte, diesen dann sofort wieder auf Null gesetzt hatte und dann erst die Routine, die den Speicher dynamisch angefort hatte, gestartet hatte. // Hier sagst Du das der Speicher reserviert bleibt - obwohl mit SetLength auf 0 gesetzt?
Ansonsten hat Socke es ja eindeutig erklärt, dass von Lazarus/FPC-Seite aus mit einem SetLength(a, 0) kein Speicher mehr für die Daten reserviert ist. // Und hier sagst Du er wird freigegeben, wie denn nun?
Deswegen meine Frage : Ob es nicht sinnvoller wäre, nil zu setzen, wenn man in einer Schleife immer wieder vergrössert und verkleinert und tagelang die While-Schleife / den Scope dieser Funktion nicht verläßt?

Ich glaube ein SetLength(globA[_pos],0) reicht hier nicht.

Und irgendwie scheint meine Vermutung richtig gewesen zu sein, genau hier könnte doch meine AccessViolation entstanden sein! http://forum.lazarus.freepascal.org/ind ... ?#msg95642

What you experience is the fact that setting the length to 0 does not free the memory immediately and can be used again if its required although it seems safe enough and harmless immediately after the setlength call in heavelly used environment that will return unpredictable results and if the memory used is big enough to release back to the system you will end with dificult to find access violations.

Code: Alles auswählen

while not(Terminated) do begin 
 
  tmp := nil;
 
  while(w2) do begin 
   [.......] //  tmp[]  wird resized und neu befüllt
  end;
 
  if(Length(globA[_pos]) <> Length(tmp)) then globA[_pos] := nil;// war richtig, solange sich die grösse nicht ändert ok, wenn sie sich aber ändert geben wir sofort frei
 
  globA[_pos] := tmp;
 
end;
Und das passt dann auch zu nem Logeintrag in der VM, no_page_in_free_list oder so ähnlich :roll:... Es scheint doch so zu sein wie ich befürchtet habe, SetLength(a,0) setzt auf 0, der Speicher wird jedoch anscheinend nicht sofort (wie bei a:= nil) freigegeben. Macht man das 300 x kein Problem, aber in "heavily used environment" kann das ohne ein nil irgendwann AccessViolations erzeugen. Diese Aussage beschreibt genau meine AccessViolation, nicht reproduzierbar, ich habe an keiner anderen verdächtigen Stelle einen wirklichen Fehler gefunden + passender Logeintrag.
Zuletzt geändert von laz847 am Fr 3. Apr 2015, 00:22, insgesamt 15-mal geändert.

laz847
Beiträge: 114
Registriert: Mi 18. Jun 2014, 16:39

Re: dyn array kopieren mit a:=b und b dann nil setzen? [gelö

Beitrag von laz847 »

Da es um eine Nachfrage zu einem Beispiel in diesem Thema geht, stell ich die mal gleich hier. Die eigentliche Frage ist aber geklärt.

Lieber Socke biste noch da :D ???

Dein Beispiel:

Code: Alles auswählen

program Project1;
type
  tarr = array of byte;
var
  a1: tarr; 
 
procedure dosth;
var
 a2: tarr;
begin
  a2 := a1;  // a1 und a2 zeigen auf den selben Speicher,  Length(a2) = 10
end; 
 
begin
  SetLength(a1,10);  // Length(a1) = 10
  dosth;
end.
Wenn ich a1[0] := 6 setze ist a2[0] = 6, logisch a1, a2 zeigen auf den selben Speicher

Code: Alles auswählen

TInputArray = Array[0..9] of Array[0..9] of Array of TInputStruct;
 
IP[rul][act][ipt] := Input[rul][act][ipt]; // hier will ich daten kopieren, dachte mist der kopiert ja die referenz (beide Arrays TInputArray)
Jetzt dachte ich juhu einen Fehler gefunden aber nee, ich hatte array of record und nicht array of int/byte usw...

Ändert man nun IP[0][0][0].iid := 12; ändert sich Input[0][0][0].iid nicht, jedenfalls war das so bei meinem Test gestern.

Mir ist nicht so richtig klar warum das bei Array of Byte so ist, bei Array of Record wieder nicht?

Die letzte Ebene ist ja dynamisch, liegts daran weil die ersten 2 statisch sind oder am array of record?

Das sind ganz schön gefährliche Fallen :?: :roll:

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: dyn array kopieren mit a:=b und b dann nil setzen?

Beitrag von mse »

laz847 hat geschrieben: Genau darum gings ja anfänglich in meiner Frage, gibt SetLength(a ,0) den Speicher SOFORT frei, wie a := nil? Oder wird nur auf 0 gesetzt und der Speicher wird beim Beenden Verlassen des Scopes freigegeben?
"SetLength(a ,0)" und "a := nil" haben die gleiche Wirkung, "a:= nil" ist schneller.

Code: Alles auswählen

 
var
 ar1: array of byte;
begin
 setlength(ar1,10);
 ar1:= nil;
 setlength(ar1,10);
 setlength(ar1,0);
end.
 

Code: Alles auswählen

 
# [5] setlength(ar1,10);
	movl	$10,-4(%ebp)
	leal	-4(%ebp),%eax
	pushl	%eax
	movl	$INIT_P$TEST1_DEF1,%edx
	movl	$U_P$TEST1_AR1,%eax
	movl	$1,%ecx
	call	fpc_dynarray_setlength
# [6] ar1:= nil;
	movl	$INIT_P$TEST1_DEF1,%edx
	movl	$U_P$TEST1_AR1,%eax
	call	fpc_dynarray_clear
# [7] setlength(ar1,10);
	movl	$10,-4(%ebp)
	leal	-4(%ebp),%eax
	pushl	%eax
	movl	$INIT_P$TEST1_DEF1,%edx
	movl	$U_P$TEST1_AR1,%eax
	movl	$1,%ecx
	call	fpc_dynarray_setlength
# [8] setlength(ar1,0);
	movl	$0,-4(%ebp)
	leal	-4(%ebp),%eax
	pushl	%eax
	movl	$INIT_P$TEST1_DEF1,%edx
	movl	$U_P$TEST1_AR1,%eax
	movl	$1,%ecx
	call	fpc_dynarray_setlength
 

Code: Alles auswählen

 
{ releases and finalizes the data of a dyn. array and sets p to nil }
procedure fpc_dynarray_clear_internal(p : pointer;ti : pointer);
  var
    eletype : pdynarraytypeinfo;
  begin
     if p=nil then
       exit;
 
     { skip kind and name }
     inc(pointer(ti),ord(pdynarraytypeinfo(ti)^.namelen)+2);
 
     ti:=aligntoptr(ti);
 
     eletype:=pdynarraytypeinfo(pointer(pdynarraytypeinfo(pointer(ti)+sizeof(sizeint)))^);
 
     { finalize all data }
     int_finalizearray(p+sizeof(tdynarray),eletype,pdynarray(p)^.high+1);
 
     { release the data }
     freemem(p);
  end;
  
procedure fpc_dynarray_clear(var p : pointer;ti : pointer); [Public,Alias:'FPC_DYNARRAY_CLEAR']; compilerproc;
  var
     realp : pdynarray;
  begin
    if (P=Nil) then
      exit;
    realp:=pdynarray(p-sizeof(tdynarray));
    if declocked(realp^.refcount) then
      fpc_dynarray_clear_internal(p-sizeof(tdynarray),ti);   <<<<<----
    p:=nil;
  end;
 
procedure fpc_dynarray_setlength(var p : pointer;pti : pointer;
  dimcount : dword;dims : pdynarrayindex);[Public,Alias:'FPC_DYNARR_SETLENGTH']; compilerproc;
 
  var
     i : tdynarrayindex;
     movelen,
     size : sizeint;
     { contains the "fixed" pointers where the refcount }
     { and high are at positive offsets                 }
     realp,newp : pdynarray;
     ti : pdynarraytypeinfo;
     updatep: boolean;
     elesize : sizeint;
     eletype : pdynarraytypeinfo;
 
  begin
     ti:=pdynarraytypeinfo(pti);
 
     { skip kind and name }
     inc(pointer(ti),ord(pdynarraytypeinfo(ti)^.namelen)+2);
 
     ti:=aligntoptr(ti);
 
     elesize:=psizeint(ti)^;
     eletype:=pdynarraytypeinfo(pointer(pdynarraytypeinfo(pointer(ti)+sizeof(sizeint)))^);
     { determine new memory size }
     { dims[dimcount-1] because the dimensions are in reverse order! (JM) }
     size:=elesize*dims[dimcount-1]+sizeof(tdynarray);
     updatep := false;
 
     { not assigned yet? }
     if not(assigned(p)) then
       begin
          if dims[dimcount-1]<0 then
            HandleErrorFrame(201,get_frame);
          { do we have to allocate memory? }
          if dims[dimcount-1] = 0 then
            exit;
          getmem(newp,size);
          fillchar(newp^,size,0);
          updatep := true;
       end
     else
       begin
          realp:=pdynarray(p-sizeof(tdynarray));
          newp := realp;
 
          { if the new dimension is 0, we've to release all data }
          if dims[dimcount-1]<=0 then
            begin
               if dims[dimcount-1]<0 then
                 HandleErrorFrame(201,get_frame);
               if declocked(realp^.refcount) then
                 fpc_dynarray_clear_internal(realp,pdynarraytypeinfo(pti)); <<<<-----
               p:=nil;
               exit;
            end;
[...]
 

laz847
Beiträge: 114
Registriert: Mi 18. Jun 2014, 16:39

Re: dyn array kopieren mit a:=b und b dann nil setzen?

Beitrag von laz847 »

Cool danke mse, da erübrigt sich dann auch jedes weitere Rätselraten.

Also kann man sagen SetLength(a,0) setzt a := nil, gibt aber den Speicher nur frei, wenn keine weitere Referenz auf den Inhalt besteht oder?

Aber wie ist das wenn noch irgendwo eine Referenz hängt? Vermutlich ist "if declocked(realp^.refcount)" die Referenzzählung oder?

Nehmen wir an ich habe Copy vergessen und durch a:= b die Referenz kopiert anstatt die Daten? Setz ich dann SetLength(b,0) hat a die Referenz auf die Daten, b wird nur genilt.

Dann wird fpc_dynarray_clear_internal >> freemem(p); nicht ausgeführt sondern nur p:=nil was exakt dieses Verhalten auslösen könnte oder?

Mach ich das inner Schleife und b ändert sich, bleibt doch da immer der "bisherige Inhalt" erhalten und wird nie freigegeben oder?

Code: Alles auswählen

while not(Terminated) do begin 
 
  tmp := nil;
 
  while(w2) do begin 
   [.......] //  tmp[]  wird resized und neu befüllt
  end;
 
  if(Length(globA[_pos]) <> Length(tmp)) then globA[_pos] := nil; // hier sollte dann beides gehen, SetLength(globA[_pos],0) oder nil, da nur eine referenz auf globA[_pos] besteht, tmp war nil und enthält neue daten 
 
  // nimmt man aber diese zeile raus und nutzt weder setlength(a,0) noch nil bleibt doch da was im speicher liegen oder?
 
  globA[_pos] := tmp; // ist es sicher das bei der neuen zuweisung ohne vorher setlength(globA[_pos],0) oder nil zu setzen der speicher des bisherigen inhalts SOFORT freigegeben wird, auch wenn wir den scope nicht verlassen sondern das in eine while tun?
 
end;
Ich kann kein Assembler aber ich sehe die call's auf die fpc Routinen. Wie mache ich diese Ausgaben sichtbar und wie / wo finde ich die entsprechenden Routinen?

Michl
Beiträge: 2511
Registriert: Di 19. Jun 2012, 12:54

Re: dyn array kopieren mit a:=b und b dann nil setzen?

Beitrag von Michl »

Ich hatte eben mal einen Test gemacht und gesehen, dass ich da was im Gedächtnis verhauen habe.

Folgender Code:

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  FreeRAM: DWord;
  a: Array of Integer;
begin
  FreeRAM:=GetFreeRAM;
  Writeln('Vor erster Speicheranforderung: ', GetFreeRAM);
  SetLength(a, 100000);
  Writeln('Nach erster Speicheranforderung: ', GetFreeRAM);
//  SetLength(a, 0);
  a:=Nil;
  Writeln('Nach Speicherfreigabe: ', GetFreeRAM);
  SetLength(a, 100000);
  Writeln('Nach zweiter Speicheranforderung: ', GetFreeRAM);
//  SetLength(a, 0);
  a:=Nil;
  Writeln('Nach Speicherfreigabe: ', GetFreeRAM);
  SetLength(a, 50000);
  Writeln('Nach dritter Speicheranforderung: ', GetFreeRAM);
//  SetLength(a, 0);
  a:=Nil;
  Writeln('Nach Speicherfreigabe: ', GetFreeRAM);
  SetLength(a, 200000);
  Writeln('Nach vierter Speicheranforderung: ', GetFreeRAM);
//  SetLength(a, 0);
  a:=Nil;
  Writeln('Nach Speicherfreigabe: ', GetFreeRAM);
  Writeln('Fehlspeicher: ',FreeRAM - GetFreeRAM);
end; 
ergibt folgende Ausgabe

Code: Alles auswählen

Vor erster Speicheranforderung: 3079249920
Nach erster Speicheranforderung: 3079102464
Nach Speicherfreigabe: 3079102464
Nach zweiter Speicheranforderung: 3079102464
Nach Speicherfreigabe: 3079102464
Nach dritter Speicheranforderung: 3079102464
Nach Speicherfreigabe: 3079102464
Nach vierter Speicheranforderung: 3078545408
Nach Speicherfreigabe: 3078545408
Fehlspeicher: 704512
Der für das Array reservierte Speicher wird unter Windows erst wieder freigegeben, wenn er nicht mehr benötigt wird (z.B. Programmende).


Allerdings hatte ich hier
Michl hat geschrieben:Ich konnte mal in einem Programm eine erhebliche Geschwindigkeitssteigerung erzielen, indem ich zuerst den vermutlich maximal genutzten Speicher reserviert hatte, diesen dann sofort wieder auf Null gesetzt hatte und dann erst die Routine, die den Speicher dynamisch angefort hatte, gestartet hatte.
etwas falsch in Erinnerung.

Ich hatte sowas gemacht gehabt (vereinfacht dargestellt - der Versuch mit Button1 ist schneller als der mit Button2):

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
  Cnt: Int64;
begin
  Cnt:=GetTickCount64;
  SetLength(a, 0);
  FLength:=0;
  for i:=0 to 10000000 do Add1(i);
  Caption:=IntToStr(GetTickCount64 - Cnt);
end;
 
procedure TForm1.Button2Click(Sender: TObject);
var
  i: Integer;
  Cnt: Int64;
begin
  Cnt:=GetTickCount64;
  SetLength(a, 0);
  for i:=0 to 10000000 do Add2(i);
  Caption:=IntToStr(GetTickCount64 - Cnt);
end;
 
procedure TForm1.Add1(i: Integer);
begin
  inc(FLength);
  if FLength > Length(a) then SetLength(a, Length(a) + 100000);
  a[FLength - 1]:=i;
end;
 
procedure TForm1.Add2(i: Integer);
begin
  SetLength(a, Length(a) + 1);
  a[High(a)]:=i;
end; 
Logisch, die Speicherallokierung braucht dabei ja nicht so oft durchgeführt werden. Also nix mit SetLength(a, 0), naja, lag wohl etwas zu weit zurück :oops:

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection;  

Antworten