![]() |
Clientdataset Speicherfreigabe
Hallo zusammen,
wieso wird beim TClientdataset der Speicher nicht freigegeben wenn man Datensätze löscht? Muss ich da noch irgendetwas auslösen? Beim TFDMemTable wird der Speicher direkt nach dem löschen wieder freigegeben.
Delphi-Quellcode:
unit Unit121;
interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.DB, Datasnap.DBClient, Vcl.StdCtrls, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf, FireDAC.Comp.DataSet, FireDAC.Comp.Client; type TForm121 = class(TForm) gb_clientdataset: TGroupBox; b_clientdataset_add: TButton; b_clientdataset_delete: TButton; l_clientdataset: TLabel; gb_fdmemtable: TGroupBox; l_fdmemtable: TLabel; b_fdmemtable_add: TButton; b_fdmemtable_delete: TButton; procedure b_clientdataset_addClick(Sender: TObject); procedure b_clientdataset_deleteClick(Sender: TObject); procedure b_fdmemtable_addClick(Sender: TObject); procedure b_fdmemtable_deleteClick(Sender: TObject); procedure FormCreate(Sender: TObject); private { Private-Deklarationen } public { Public-Deklarationen } lClientDataSet: TClientDataSet; lFDMemTable: TFDMemTable; procedure Add( xDataset: TDataSet; xLabel: TLabel); procedure Delete( xDataset: TDataSet; xLabel: TLabel); end; var Form121: TForm121; implementation {$R *.dfm} procedure TForm121.Add(xDataset: TDataSet; xLabel: TLabel); begin for var i := 1 to 9999 do begin xDataset.Append; xDataset.FieldByName('TEST1').AsString := Random( MaxInt).ToString; xDataset.FieldByName('TEST2').AsString := Random( MaxInt).ToString; xDataset.FieldByName('TEST3').AsString := Random( MaxInt).ToString; xDataset.Post; end; xLabel.Caption := Format( 'Recordcount: %d', [ xDataset.RecordCount]); end; procedure TForm121.b_clientdataset_addClick(Sender: TObject); begin Add( lClientDataSet, l_clientdataset); end; procedure TForm121.b_clientdataset_deleteClick(Sender: TObject); begin Delete( lClientDataSet, l_clientdataset); end; procedure TForm121.b_fdmemtable_addClick(Sender: TObject); begin Add( lFDMemTable, l_fdmemtable); end; procedure TForm121.b_fdmemtable_deleteClick(Sender: TObject); begin Delete( lFDMemTable, l_fdmemtable); end; procedure TForm121.Delete(xDataset: TDataSet; xLabel: TLabel); begin while ( xDataset.RecordCount > 0) do begin xDataset.Delete; end; xLabel.Caption := Format( 'Recordcount: %d', [ xDataset.RecordCount]); end; procedure TForm121.FormCreate(Sender: TObject); begin Randomize; lClientDataSet := TClientDataSet.Create( Self); lClientDataSet.FieldDefs.Add( 'TEST1', ftString, 500); lClientDataSet.FieldDefs.Add( 'TEST2', ftString, 500); lClientDataSet.FieldDefs.Add( 'TEST3', ftString, 500); lClientDataSet.CreateDataSet; lFDMemTable := TFDMemTable.Create( Self); lFDMemTable.FieldDefs.Add( 'TEST1', ftString, 500); lFDMemTable.FieldDefs.Add( 'TEST2', ftString, 500); lFDMemTable.FieldDefs.Add( 'TEST3', ftString, 500); lFDMemTable.CreateDataSet; end; end. |
AW: Clientdataset Speicherfreigabe
Hast du es mal mit MergeChangeLog probiert?
|
AW: Clientdataset Speicherfreigabe
Zitat:
Wenn ich nur an einer stelle Insert oder Delete MergeChangeLog ausführe wächst die Auslastung bei jedem neuen Insert. LogChanges hilft auch nicht. |
AW: Clientdataset Speicherfreigabe
Welcher Speicher wächst?
Ich tippe mal auf eine Speicherfragmentierung im FastMM. Was sagt denn GetMemoryManagerState? Hat ein billiges ClientDataSet überhaupt ein ChangeLog? |
AW: Clientdataset Speicherfreigabe
Zitat:
Clientdataset
FDMemTable
Was sagt denn GetMemoryManagerState? - Da steht immer das selbe hättest du da ein Beispiel wie ich das richtig anwende bzw. was möchtest du da genau sehen? Beim dxMemTable von DevExpress wird der Speicher auch freigegeben. Wenn ich bei einer TList oder TObjectList Einträge entferne werden die auch direkt freigegeben. Ich hab das Problem nur bei TClientDataSets |
AW: Clientdataset Speicherfreigabe
Liste der Anhänge anzeigen (Anzahl: 1)
Hier nochmal das komplette Testprojekt.
|
AW: Clientdataset Speicherfreigabe
Die scheinen den Speicher direkt bei Windows zu reservieren.
Hmmm, im FastMM steigt nichts an. Aber im Code (Datasnap.DBClient) finde ich nur ein AllocMem, was eigentlich im Delphi-MemoryManager (FastMM) landen sollte. :gruebel: Ich komm nach genügend Durchläufen (Add+Delete) sogar in einen OutOfMemory.
Delphi-Quellcode:
procedure TForm8.FormCreate(Sender: TObject);
var CDS: TClientDataSet; Mem: UInt64; procedure ShowState; var GStatus: TMemoryStatusEx; MMState: TMemoryManagerState; begin //Memo1.Lines.Add(' Records ' + CDS.RecordCount.ToString); GStatus.dwLength := SizeOf(GStatus); GlobalMemoryStatusEx(GStatus); GetMemoryManagerState(MMState); var FastMM: Int64 := 0; for var i := 0 to High(MMState.SmallBlockTypeStates) do Inc(FastMM, MMState.SmallBlockTypeStates[i].UseableBlockSize * MMState.SmallBlockTypeStates[i].AllocatedBlockCount); Inc(FastMM, MMState.TotalAllocatedMediumBlockSize); Inc(FastMM, MMState.TotalAllocatedLargeBlockSize); Memo1.Lines.Add(Format(' Memory %d%% %.2nm %s%.2nm / %.2nm', [ GStatus.dwMemoryLoad, Int64(GStatus.ullTotalVirtual - GStatus.ullAvailVirtual) / 1048576, IfThen(Mem < GStatus.ullAvailVirtual, '', '+'), Int64(Mem - GStatus.ullAvailVirtual) / 1048576, FastMM / 1048576 ])); Mem := GStatus.ullAvailVirtual; end; begin Mem := 0; CDS := TClientDataSet.Create(Self); CDS.FieldDefs.Add('TEST1', ftString, 500); CDS.FieldDefs.Add('TEST2', ftString, 500); CDS.FieldDefs.Add('TEST3', ftString, 500); CDS.CreateDataSet; for var L := 1 to 99 do try Memo1.Lines.Add(L.ToString); //Memo1.Lines.Add('Add'); for var i := 1 to 9999 do begin CDS.Append; CDS.FieldByName('TEST1').AsString := Random(MaxInt).ToString; CDS.FieldByName('TEST2').AsString := Random(MaxInt).ToString; CDS.FieldByName('TEST3').AsString := Random(MaxInt).ToString; CDS.Post; end; ShowState; //Memo1.Lines.Add('Delete'); while CDS.RecordCount > 0 do CDS.Delete; ShowState; except on E: Exception do begin Memo1.Lines.Add(L.ToString + ' : ' + E.Message); ShowState; Break; end; end; end; |
AW: Clientdataset Speicherfreigabe
Vielleicht ist es nicht jedem bewusst, aber die DB-Engine des TClientDataset liegt in der Midas.dll bzw. ihrem eingelinkten binären Zwilling. Damit arbeitet es schon etwas anders als die reine Delphi-Implementierung von FireDAC.
|
AW: Clientdataset Speicherfreigabe
Die ClientDataSets sind ja eigentlich für n-tier Anwendungen gemacht, hängen also an einem Provider, der die das Änderungslog (DELTA) weiterreicht. Ein DELETE löscht also nicht wirklich, sondern hebt alles auf, was notwendig ist, um die Änderung weiterreichen zu können UND um das Löschen auch rückgängig machen zu können. Siehe "CancelUpdates".
Es überrascht mich also nicht, dass Speicher alloziert bleibt. Jedes Feld gibt es im CDS 3x: newValue/Value/oldValue - da kommt im Code unten schon (unrealistischerweise) was zusammen: 99 x 9999 x 3 x 3 x 500 Mit dem Zerstören des CDS wird dann auch der Speicher freigegeben. |
AW: Clientdataset Speicherfreigabe
Einfach mal nach "CreateDataSet" die Property "LogChanges" auf false setzen.
|
AW: Clientdataset Speicherfreigabe
Zitat:
Macht keinen Unterschied. |
AW: Clientdataset Speicherfreigabe
ApplyUpdates + Handler für BeforeApplyUpdates sollte das Delta leeren.
CDS schließen und öffnen. CDS zerstören + neu erzeugen. Ein anderes MemoryDataSet nehmen. Das Problem ignorieren - in der Praxis sollte das doch nicht stören, oder was machst du da? |
AW: Clientdataset Speicherfreigabe
Hab meinen Testcode noch etwas erweitert (erstmal alles, was an Property und Methoden im ClientDataSet zu sehen war, nach dem Delete rein, gesehen es geht und dann schrittweise alles Unnütze wieder raus)
und eine Teillösung gefunden. Es gibt den Speicher nicht komplett frei, aber zumindesten das Log schein weg zu sein. (beim nächsten Add und Delete wird kein neuer Speicher hinzugefügt oder freigegeben)
Delphi-Quellcode:
{$OVERFLOWCHECKS OFF}
uses System.StrUtils, Data.DB, Datasnap.DBClient; procedure TForm8.FormCreate(Sender: TObject); var CDS: TClientDataSet; Mem: UInt64; procedure ShowState; var GStatus: TMemoryStatusEx; MMState: TMemoryManagerState; begin //Memo1.Lines.Add(' Records ' + CDS.RecordCount.ToString); GStatus.dwLength := SizeOf(GStatus); GlobalMemoryStatusEx(GStatus); GetMemoryManagerState(MMState); var FastMM: Int64 := 0; for var i := 0 to High(MMState.SmallBlockTypeStates) do Inc(FastMM, MMState.SmallBlockTypeStates[i].UseableBlockSize * MMState.SmallBlockTypeStates[i].AllocatedBlockCount); Inc(FastMM, MMState.TotalAllocatedMediumBlockSize); Inc(FastMM, MMState.TotalAllocatedLargeBlockSize); Memo1.Lines.Add(Format(' Memory %d%% %.2nm %s%.2nm / %.2nm', [ GStatus.dwMemoryLoad, Int64(GStatus.ullTotalVirtual - GStatus.ullAvailVirtual) / 1048576, IfThen(Mem < GStatus.ullAvailVirtual, '', '+'), Int64(Mem - GStatus.ullAvailVirtual) / 1048576, FastMM / 1048576 ])); Mem := GStatus.ullAvailVirtual; end; begin Mem := 0; CDS := TClientDataSet.Create(Self); CDS.FieldDefs.Add('TEST1', ftString, 500); CDS.FieldDefs.Add('TEST2', ftString, 500); CDS.FieldDefs.Add('TEST3', ftString, 500); CDS.CreateDataSet; CDS.LogChanges := False; for var L := 1 to 199 do try Memo1.Lines.Add(L.ToString); //Memo1.Lines.Add('Add'); for var i := 1 to 9999 do begin CDS.Append; CDS.FieldByName('TEST1').AsString := Random(MaxInt).ToString; CDS.FieldByName('TEST2').AsString := Random(MaxInt).ToString; CDS.FieldByName('TEST3').AsString := Random(MaxInt).ToString; CDS.Post; end; ShowState; //Memo1.Lines.Add('Delete'); {$IF false} while CDS.RecordCount > 0 do CDS.Delete; {$ELSE} CDS.EmptyDataSet; {$IFEND} ShowState; except on E: Exception do begin Memo1.Lines.Add(L.ToString + ' : ' + E.Message); ShowState; Break; end; end; end; |
AW: Clientdataset Speicherfreigabe
Zitat:
Die letzten 25 Logeinträge werden dort angezeigt. Bevor der 26.Eintrag geschrieben wird, wird der älteste Eintrag gelöscht. Dabei ist mir das aufgefallen, obwohl ich immer nur 25 Einträge habe läuft der Speicher immer weiter voll. |
AW: Clientdataset Speicherfreigabe
Wenn du nur die Datensätze überschreibst, anstatt zu löschen und neu zu erstellen, dann gibt es kein Problem.
Aber man sollte dringend LogChanges abschalten, denn erstmal bremst das ja ganz extrem und es verbrät dennoch ordentlich Speicher.
Delphi-Quellcode:
{$OVERFLOWCHECKS OFF}
uses System.StrUtils, Data.DB, Datasnap.DBClient; procedure TForm8.FormCreate(Sender: TObject); var CDS: TClientDataSet; Mem: UInt64; procedure ShowState; var GStatus: TMemoryStatusEx; MMState: TMemoryManagerState; begin GStatus.dwLength := SizeOf(GStatus); GlobalMemoryStatusEx(GStatus); GetMemoryManagerState(MMState); var FastMM: Int64 := 0; for var i := 0 to High(MMState.SmallBlockTypeStates) do Inc(FastMM, MMState.SmallBlockTypeStates[i].UseableBlockSize * MMState.SmallBlockTypeStates[i].AllocatedBlockCount); Inc(FastMM, MMState.TotalAllocatedMediumBlockSize); Inc(FastMM, MMState.TotalAllocatedLargeBlockSize); Memo1.Lines.Add(Format(' Memory %d%% %.2nm %s%.2nm / %.2nm / %.1nk records / %.1nk changes', [ GStatus.dwMemoryLoad, Int64(GStatus.ullTotalVirtual - GStatus.ullAvailVirtual) / 1048576, IfThen(Mem < GStatus.ullAvailVirtual, '', '+'), Int64(Mem - GStatus.ullAvailVirtual) / 1048576, FastMM / 1048576, CDS.RecordCount / 1000, CDS.ChangeCount / 1000 ])); Mem := GStatus.ullAvailVirtual; end; begin Mem := 0; CDS := TClientDataSet.Create(Self); CDS.FieldDefs.Add('TEST1', ftString, 500); CDS.FieldDefs.Add('TEST2', ftString, 500); CDS.FieldDefs.Add('TEST3', ftString, 500); CDS.CreateDataSet; CDS.LogChanges := False; for var i := 1 to 25 do begin CDS.Append; CDS.FieldByName('TEST1').AsString := Random(MaxInt).ToString; CDS.FieldByName('TEST2').AsString := Random(MaxInt).ToString; CDS.FieldByName('TEST3').AsString := Random(MaxInt).ToString; CDS.Post; end; ShowState; for var i := 1 to 100000 do try CDS.RecNo := i mod {25} CDS.RecordCount + 1; CDS.Edit; CDS.FieldByName('TEST1').AsString := Random(MaxInt).ToString; CDS.FieldByName('TEST2').AsString := Random(MaxInt).ToString; CDS.FieldByName('TEST3').AsString := Random(MaxInt).ToString; CDS.Post; if i mod 5000 = 0 then ShowState; except on E: Exception do begin Memo1.Lines.Add(i.ToString + ' : ' + E.Message); ShowState; Break; end; end; ShowState; end; |
AW: Clientdataset Speicherfreigabe
Vielleicht ist ein CDS auch nicht optimal für die Aufgabenstellung. Vielleicht wäre eine simple Liste passender? Oder wenn du es visualisiert haben willst, nimm eine TListBox + füttere dort jeweils 5 Items.
|
AW: Clientdataset Speicherfreigabe
Zitat:
Hätte ich nicht erwartet, da auch andere DataSets von FireDAC oder DevExpress z.B. sich nicht so verhalten. |
AW: Clientdataset Speicherfreigabe
Naja, DevEx und Firedac sind reine InMemory-Datasets + konsolidieren die Änderunggen eben nicht in einem Delta für n-Tier.
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 23:56 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