Italian community of Lazarus and Free Pascal

Programmazione => Generale => Topic aperto da: kalagan - Dicembre 04, 2011, 09:39:33 am

Titolo: Dynamic array e ansistring
Inserito da: kalagan - Dicembre 04, 2011, 09:39:33 am
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:

Codice: [Seleziona]
  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:
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.
Codice: [Seleziona]
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
Titolo: Re:Dynamic array e ansistring
Inserito da: xinyiman - Dicembre 04, 2011, 10:32:47 am
Ciao, solo alcune domande.

1. Tu esattamente cosa devi fare?
2. I dati che leggi al posto di tenerli in memoria non conviene caricarli in un DB?
3. I campi della singola riga sono divisi tra di loro da qualcosa? Tipo ; e " ?

Fammi sapere
Titolo: Re:Dynamic array e ansistring
Inserito da: kalagan - Dicembre 04, 2011, 11:48:56 am
Ciao xinyiman,
cerco di spiegarmi meglio che posso...

Sto realizzando un viewer di file multiplo. In pratica un servizio Microsoft ha diversi thread. Ogni thread scrive un log, indipendente l'uno dall'altro.
Il problema è che se una persona deve analizzare i log per capire cosa sta succedendo, risulta molto difficile allineare le varie informazioni loggate in ordine temporale.

Allora la mia idea è quello di caricare tutti i log in ram e ordinare l'array contenente i dati per campo data/ora.
Ti posto uno screenshot dell'applicativo...

Siccome questi log vengono scritti sia sul server che per ogni client dov'è installato questo servizio, mi serviva di realizzare un applicativo che non carica i dati in un DB specifico... ma di caricarli al runtime (sulla macchina dove viene eseguito l'applicativo). Certo potrei usare un TBufDataSet (o similari) e agganciarci una dbgrid... ma non mi è chiaro poi come gestire la visualizzazione dei dati. Infatti, primo non saprei come ordinare i dati (in modo efficiente e "stabile"), secondo non saprei come "caricare" solo i dati da visualizzare nella griglia e terzo, non saprei come, a fronte di un evento di modifica, aggiorare i dati visualizzati in tempo reale (funzione tail).
Approfondisco il tema: inizialmente ho pensato di usare una stringgrid; poi mi sono accorto che scrivere i dati (70MB) di file in una stringgrid raggiungevo i 600MB in ram; allora ho pensato (mi hanno suggerito) di usare una drawgrid, dove i dati non venivano scritti nella griglia, ma venivano "disegnati" sulla griglia. Per esempio: se devo visualizzare le righe da 17000 a 17036 (37 sarebbero le righe visualizzabili in questo esempio) allora basta "disegnare" i dati presenti nella mia array da 17000-1 a 17036-1. Così facendo l'utente può scrollare su e giù la griglia, ma i dati da "disegnare" sarebbero sempre e solo quanti sono le righe da visualizzare sullo schermo. Oltre a questo, il mio problema è che le righe da visualizzare non hanno altezza fissa: nel senso che ci sono record che contengono più righe (per cui devo modificare l'altezza della grigia). Insomma, la faccio breve, ho deciso che il componente migliore non è la drawgrid, ma una KGrid (componente esterno) dove ho dichiarato che i dati sono virtuali. E funziona tutto correttmente.
Se poi mi riesci a dare una qualche indicazione di come usare un DB per fare quanto ho bisogno (magari con un piccolo esempio) te ne sarei molto grato.

Riguardo ai dati da caricare.
Quì è un vero casino (scusa per il termine). Infatti Microsoft ha usato diversi "template", se così si possono definire. Fondamentalmente li posso dividere in 2: quelli client e quelli server.

