Newsgroup: it.comp.java
Titolo: Un problema di prestazioni per i più esperti

Ciao a tutti. Ho uno strano problema di prestazioni, di quelli che
piaceranno ai più esperti. Ho ceato una classe che si connette a un server
di dati remoto, esegue una query, e crea una tabella html con il risultato.
Pochè il server è remoto, mi faccio passare non da ResultSet, ma creo una
classe ResultData:
import java.util.Vector;
public class ResultData implements java.io.Serializable
{
        public Vector rows;
        public Vector heads;
        public ResultData(Vector h, Vector r)
        {
                rows = r;
                heads = h;
        }
}
che viene creato e popolato dal server, e inviato; il tutto velocemente.
Alla fine a me arrivano 2 vettori: resultdata.heads, che contiene
l'intestazione della colonna risultante, e resultdata.rows, che contiene un
vettore per ogni ecord della tabella. Per creare la <table> html scorro i
vettori e ci aggiungo i tag <td> e <tr> dove opportuno. Questo procedimento
però è risultato incredibilmente lento. Perchè? Come renderlo più veloce?
Ecco il codice:
public String creaTabelaHTML(String query)
{
String ret = "<TABLE>";
//si connette al DB e invia la query.
//Questa parte è veloce, anche se passa dalla rete.
System.out.println("Prima di query");
ResultData rd = ClientConnection.runQuery(query);
System.out.println("Dopo query");
//Qui inizia la parte lenta
//leggo il vettore della testa della tabella
System.out.println("Elaborazione")
ret += "<tr class=\"heads\">";
for (int i = 0; i < rd.heads.size(); i++)
{
        ret += "<TD border = \"1\">"
        + rd.heads.elementAt(i) + "</td>";
}
ret+="</tr>\n"
//leggo il vettore dei dati della tabella, che a sua volta contiene vettori,
uno per ogni record
for (int i = 0; i < rd.rows.size(); i++)
{
        Vector v = (Vector) rd.rows.elementAt(i);
        ret += "<tr class = \"rows\">";
        for (int j = 0; j < v.size(); j++)
        {
                ret += "<TD>" + v.elementAt(j) + "</td>";
        }
       ret += "</tr>\n";
}
ret+="</TABLE>";
System.out.println("Fine Elaborazione ");
System.out.println(ret);
return ret;
}

 
[CUT]
Secondo me i problemi iniziano qui. Certo, il costrutto di concatenamento
fra stringe è facile da comprendere, ma è anche una fortissima tentazione.
Ricordati che ogni riga del tipo:
  String ret = "Stringa1" + "Stringa2";
viene trasformata dal compilatore in
  String ret = new StringBuffer()
                   .append("Stringa1")
                   .append("Stringa2)
                   .toString();
conseguentemente l'istruzione del tuo codice
  ret += "<TD border = \"1\">" + rd.heads.elementAt(i) + "</td>";
 
diventa alla fine
  ret = new StringBuffer()
            .append(ret)
            .append("<TD border = \"1\">")
            .append(rd.heads.elementAt(i))
            .append("</td>")
            .toString();
è logico che questa operazione (o quella simile sui record), compiuta tante
volte di fila mette sotto stress estremo l'heap e il garbage collector, a
causa della continua creazione di oggetti StringBuffer temporanei (e loro
successiva distruzione per inutilizzo).
Personalmente avrei gestito queste operazioni tramite un buffer interno.
Senza cambiare troppo la tua procedura, puoi partire creando un
ByteArrayOutputStream (che si presta benissimo come buffer) con una
dimensione iniziale abbastanza estesa, così da minimizzare la possibilità di
dover costringere il buffer stesso a riallocarsi per fare spazio a nuovi
elementi:
  import java.io.*;
 
  ...
  ByteArrayOutputStream buffer=new ByteArrayOutputStream(16384); //16Kb
  //aumenta pure la dimensione se pensi di dover memorizzare un quantitativo
  //di dati maggiore
  PrintStream ps=new PrintStream(buffer); //così è più comodo
 
La creazione del PrintStream ps ti consente di inserire nuovi elementi nel
buffer semplicemente usanto gli straconosciutissimi println() e print().
Sempre per farti un esempio, ecco come verrebbe la parte iniziale della tua
procedura:
  ...
  ResultData rd = ClientConnection.runQuery(query);
  ps.print("<TABLE>");
  ps.print("<tr class=\"heads\">");
  for(int i=0; i<rd.heads.size(); i++) {
    ps.print("<TD border=\"1\">");
    ps.print(rd.heads.elementAt(i));
    ps.print("</TD>");
    //cerca sempre di evitare i concatenamenti (+) tra stringhe!!!
  }
  ps.println("</tr>");
  ...
 
e così via (qualcosa dovrai pur farla anche te :P).
Finito l'inserimento di tutti gli elementi, trasformi il buffer in una
stringa e la riporti all'esterno (così evitiamo di farti cambiare
l'interfaccia verso il resto della tua applicazione):
  ...
  ps.flush();ps.close();
  buffer.flush();buffer.close();
  //Anche se le sequenze flush/close non sono indispensabili, ci
  //garantisce che future implementazioni ottimizzate di
  //ByteArrayOutputStream funzionino sempre in modo coerente.
 
  return new String(buffer.toByteArray());
}
Come prima ottimizzazione dovrebbe accelerare un po' le cose. Se poi hai
voglia di farci una bella ripassata, secondo me ti conviene smettere di
usare i Vector e passare ai LinkedList, e sostituire i cicli for con gli
iteratori. Sempre prendendo l'esempio di prima, se heads fosse definito come
LinkedList:
  Iterator iter=rd.heads.iterator();
  while(iter.hasNext()) {
    ps.print("<TD border=\"1\">");
    ps.print(iter.next());
    ps.print("</TD>");
  }
