Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Out of Resources


HenryHux - Fr 08.10.10 12:03
Titel: Out of Resources
Hi,

ich habe immoment ein Programm, dass Bitmaps aufeinander vergleicht.
Läuft alles gut, aber nach 10Minuten hat der Prozess schon über 200Mb Ram gefressen und irgendwann bricht es dann ab aufgrund von Resourcen-Mangel.
Wodran könnte das liegen?

Lg

Henry


platzwart - Fr 08.10.10 12:05

Naja, das liegt wohl daran, dass du ein Bild in den RAM lädst, es vergleichst und dann das nächste laädst, ohne das vorherige aus dem RAM zu löschen. So häufen sich die Bilder an und irgendwann ist der RAM voll...


HenryHux - Fr 08.10.10 12:09

Ok, und wie werden die Bilder aus dem RAM gelöscht?
Mit imagex.free?


platzwart - Fr 08.10.10 12:10

Das wäre schon mal ein guter Ansatz ;) Wenn du ein wenig deines Quellcodes zeigen würdest, könnte man da genauere Angaben machen.


HenryHux - Fr 08.10.10 12:18

Ok, also die Bitmaps werden zuerst geshootet, dann geschnitten und anschließend verglichen.
Dazu folgende Funktionen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
Procedure Screenshot(image:Timage;Bounds:TRect;Faktor:Double);           
var DeskCanvas: TCanvas;
begin
  DeskCanvas := TCanvas.Create;
  DeskCanvas.Handle := GetDC(0);
  image.Width := Round((Bounds.Right - Bounds.left) * Faktor);
  image.Height := Round((Bounds.Right - Bounds.Left) * Faktor);
  Image.Canvas.CopyRect(Image.ClientRect, DeskCanvas, Bounds);
  DeskCanvas.Free;
end;



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
procedure Schneiden(Links, Rechts, Oben, Unten, z1, z2 : Integer);                                              
var SourceBitmap : TBitmap;
    TargetBitmap : TBitmap;
    Cut : TRect;
begin
  SourceBitmap:=Tbitmap.Create;
  TargetBitmap:=Tbitmap.Create;
  SourceBitmap.LoadFromFile(Path+'image'+inttostr(z1)+'.bmp');
  Cut.Left := Links;
  Cut.Top := Oben;
  Cut.Right := Rechts;
  Cut.Bottom := Unten;

  TargetBitmap.Width := Cut.Right - Cut.Left;
  TargetBitmap.Height := Cut.Bottom - Cut.Top;
  Sleep(50);
  Application.ProcessMessages;
  BitBlt(TargetBitmap.Canvas.Handle, 00, Cut.Right, Cut.Bottom, SourceBitmap.Canvas.Handle, Cut.Left, Cut.Top, SRCCOPY);
  SourceBitmap.Canvas.Refresh;
  TargetBitmap.SaveToFile(Path+'image'+inttostr(z2)+'.bmp');
end;



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
function CompareImages(Bitmap3, Bitmap2: TBitmap): LongWord;
var
  xy: integer;
  P1, P2: PInteger;
begin
  Result:=0;
  Bitmap3.PixelFormat:=pf32bit;
  Bitmap2.PixelFormat:=pf32bit;
  P1:=Bitmap3.ScanLine[Bitmap3.Height-1];
  P2:=Bitmap2.ScanLine[Bitmap2.Height-1];
  for xy:=1 to Bitmap3.Height*Bitmap3.Width do begin
    Inc(Result, Byte(P1^<>P2^));
    Inc(P1);
    Inc(P2);
  end;
end;