Quelli client sono così formattati:
<![LOG[descrizione]LOG]!><time="hh:nn:ss.zzz+-timezone" date="mm-dd-yyyy" component="componente" context="" type="codice tipo" thread="thread id" file="file cpp:riga">.
Non sembra difficile prendere i dati che mi interessano... purtroppo, in realtà, il campo descrizione può essere spezzettato su più righe... che non sono terminate con il classico #13#10 ma solo con #10. E questo mi ha causato grossi problemi perchè in FPC, una riga può terminare indifferentemente con #13, #10 oppure #13#10...
ESEMPIO:
<![LOG[Raising event:

instance of CCM_PolicyAgent_SettingsEvaluationComplete
{
   ClientID = "GUID:E0DACB52-0EB8-4CA1-9E2F-755B461198F3";
   DateTime = "20110823134223.458000+000";
   PolicyNamespace = "\\\\office\\root\\ccm\\policy\\machine\\actualconfig";
   ProcessID = 2768;
   ThreadID = 916;
};
]LOG]!><time="15:42:23.458+-120" date="08-23-2011" component="PolicyAgent_PolicyEvaluator" context="" type="1" thread="916" file="event.cpp:525">

In questo caso, come faccio a capire dove finisce il record se la lettura (readln) mi termina a "<![LOG[Raising event:"??
Quindi ho dovuto riscrivermi il readln per considerare #13#10 come unico terminatore di linea possibile...

Riguardo i log server questi sono formattati così:
Descrizione  $$<componente><ddd mmm dd hh:nn:ss.zzz yyyy W. Europe Daylight Time><thread=thread id>
ESEMPIO
INFO: successfully completed directory search~  $$<SMS_AD_SYSTEM_GROUP_DISCOVERY_AGENT><Fri Aug 26 19:06:17.797 2011 W. Europe Daylight Time><thread=7924 (0x1EF4)>

Ovviamente, come nessuno avrebbe mai immaginato, "ddd mmm" possono essere indifferentemente in italiano o inglese... anche nello stesso file...(?!? come ci siano riusciti è un mistero).
Titolo: Re:Dynamic array e ansistring
Inserito da: xinyiman - Dicembre 04, 2011, 04:53:44 pm
Mi posti un paio di righe di quei log che vedo cosa posso fare per crearti un esempio!
Titolo: Re:Dynamic array e ansistring
Inserito da: kalagan - Dicembre 04, 2011, 08:57:01 pm
Ecco 2 log, uno per tipologia
Titolo: Re:Dynamic array e ansistring
Inserito da: xinyiman - Dicembre 05, 2011, 11:05:39 am
Prova il mio allegato e fammi sapere. Per provarlo basta che prendi i due file che mi hai allegato, li metti nella cartella dove c'è il progetto e compili. E' solo un esempio per farti vedere come farei io. Poi gestiscitelo tu, metti le colonne come vuoi e via discorrendo. Se devi inserire più file inseriscili in una matrice e poi popola la griglia con quella matrice. Ciao
Titolo: Re:Dynamic array e ansistring
Inserito da: kalagan - Dicembre 05, 2011, 06:02:32 pm
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:
Codice: [Seleziona]
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?
Codice: [Seleziona]
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ì:

Codice: [Seleziona]
{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).
Titolo: Re:Dynamic array e ansistring
Inserito da: xinyiman - Dicembre 06, 2011, 09:52:35 am
Domanda stupida, io non ho a disposizione tutto il tuo sorgente per capire bene cosa fai o cosa non fai. Ma se scrivi nome della variabile.free o .destroy quando non ti serve più cosa succede! Generalmente la sintassi per liberare l'area di memoria occupata è NomeVariabile.Free se parliamo di una classe. Diversamente consiglio di caricarti questi dati in un DB SqlLite e poi visualizzare i dati dopo!
Titolo: Re:Dynamic array e ansistring
Inserito da: kalagan - Dicembre 12, 2011, 05:31:28 pm
Ciao xinyiman,
scusa se non ti ho risposto ma ero occupato.
Volevo dirti che ci sono riuscito a liberare sta ram!!. Finalmente...

Ovviamente ero io che sbagliavo... Avevo aggiunto una riga che mi mandava in follia i puntatori delle ansistring: FillChar(rec,sizeof(TRecord),#0);. Tolta sta maledetta riga adesso la heaptrc mi riporta 0 unfreed memory blocks.
Successo informatico...
Titolo: Re:Dynamic array e ansistring
Inserito da: xinyiman - Dicembre 12, 2011, 07:25:58 pm
Grande  ;)