Newsgroup: it.comp.lang.delphi
Titolo: Prestazioni per lavoro su file e tabelle

Salve a tutti sono qua per chiedere opinioni su come ottimizzare un
programmino che ho fatto.
Spiego innanzitutto il problema, ho un file testo generato da un
sistema unix, ogni riga un record, diviso secondo un tracciato record
ben preciso (niente csv), io devo aggiungere in fondo al tracciato
altri due campi da 4 caratteri, in base a criteri in parte
automatizzati in parte manuali ( e si verificano assieme) quindi ho la
necessità di poter visualizzare l'intera lista e scorrendola decidere
dove inserire questi campi.
Ho agito così:
ho creato una stringlist e ci ho caricato il file (operazione che
avviene in maniera velocissima anche con un file da 61000 record di
circa 19MB)
visto che ho la necessità di visualizzare i campi separati e tutti i
record ho pensato di spararne in contenuto in una grid, a questo punto
mi chiedo, mi conviene passare il contenuto su una tabellina locale in
paradox e poi aprirlo tramite la solita query e la dbgrid oppure
gestirmi diversamente una normale grid?? Io per ora (ma posso anche
cambiare volendo) ho seguitop il primo metodo, mi scarico tutta la
lista di stringhe nella tabella, ovviamente per ogni record vado a
estrarre campo per campo i valori e li inserisco nei rispetivi campi
della tabella.
Questo è il punto dolente, tutto il resto del programma scorre via
liscio, elaborando grosse (poi neanche tanto grosse)quantità di dati
in pochissimo tempo, ma per fare questo passaggio metto un sacco di
tempo.
Poi scorro lungo i dati in maniera automaticva e manuale assieme (in
automatico faccio dei salti prestabiliti, poi a mano vaod a rifinire
dove esattamente dovrò spezzare) e anche qui tutto avviene velocemnte.
in fine dopo aver confermato i punti in cui spezare, il programma va a
aggiungere questi campi sulla lista originale dei dati (quella che
avevo caricato all'inizio) e va a salvarli in un altro file, anche
questo a supervelocità.
Io mi incazzo come un'ape per quel passaggio che poi dopo aver
sfogliato tutti i campi rimane li (i dati dalla tabella non vengono
più presi, anzi mi occupo sempre di svuotare la tabella all'inizio del
programma) e mi fa perdere su un server birpocessore PIII 1.4 Ghz con
2 Gb di ram circa 8 minuti con un utilizzo della cpu del 50% (il
programma dovrebbe essere lanciato massimo 10 volte all'anno e da una
sola persona, quindi potrei anche accettare il tempo, ma non mi piace
che un'applicazione che per tutti i restanti passaggi si compoerta
così bene mi faccia perdere tutto quel tempo in quello meno
importante).
Come posso velocizzare quel passaggio???
io ho impostato una insert con un parametro per campo, e poi ciclando
sulla lista di stringhe passo ogni volta tutti i parametri e execsql,
così da inserirle tutte, ho proprio scazzato?? Solitamente uso le
query per abitudine, ma in questo caso non è che una insert con la
Ttable potrebbe essere più veloce?? ovviament epotrei limitare il
nunmero di campim inserito, ma ho dato la possibilità all'utente di
personalizzare i campi visualizzati e ho nmotato che alcuni
prefersicono dei campi rispetto ad altri, quindi sono costretto a
inserirli tutti.
Posto quim di seguito la parte di codice incriminata: (scusate se le
righe non ritornano a capo perfettramente)
//inserisco i dati in tabella, prima cambio la caption del messaggio
    showmessage('Verranno ora memorizzati i dati attuali nella
tabella,' + #13 + 'attendere prego...' + '(Premere OK per
proseguire)');
    with datamodule2.Query1 do
          begin
            sql.Clear;
            sql.Add('insert into "tab.db"');
            sql.Add('values
(:id,:prog-utenza,:servizio,:cod-contr,:comune-loc,:zona-loc,:via-loc,');
            sql.add(':civico-loc,:presa-loc,:interno-loc,:ragione-sociale,:attrib-via,:via,');
            sql.add(':civico,:presa,:descr-comune,:matr-num,:matr-alf,:descr-marca,:descr-ubi,');
            sql.add(':tipo-abita,:descr-uso,:portata,:anno-fabb,:periodicita,:data-posa,:cifre,');
            sql.add(':ultima-lett,:data-ult-lett,:consumo,:perc-scostp,:perc-scostn,:scartp,:scartn,');
            sql.add(':perc-scostbp,:perc-scostbn,:sino-boll)');
          end;
    //comincio un ciclo che durerà fino all'ultima riga della
stringlist da leggere
    for i:=0 to dati.Count - 1 do
      begin
        //estraggo per ogni riga il contenuto nella tabella
        with datamodule2.Query1 do
          begin
            parambyname('id').AsInteger:=i+1;
            ParamByName('prog-utenza').AsString:=copy(dati.Strings[i],1,6);
            ParamByName('servizio').AsString:=copy(dati.strings[i],7,1);
            ParamByName('cod-contr').AsString:=copy(dati.strings[i],8,2);
            ParamByName('comune-loc').AsString:=copy(dati.strings[i],10,2);
            ParamByName('zona-loc').AsString:=copy(dati.strings[i],12,3);
            ParamByName('via-loc').AsString:=copy(dati.strings[i],15,3);
            ParamByName('civico-loc').AsString:=copy(dati.strings[i],18,3);
            ParamByName('presa-loc').AsString:=copy(dati.strings[i],21,3);
            ParamByName('interno-loc').AsString:=copy(dati.strings[i],24,3);
            ParamByName('ragione-sociale').AsString:=copy(dati.strings[i],27,36);
            ParamByName('attrib-via').AsString:=copy(dati.strings[i],63,5);
            ParamByName('via').AsString:=copy(dati.strings[i],68,24);
            ParamByName('civico').AsString:=copy(dati.strings[i],92,3);
            ParamByName('presa').AsString:=copy(dati.strings[i],95,3);
            ParamByName('descr-comune').AsString:=copy(dati.strings[i],98,30);
            ParamByName('matr-num').AsString:=copy(dati.strings[i],128,9);
            ParamByName('matr-alf').AsString:=copy(dati.strings[i],137,2);
            ParamByName('descr-marca').AsString:=copy(dati.strings[i],139,20);
            ParamByName('descr-ubi').AsString:=copy(dati.strings[i],159,38);
            ParamByName('tipo-abita').AsString:=copy(dati.strings[i],197,38);
            ParamByName('descr-uso').AsString:=copy(dati.strings[i],235,10);
            ParamByName('portata').AsString:=copy(dati.strings[i],245,8);
            ParamByName('anno-fabb').AsString:=copy(dati.strings[i],253,2);
            ParamByName('periodicita').AsString:=copy(dati.strings[i],255,2);
            ParamByName('data-posa').AsString:=copy(dati.strings[i],257,6);
            ParamByName('cifre').AsString:=copy(dati.strings[i],263,1);
            ParamByName('ultima-lett').AsString:=copy(dati.strings[i],264,8);
            ParamByName('data-ult-lett').AsString:=copy(dati.strings[i],272,6);
            ParamByName('consumo').AsString:=copy(dati.strings[i],278,8);
            ParamByName('perc-scostp').AsString:=copy(dati.strings[i],286,3);
            ParamByName('perc-scostn').AsString:=copy(dati.strings[i],289,2);
            ParamByName('scartp').AsString:=copy(dati.strings[i],291,3);
            ParamByName('scartn').AsString:=copy(dati.strings[i],294,3);
            ParamByName('perc-scostbp').AsString:=copy(dati.strings[i],297,3);
            ParamByName('perc-scostbn').AsString:=copy(dati.strings[i],300,2);
            ParamByName('sino-boll').AsString:=copy(dati.strings[i],302,1);
            ExecSQL;
          end;
      end;
Spero che saprete aiutarmi, e mi scuso per la lunghezza del messaggio.
Roberto Gibertini

"Roberto Gibertini" ha scritto nel messaggio
[CUT]
Penso che la soluzione di passare tramite una tabella sia piu che adeguata.
Sicuramente la struttura della tabella influisce sulle prestazioni: se non
hai bisogno di indici particolari per lo scorrimento, elimina ogni indice
secondario ed, eventualmente, anche la chiave primaria (i record verranno
inseriti e saranno visibili esattamente nell'ordine di inserimento). Questo
ovviamente influisce poi sulle prestazioni delle fasi successive di
elaborazione.
In una situazione simile alla tua, ma con un numero maggiore di record, ho
notato una discreta differenza tra lavorare su una tabella precedentemente
svuotata (contenente i dati dell'elaborazione precedente) e lavorare su una
vuota: quindi, non se non l'hai già fatto, invece di svuotare la tabella
dalla precedente elaborazione, eliminarla cancellandone i files, e
rimpiazzarla con una copia vuota depositata i un'altra cartellina e lavorare
direttamente su questa.
In una tabella di tipo Paradox (immagino aperta tramite BDE) l'uso di SQL è
meno performante delle operazioni di inserimento diretto tramite oggetto
TTable.
Inoltre prova a guardare il componente TBatchMove, magari potrebbe fare al
caso tuo per il trasferimento dei dati e potresti utilizzarlo in
sostituzione del caricamento nella StringList e nel successivo scaricamento
nella tabella Paradox. In effetti è pensato per eseguire anche operazioni
del genere.
L'ultima cosa, ma non so se e come funziona, provare ad utilizzare il driver
BDE per files di testo. Io non l'ho mai usato, ma magari potrebbe fare al
caso tuo. In questo modo dovresti poter vedere il file di testo come
tabella, sicuramente con dei limiti, ma almeno eviteresti il trasferimento
nella tabella Paradox.
Come già saprai il fatto che il computer sia biprocessore non influisce
sulle prestazioni dell'applicazione, in quanto comunque un singolo thread
viene eseguito su un solo processore.
Sinceramente, visto il tipo di applicazione e la modalità in cui viene
eseguita, io non cercherei ulteriori ottimizzazioni, preferendo un algoritmo
piu pulito e leggibile; magari potresti aggiungere (se non l'hai già fatto)
una bella interfaccia Utente che mostri l'avanzamento delle operazioni in
corso...
Buon lavoro.
Gianluca.

 dove inserire questi campi.
[cut resto msg]
Un paio (o piu') di prove che farei:
a) fare *un* Prepare esplicito della query prima di iniziare il ciclo di
valorizzazione parametri ed esecuzione dello statement;
b) sostituire la tabella con un ClientDataSet in modo da far tutto in
memoria (in tal caso s'usera' Insert o InsertRecord);
c) lasciar perdere il supporto della dbGrid (che immagino sia cio' che
importa) e fare la visualizzazione:
  c1) con una StringGrid (visualizzazione ed editing)
  c2) con una ListView (solo visualizzazione: editing dei due campi a
parte)
Inoltre, se possibile, eviterei di visualizzare quelle righe per le
quali il programma e' in grado di calcolare automaticamente i campi
addizionali.
Infine, ma e' una mia preferenza, farei lo spezzamento della stringa con
un ciclo
type
  tFields = (
    fUtenza,
    fServizio,
    ...
    fSino-boll
  );
  tStartLen = (slStart, slLen);
const
  Limits: array[tFields, tStartLen] of integer = (
    (1, 6),
    (7, 1),
     ...
    (302, 1)
  );
for i := 0 to Source.Count - 1 do begin
  Cells[0, i+1] := Inttostr[i+1];
  for f := low(tfields) to High(tfields) do
    Cells[ord(f)+1, i+1] := Copy(Source[i],
      Limits[f, slStart], Limits[f, slLen]);
end;
Saluti
Paolo Arosio

 Gianluca.
Grazie mille e buona lavoro anche a te.

 Paolo Arosio
Ciao e grazie mille.
Roberto Gibertini

 oppure lo faccio durare il minor tempo possibile.
Sei su Terminal Server?
Gianluca.

 la sconsigli??
Intendevo: visto che il collo di bottiglia era il dataset, se lavori
direttamente su una stringgrid ti eviti di aggiungere i record al
dataset:
devi solo fare
Griglia.ColCount := NumeroCampi + 1;
Griglia.RowCount := SourceStringList.Count + 1;
a quel punto
for j := 0 to NumCampi - 1
  Griglia.Cells[j+1, 0] := NomeCampo[j];
for i := 0 to SourceStringList.Count-1 do begin
  Griglia.Cells[0, i + 1] := Inttostr(i + 1);
  for j := 0 to NumCampi-1 do
    Griglia.Cells[j + 1, i + 1] := EstraiCampo(j, SourceStringList[i]);
end;
Saluti
Paolo Arosio

che lavorano in memoria)
Se vi interessa, sul sito Borland è possibile leggere una serie di
articoli di Cary Jensen sull'uso dei CDS.
-- 
Andrea Laforgia
#exclude <windows.h>

articoli di Cary Jensen sull'uso dei CDS.
Hai il link diretto?
Morde.
morde [at] programmer [.] net

Hai il link diretto?
http://bdn.borland.com/soapbox/professionaldeveloper/


 sono) non rallenteranno la mia  applicazione??
Il controllo per l'aggiornamento della progress bar lo fai tipo questo:
  ....
  if NumRecord mod 2500 = 0 then
    AggiornaAvanzamento;
  ...
Rispetto al resto delle operazioni occuperebbe meno del 1% (penso).
Per ridurre l'uso della cpu se non riesci a velocizzare il processo puoi
impostare
la priorità dell'applicazione ad un livello più basso così che se a qualche
client serve
un po' di cpu il tuo programma viene messo in "background" dal S/O.

 Gianluca.
Ciao 
Roberto Gibertini

 un po' di cpu il tuo programma viene messo in "background" dal S/O.
questo lo posso fare da programma o lo devo impostare sul server??
Ciao e grazie
Roberto Gibertini

 Paolo Arosio
CIao
Roberto Gibertini

 questo lo posso fare da programma o lo devo impostare sul server??
Per cambiare la priorità mi sembra si faccia così:
var
  H : THandle;
begin
  H := GetCurrentProcess();
  SetPriorityClass(H,REALTIME_PRIORITY_CLASS);
end;
Al posto di REALTIME_PRIORITY_CLASS devi metterci un valore molto più basso
(quello è il più alto!!).