Anschließend aufgerufen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
  Screenshot(image1,Rect(Breite,Hoehe,Breite + Breite2,Hoehe + Hoehe2),Faktor);
  Image1.Picture.SaveToFile (Path+'image'+inttostr(z1)+'.bmp');
  schneiden(30,43,64,100,1,2);
  EinBild:= Tbitmap.create;
  NochEinBild:= Tbitmap.create;
  image2.picture.loadfromfile(Path+'image'+inttostr(z2)+'.bmp');
  EinBild.LoadFromFile(Path+'image2.bmp');
  for i := 1 to 20 do
  begin
    NocheinBild.LoadFromFile(Path+'Ordner1\'+'K'+inttostr(i)+'.bmp');
    Diff := CompareImages(EinBild, NocheinBild);


und am Ende der Prozedur:

Delphi-Quelltext
1:
2:
EinBild.Free;
NochEinBild.Free;


Ich dachte eigentlich, dass der Speicher durch die letzten Zeilen wieder freigegeben würde, aber anscheinend doch nicht.

Lg


Delete - Fr 08.10.10 12:21

Und wo werden die die Bitmaps in der Prozedur Schneiden wieder freigegeben?


platzwart - Fr 08.10.10 12:24


Delphi-Quelltext
1:
2:
SourceBitmap.Free;
TargetBitmap.Free;


... und alles sollte laufen.


Delete - Fr 08.10.10 12:32

Und ein Ressourcenschutzblock könnte auch nicht schaden. Und was soll das:

Delphi-Quelltext
1:
2:
Sleep(50);
Application.ProcessMessages;


HenryHux - Fr 08.10.10 12:33

Ok, danke hatte ich vergessen.
Steigt jetzt zwar wesentlich langsamer, aber trotzdem steigt er noch und das Programm denke hat nach 20min auch an die 30mb erreicht.
Ich lasse ihn atm nur in dieser Schleife laufen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
  repeat
    bitmap1:=0;
    bitmap2:=0;
    bitmap3:=0;
    bitmap4:=0;
    getbitmap1.Click;
    getbitmap2.click;
    getbitmap3.Click;
    getbitmap4.Click;
    wait(500);
  until (bitmap1>0and (bitmap2>0and (bitmap3>0and (bitmap4>0);


Wie gesagt, diese getbitmaps werden nur durch den oben genannten Aufruf aufgerufen.
Muss ich den RAM Anspruch in Kauf nehmen, oder habe ich noch etwas übersehen?
Lg

Edit:
user profile iconLuckie hat folgendes geschrieben Zum zitierten Posting springen:
Und ein Ressourcenschutzblock könnte auch nicht schaden. Und was soll das:

Delphi-Quelltext
1:
2:
Sleep(50);
Application.ProcessMessages;

Er soll die Bilder auf Veränderungen prüfen und da dachte ich mir, es wäre besser ihn nicht durchgängig laufen zu haben, sondern den Prozess ein bisschen langsamer laufen zu lassen.

Henry


Flamefire - Fr 08.10.10 12:43

Lade dir mal FastMM4 runter und binde das als 1. Unit im Projekt ein. (Also im Projekt auf Quelltext anzeigen klicken. Dass muss wirklich da rein, wo Application.CreateForm usw steht)
Dann rufe deine Prozeduren einmal auf und beende danach immer das Programm. FastMM sagt dir dann, wo Memleaks aufgetreten sind. Mach das mit jeder Prozedur, bis du den Fehler gefunden hast.


BenBE - Fr 08.10.10 12:47

Für regelmäßig auszuführende Tätigkeiten gibt's Timer

Weiter gehören interne Daten (Zwischengespeicherte Bilder) nicht in ein TImage (generell nicht in visuelle Komponenten der VCL).

Ressourcen-Schutzblöcke könnten deinem Source wirklich nicht schaden.

Lass mal FastMM4 mit aktivierter Memory Leak Detection laufen.

Was sollen deine Canvas.Refresh-Aufrufe?


HenryHux - Sa 09.10.10 11:10

Ok, Danke habe nen paar Schen jetzt geändert.
Aber leider komme ich mit FastMM4 nicht ganz klar.
Bei jedem kompilieren bekomme ich diese Meldung:


---------------------------
Read1.exe: Cannot install FastMM4 - Memory has already been allocated
---------------------------
FastMM4 cannot install since memory has already been allocated through the default memory manager.

FastMM4.pas MUST be the first unit in your project's .dpr file, otherwise memory may be allocated

through the default memory manager before FastMM4 gains control.



If you are using an exception trapper like MadExcept (or any tool that modifies the unit initialization order),

go into its configuration page and ensure that the FastMM4.pas unit is initialized before any other unit.
---------------------------
OK
---------------------------

Wenn es daran liegt, dass ich FastMM4 nicht als erste Unit habe, wie könnte ich das denn dann machen?

Lg


BenBE - Sa 09.10.10 11:27

user profile iconHenryHux hat folgendes geschrieben Zum zitierten Posting springen:
Ok, Danke habe nen paar Schen jetzt geändert.
Aber leider komme ich mit FastMM4 nicht ganz klar.
Bei jedem kompilieren bekomme ich diese Meldung:



Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
---------------------------
Read1.exe: Cannot install FastMM4 - Memory has already been allocated
---------------------------
FastMM4 cannot install since memory has already been allocated through the default memory manager.

FastMM4.pas MUST be the first unit in your project's .dpr file, otherwise memory may be allocated

through the default memory manager before FastMM4 gains control. 



If you are using an exception trapper like MadExcept (or any tool that modifies the unit initialization order),

go into its configuration page and ensure that the FastMM4.pas unit is initialized before any other unit.
---------------------------
OK   
---------------------------

Bitte Code-Blöcke für sowas ;-)

user profile iconHenryHux hat folgendes geschrieben Zum zitierten Posting springen:
Wenn es daran liegt, dass ich FastMM4 nicht als erste Unit habe, wie könnte ich das denn dann machen?

Lg

Ich wiederhole mal:
user profile iconFlamefire hat folgendes geschrieben Zum zitierten Posting springen:
Lade dir mal FastMM4 runter und binde das als 1. Unit im Projekt ein. (Also im Projekt auf Quelltext anzeigen klicken. Dass muss wirklich da rein, wo Application.CreateForm usw steht)


Oder noch mal zum Mitmeißeln:
Oben im Menü auf Projekt --> Quelltext anzeigen
Dort dürfte ein Fenster mit "project XYZ;" in der ersten Zeile aufgehen.
Dort als erste Unit in der Uses FastMM4 eintragen.

Bei aktuellen Delphi-Versionen kannst Du den Speichermanager auch über Projekt-->Optionen einstellen.


HenryHux - Sa 09.10.10 11:48

Ok, danke habe ihn so zum Laufen bekommen.
Habe die Schleife einmal laufen lassen und Progg beendet.
Es kam:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
---------------------------
Read1.exe: Memory Leak Detected
---------------------------
This application has leaked memory. The small block leaks are (excluding expected leaks registered by pointer):



61 - 68 bytes: TBitmap x 2

117 - 124 bytes: TBitmapImage x 2



Note: To obtain a log file containing detail on memory leaks, enable the "FullDebugMode" and "LogMemoryLeakDetailToFile" conditional defines. To disable this memory leak check, undefine "EnableMemoryLeakReporting".


---------------------------
OK   
---------------------------


Ich wusste nicht genau, wie ich den FullDebugMode aktivieren kann, habs einfach mal hiermit versucht, hatte sich aber auch nicht viel verändert.

Delphi-Quelltext
1:
2:
  FullDebugModeScanMemoryPoolBeforeEveryOperation: Boolean = true;
  FullDebugModeRegisterAllAllocsAsExpectedMemoryLeak: Boolean = true;


Kann man in den Informationen schon iwas erkennen?

Lg


BenBE - Sa 09.10.10 11:53

Im Verzeichnis mit der EXE dürfte sich nun eine Logfile befinden. Schau dort mal rein. Da sollten Zeilennummern stehen, wo die Bitmaps herkommen.

Zum Aktivieren des Full Debug Modes: Schau mal in die FastMM4.pas rein. Da dürfte es eine ausführliche Beschreibung geben.

Ach ja: Hast Du mal aktualisierte Sources für uns? Bitte die Routinen soweit es geht vollständig, die aufgerufen werden.


HenryHux - Sa 09.10.10 11:59

So, endlich klappt es.. Danke!
Tja falsche Bitmaps in ein paar Prozeduren freigegeben, d.h ein paar wurden nicht wieder freigegeben.
Kommt von copy pasting :D
Aber zu den Resourcen-Schutzblöcken die ihr angesprochen habt, was genau bringen die?
Hab mir das mit try-finally und try-exept mal angeguckt.
Was bringt es mir etwas einzubauen was sowieso ausgeführt wird?

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:
begin
  try
    SourceBitmap:=Tbitmap.Create;
    TargetBitmap:=Tbitmap.Create;
    SourceBitmap.LoadFromFile(Path+'image'+inttostr(z1)+'.bmp');
    Cut.Left := Links;
    Cut.Top := Oben;
    Cut.Right := Rechts;
    Cut.Bottom := Unten;

    TargetBitmap.Width := Cut.Right - Cut.Left;
    TargetBitmap.Height := Cut.Bottom - Cut.Top;
    Sleep(50);
    Application.ProcessMessages;
    BitBlt(TargetBitmap.Canvas.Handle, 00, Cut.Right, Cut.Bottom, SourceBitmap.Canvas.Handle, Cut.Left, Cut.Top, SRCCOPY);
  //  SourceBitmap.Canvas.Refresh;
    TargetBitmap.SaveToFile(Path+'image'+inttostr(z2)+'.bmp');
  finally
    SourceBitmap.Free;
    TargetBitmap.Free;
  end;

end;


Also was macht es für ein Unterschied, das try-finally.

Danke und Lg


jaenicke - Sa 09.10.10 12:16

Wenn zwischen dem Erzeugen und der Freigabe eine Exception auftritt, werden die Objekte sonst nicht freigegeben.

Der finally-Block wird aber immer ausgeführt, egal ob eine Exception aufgetreten ist oder du mit Exit aus der Prozedur springst. Deshalb wird dann immer freigegeben.


BenBE - Sa 09.10.10 12:20

Nehmen wir mal folgendes Beispiel:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
src := TBitmap.Create;
Src.width := IntToStr(Edit1.Text);
Src.Height := IntToStr(Edit2.Text);
//Some more obscure source
Src.Free;
Src := nil;


Bist Du Dir GANZ sicher, dass Src IMMER freigegeben wird? Mach Dir den Spaß und teste das mal mit FastMM4 im Hintergrund.

Zum Vergleich nimm mal folgenden Source:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
src := TBitmap.Create;
try
    Src.width := IntToStr(Edit1.Text);
    Src.Height := IntToStr(Edit2.Text);
    //Some more obscure source
finally
    Src.Free;
    Src := nil;
end;


Den Unterschied siehst Du bei diesen beiden Code-Schnipseln unter Normalbedingungen nicht. Aber geb mal abc in Edit1 ein. Dann dürfte es offensichtlich werden. Finally erzwingt, dass der Code auch im Fehlerfalle ausgeführt wird. Ohne den Schutzblock greift sofort der Exception-Handler und der überspringt halt die Objekt-Freigabe, weil die nicht zur Fehlerbehandlung notwendig ist.


HenryHux - Sa 09.10.10 12:24

Achso, ist ja dann doch praktischer als vermutet :D
Denke bau ich dann mal besser ein, kann ja denke nicht schaden.
Danke =)