Se leggi la documentazione, potrebbe saltarti all'occhio anche ArrayList. Ho
preferito utilizzare LinkedList proprio per un fatto di prestazioni.
ArrayList infatti quando genera i propri iteratori, internamente usa un
get(i) per recuperare gli elementi di volta in volta, mentre gli iteratori
generati da una LinkedList semplicemente scorrono la lista interna di
riferimenti in riferimento, operazione molto più veloce di un get() (almeno
per le consultazioni sequenziali).
-- 
Fermiamo Scientology prima che sia troppo tardi!!!
http://xenu.com-it.net
  ---  
http://xenu.freewinds.cx
(per rispondere personalmente leva IlDitoDalCulo)

[non quoto per pigrizia]
usa uno StringBuffer invece che una String
leggi
http://java.sun.com/j2se/1.4.2/docs/api/java/lang/String.html
http://java.sun.com/j2se/1.4.2/docs/api/java/lang/StringBuffer.html
per capire il perchè
Ah, non mi ritengo "uno dei più esperti" (poi, diciamocelo, non è che 
esattamente il tuo problema fosse da esperti) :)
Mi spieghi cosa intendi esattamente con "server remoto"? No, perchè non 
capisco cos'è sto casino che fai con ResultData... :) hai porte 
chiuse?... boh?...
Salut' :)
PS: visto che di performance parliamo, lascia perdere i Vector e usa 
(LinkedList o ArrayList vedi tu) che non sono sincronizzate - sempre che 
la sincronizzazione non ti serva (e non mi pare ti serva). in ogni caso 
Vector è antiestetico e fa sebrare brutto e vecchio il tuo codice (IMHO)
PS2: ma il tuo ClientConnection.runQuery(query) fa qualche tipo di 
autenticazione sul tuo server, non è che si connette e può eseguire una 
query arbitraria, vero?!?
PS3: per ciclare i Vector (o qualsiasi altra Collection) considera che, 
in generale, se usi un Iterator puoi star certo che è implementato nella 
maniera più performante (cosa che invece non è sempre vera per l'accesso 
random che fai tu)
PS4: in HTML non devi mettere spazi tra gli attributi e i loro valori 
(che io sappia, almeno - non ho voglia di cercare al w3c)
PS5: il fatto che rows e heads siano pubblici in ResultData è *molto* 
antiestetico
PS6: quando farai una query con una colonna di tipo DATE (o LOB) non 
lamentarti che la data ti viene stampata con un formato che non ti piace 
(o che non si capisce niente dell'output)
(ma, magari, la fai sul server la formattazione; il che, comunque, non 
va bene)
PS7: anche il fatto che mischi maiuscolo e minuscolo nei nomi dei tag 
html è antiestetico
PS8: in HTML non esiste un attributo border per i td - che poi IE se ne 
faccia qualcosa, non so, ma nell'HTML _non_esiste_
PS9: "creaTabelaHTML" (a parte l'ortografia) e "ResultData"... 
antiestetico ai massimi livelli. ma che ti costa a usare una lingua sola?!
PSultimo: non è che ce l'ho con te, è solo che mi è presa male... 
scusa... però ormai che l'ho scritta la mando :)
quando ti incontro ti offro una birra
ciao,
Giorgio

 l'intestazione della colonna risultante, e resultdata.rows, che contiene
un
...[CUT]...
Allora, a parte ciò che ti hanno consigliato gli altri, ti consiglio di non
usare
un Vector per ogni riga della tabella;
Ho dovuto risolvere anche io un problema simile al tuo percui valuta la
seguente sostituzione:
La tabella restituita dalla query sia rappresentata da una lista
(LinkedList) di Object[]
- LinkedList poichè
  1. In scrittura è più performante rispetto l'arrayList
  2. Dal tuo codice pare che l'unico urilizzo che ne farai sarà scorrerla
linearmente col ciclo for (*)
-Object[] per quanto segue:
  Una volta eseguita la query, il numero di colonne del ResultSet restituito
è costante ( usa
  getColumnCount ),
  quindi perchè usare una struttura dati dinamica quando puoi
  usare tranquillamente un Object[] ? ... stessa cosa per l'intestazione...
Certamente è più comodo non avere il problema di sfondare l'array ma se hai
bisogno di velocizzare l'applicazione...
Inoltre, una volta scritto e testato il codice, lo potrai riutilizzare in
futuro...;)
(*) La LinkedList è molto + rapida dell' ArrayList in inserimento /
inserimento casuale / eliminazione di un elemento,  perchè l'ArrayList si
basa su un array, percui, ogni volta che si riempie deve ricopiare l'array
in uno più grande, ogni volta che inserisci un elemento deve ricostruire
l'array ricopiandolo etc ... la linked list invece è una lista concatenata,
quindi fa MOLTO prima... Dal canto suo invece, l'ArrayList va MOLTO meglio
per accessi in lettura "casuali" ( cioè non dall'elemento 0 all'ennesimo uno
dopo l'altro, ma tipo  "scegliere una carta da un mazzo" )  in parole
povere, se invochi
Object obj = list.get(19)
su un ArrayList, esso, raggiunge il 20°  come se stesse  eseguendo
Object obj = array[19]
mentre la LinkedList deve passare per tutti gli elementi fino a raggiungere
il ventesimo ...
Questo fatto si riflette su un eventuale ordinamento che con l'ArrayList
sarebbe certamente più rapido
Vedi i test
http://java.sun.com/developer/JDCTechTips/2002/tt0910.html

1.In realtà il codice non l'ho copiato pari pari, ma a pezzi,  per quello è
scritto un po' male (per inciso il metodo creaTabela non esiete, era per non
mettere pezzi di codice a caso).Per quello non capisci cosa fa  runQuery. A
me interassava capire se String e Vector potessero avere un peso sulle
prestazioni dell'algoritmo, e il tuo aiuto è stato prezioso!
2.Non devi chiedermi scusa perchè è una delle risposte migliori che mi
potessero dare: avendo imparato da solo non può che servirmi il parere,
anche se negativo, degli altri.
3.Il programma è un prototipo scritto in pochi minuti per vedere se
funzionava, perciò sembra una porcheria, ma verra aggiustato.
4."server remoto" significa che passo a un socket una stringa (la query) e
quello mi risponde con ResultData, infilato in un "ObjectInputStream"
quindi:
- la serializzazione mi serve.
- devevono arrivami tutti i dati in blocco, quindi non posso usare
ResultSet, che se ho capito bene è solo un "puntatore" ai dati.
5.Per le schifezze che faccio con html ho un webmaster apposta che ci pensa.
6.rows e heads sono public per pigrizia.
Ciao!!
PS. Cosa significa QUOTARE?
"Giorgio Gallo" <giorg...@RMVoverlord.it> ha scritto nel messaggio
news:c9j4fs$knd$1...@grillo.cs.interbusiness.it...


 ha scritto nel messaggio
news:0Y6vc.386146$rM4.16...@news4.tin.it...


 ha scritto nel messaggio
news:Aulvc.87765$Qc.34...@twister1.libero.it...


ok ok - facciamo DUE birre pippo! ;)
te l'ho detto, ieri notte m'è presa male :)
quotare è riportare parte del messaggio cui si risponde (le parti 
salienti) per dare modo a chi legge di sapere il contesto cui ci si 
riferisce... sarebbe buona educazione farlo, ma io sono pigro... :)