Ciao a tutti.
Ho un problema che mi sta facendo diventare pazzo.
Scenario.
Nel mio applicativo ho la necessità di leggere circa un centinaio di file di testo. Su questi file devo eseguire un'operazione di parsing nel senso che ogni riga contiene diverse informazioni e di queste mi interessano solo 3: un campo TDateTime, un campo stringa (standard) e un campo AnsiString. (domanda: è più efficiente usare le regular expression o usare comandi tipo extractword e ansireplacestr?)
Quindi ho definito un nuovo tipo come segue:
TRecord = packed record
Description:ansistring;
DT:tdatetime;
Component:string;
end;
TDataRec= array of TRecord;
Adesso, siccome ho la necessità di prendere il contenuto di tutti i file e di ordinarli per il campo TDateTime, procedo nel seguente modo:
- ho una variabile globale Data di tipo TDataRec
- ho una variabile temporanea Arr1 che uso per prendere i dati dalla funzione che interpreta i singoli file, sempre di tipo TDataRec
- all'interno di un ciclo che processa tutti i file, copio la zona di memoria della variabile arr1 nella variabile data
- nel caso in cui il file che ho letto non è conforme allo standard, non "appendo" il suo contenuto all'array e lo elimino dall'elenco dei file da processare
e quì si verifica il problema (in Lazarus, uso l'opzione -gh) perchè, se non procedo con l'operazione di copia della memoria, non riesco a liberare la memoria della variabile Arr1.
Vi posto il codice che uso.
procedure TForm1.LoadData(var List:TFileList; var Data:TDataRec);
var
c,n:integer;
Arr1:TDataRec;
previous:integer;
tmp:string;
begin
c:= 0;
while c < List.Count do
if (List.Files[c].ReadStatus<>rsReaded) and (List.Files[c].FileStatus<>fsUnknown) then
begin
tmp:=List.Files[c].Name;
if ShowInfoPanel then
begin
StaticText4.caption:=List.Files[c].Name;
application.ProcessMessages;
end;
Arr1:=List.Files[c].GetData(0); //quì popolo la variabile temporanea con i dati
if (List.Files[c].FileType=ftUnknown) and (mode=mDirectory) then //se i dati non sono conformi
begin //butto via il file dall'elenco
List.Delete(c);
DeleteX(ListMonitored,c);
UpdateLeftGrid;
end
else
begin //altrimenti
Previous:=length(Data); //salvo in previous la dimensione dell'array globale
SetLength(Data, Previous+length(arr1)); //ingrandisco la variabile per ospitare i nuovi dati
Move(Arr1[0], Data[Previous], Length(Arr1)*SizeOf(Arr1[0])); //copio la memoria di Arr1 in Data
inc(c);
end;
for n:=0 to high(Arr1) do //tento di pulire la ram usata...
begin
arr1[n].Component:='';
arr1[n].Description:='';
end;
// FillChar(Arr1[0],length(Arr1)*SizeOf(Trecord),#0); //altro tentativo
SetLength(Arr1,0);
end
else
inc(c);
end;
Mi spego meglio: se List.Files[c].FileType=ftUnknown non eseguo il codice dopo else e mi si verifica un leak di memoria; se List.Files[c].FileType<>ftUnknown, funziona tutto correttamente.
Presumo che il problema sia dovuto al fatto che le variabili ansistring usano il reference count e che se non deallocate correttamente i dati restano in ram.
C'è qualche anima pia che mi spiega come faccio a pulire correttamente la variabile Arr1? E' meglio evitare di usare le ansistring in favore di qualche altro tipo (pchar o similari)?
Grazie
Ciao xinyiman,
grazie per il tuo esempio.
In realtà non è molto diverso da come procedo io...
La differenza sostanziale è che tu usi le stringlist per leggere i file e, una volta finito di fare il parsing e di inserire i dati nella stringgrid, liberi le risorse con il free.
Io invece uso le array per tenere i dati per quel discorso che ti dicevo: quando hai una cosa come 400 mila / 1 milione di righe, la stringgrid mangia un botto di ram e non è molto efficiente. Per cui se invece di usare la stringgrid usi una drawgrid, in ram hai solo i dati (dell'array) + la memoria necessaria per creare la drawgrid (grande quanto il numero di record nell'array+1, anche se con una scrollbar separata non sarebbe nemmeno necessario averla così grande) e poi disegni solo i dati che al momento sono visibili (poche righe, quanto stanno nel monitor dell'utente), andando gestire il tutto nell'evento ondrawcell. Almeno così è come l'ho pensata... e funziona molto velocemente.
Il motivo, in realtà, per cui ho chiesto aiuto, invece, è che non mi riesce di liberare la ram occupata dall'array (temporanea) Arr1, mannaggia. Ma, forse, dagli innumerevoli test che ho fatto il problema è altrove. Mi spiego meglio che posso.
Lo so che non hai bisogno di lezioni, figuriamoci da me, ma forse potrebbe essere interessante per qualcuno che legge questi post. Quando in FPC usiamo la direttiva {h+} o dichiariamo le variabili come ansistring, il compilatore abilita il reference count.
Il che significa che se scrivo il seguente codice:
var
a,b:ansistring;
begin
a:='questa è una'+#13#10+'ansistring';
b:=a;
end;
B non è uguale ad A, ma B punta alla zona di memoria occupata da A e in A viene incrementato il "reference count" che dice "attenzione, c'è una variabile che fa riferimento a questa zona di memoria". Solo eseguendo una modifica in B, avviene l'effettiva copia di dei dati di A in B. Questo per risparmiare ram e aumentare la velocità.
Ma cosa succede (e questa è una domanda...) se scrivo il seguente codice?
var
a:ansistring;
function funzione_di_test:ansistring;
var
tmp:ansistring;
begin
tmp:='questa è una'+#13#10+'ansistring';
result:=tmp;
end;
begin
a:=funzione_di_test;
end;
Quì le cose si complicano, e non poco... se RESULT punta a TMP... quando la funzione termina, TMP non può essere distrutto in automatico perchè ha il reference count <> 0... E poi anche A punta RESULT... Quindi internamente non so come funzioni... Ma fin quì penso che il compilatore riesce a gestire il tutto, egregiamente.
Tuttavia, potrebbe essere che se invece di dichiarare le variabili di tipi semplici, vengono usati i tipi composti, la cosa potrebbe non essere così scontata. Lo stesso esempio con il tipo di dato che uso io diventa così:
{H-}
type
TRecord = packed record
Description:ansistring;
DT:tdatetime;
Component:string;
end;
TDataRec= array of TRecord;
var
a:TDataRec;
function funzione_di_test:TDataRec;
var
tmp:TRecord;
tmpdata:TDataRec;
begin
tmp.description:='questa è una'+#13#10+'ansistring';
tmp.dt:=now;
tmp.component:='questa è una stringa standard'
setlength(tmpdata,1);
tmpdata[0]:=tmp
result:=tmpdata;
end;
begin
a:=funzione_di_test;
end;
In questo caso, nel mio codice, mi si verifica un problema di memoria (me lo dice lo heaptrc). Da qualche parte devo aver deferenziato i puntatori di una delle variabili temporanee e quindi mi restano i dati in ram...
Non so che pesci pigliare. Forse hai ragione tu: dovrei usare un DB. Sto facendo qualche esperimento con TMemDataSet (con TBufDataSet non mi riesce di usare l'AppendRecord).