Newsgroup: it.comp.java
Titolo: Prestazioni Stringhe/StringBuffer (se a qualcuno interessa)

Buon giorno a tutti! :)
Nei miei delirii notturni alla ricerca di un miglioramento delle
prestazioni di una mia applicazione, mi sono scontrato col seguente
dilemma:
  String s = " ", prova = "Prova", di = "di", concatenazione =
"concatenazione", stringhe = "stringhe";
  String somma1 = prova + s + di + s + concatenazione + s + di + s +
stringhe;
  String somma2 = new
StringBuffer(prova).append(s).append(di).append(s).append(concatenazione).append(s).append(di).append(s).append(stringhe).toString();
Quel che mi domandavo era questo: quale dei due modi di sommare le
stringhe sarà più performante? Mi ricordavo infatti che la somma tra
stringhe in Java era piuttosto lenta perchè per ogni + doveva essere
istanziato uno stringbuffer che sommava le due componenti e poi
restituiva il toString(). Spinto dalla curiosità, compilo la classe, dò
un bel 'java Test' e ottengo:
'Waffanculo'. Dopo aver sgranato gli occhi mi sono reso conto che nel
classpath avevo una classe Test di troppo (e che dovrei dormire di più
la notte, visto di cosa sono capace per fare i test); metto tutto a
posto e dò un DJava Test. Il risultato è il seguente (SNIP!-pato)
    0 ldc #1 <String " ">
    2 astore_1
    3 ldc #2 <String "Prova">
    5 astore_2
    6 ldc #4 <String "di">
    8 astore_3
    9 ldc #3 <String "concatenazione">
   11 astore local4
   13 ldc #5 <String "stringhe">
   15 astore local5
   17 new #10 <Class StringBuffer>
   20 dup
   21 aload_2
   22 invokestatic #18 <Method String.valueOf(Object):String>
   25 invokespecial #13 <Method StringBuffer.<init>(String):void>
   28 aload_1
   29 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
   32 aload_3
   33 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
   36 aload_1
   37 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
   40 aload local4
   42 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
   45 aload_1
   46 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
   49 aload_3
   50 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
   53 aload_1
   54 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
   57 aload local5
   59 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
   62 invokevirtual #17 <Method StringBuffer.toString():String>
   65 astore local6
   67 getstatic #15 <Field System.out:PrintStream>
   70 aload local6
   72 invokevirtual #16 <Method PrintStream.println(String):void>
   75 new #10 <Class StringBuffer>
   78 dup
   79 aload_2
   80 invokespecial #13 <Method StringBuffer.<init>(String):void>
   83 aload_1
   84 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
   87 aload_3
   88 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
   91 aload_1
   92 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
   95 aload local4
   97 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
  100 aload_1
  101 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
  104 aload_3
  105 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
  108 aload_1
  109 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
  112 aload local5
  114 invokevirtual #14 <Method
StringBuffer.append(String):StringBuffer>
  117 invokevirtual #17 <Method StringBuffer.toString():String>
  120 astore local7
  122 getstatic #15 <Field System.out:PrintStream>
  125 aload local7
  127 invokevirtual #16 <Method PrintStream.println(String):void>
  130 return
Come si evince... il risultato adesso è lo stesso!
Adesso qualcuno di voi mi dirà 'Bravo bischero, dormi di più la notte
invece di fare queste cose'; per quelli di voi invece che si sono sempre
spremuti per far andare i programmi un po' più veloci invece la buona
notizia è che sembra che adesso possiamo scrivere codice più leggibile.
Spero che a qualcuno possa interessare il risultato di questa piccola
prova, e scusate se sono stato un po' prolisso.
Ciao,
 Stefano
----------
"X WindowSSS, teSSSoro mio... lo odieremo per SSSempre"
Bill Gollum
----------

 
a me interessava! Grazie. Ho sempre preferito la chiarezza
all'efficienza a tutti i costi -se non proprio necessaria-
e questo mi da` una "scusa" in piu` per continuare cosi`!
-- 
Ciao, Matteo                        !! Memories flashin' through my head
                                    /\ Was I just born or am I dead?
mailto:
matteo.mattia
 (at) 
libero.it
 \/              -- The Morning After
                                    ¡¡                   Faith No More

avevo letto da qualche parte che una String non puň cambiare dimensione e
bisogna per forza usare Stringbuffer se si vogliono accodare piů stringhe.
io non ci ho creduto perchč mi sembrava una stronzata e infatti i programmi
hanno sempre funzionato.
ho fatto bene dunque ? (era una domanda che volevo fare da tempo)
cioč
String pippo1,pippo2,pippo3;
...
pippo1=pippo2+pippo3;
č ok ?
ciao
frolix8

 è ok ?
E' ok e va bene, però cose come:
String str = "";
for(int i=0; i<1024; i++)
  str += (char)i;
sono agghiaccianti: avrai 1024 riallocazioni di memoria! Perciò okkio e
dotatevi di un buon profiler.
ByEZ,
MindFlavor
--
MindFlavor's Site 
http://web.tiscalinet.it/mindflavor
Mail: mailto:
mindflav...@hotmail.com

 frolix8
Nell'esempio che dai é OK. Questo perché  in realtà Java traduce il codice
in qualcosa del genere:
pippo1=(new StringBuffer(pippo2)).append(pippo3).append(pippo4).toString();
Dove non bisogna assolutamente usare le String ma esplicitamente le
StringBuffer é in un'iterazione:
String pippo = "";
String pluto = "x";
for(int i=0; i<1000;i++) {
    pippo=pippo+pluto;
}
Questa iterazione da come risultato una String con 1000 x, ma nel frattempo
ha creato 999 StringBuffer e 999 String intermedie che sono subito garbage.
Dunque spreco memoria e anche di processore, visto che la creazione di un
oggetto é dispendiosa.
Il codice qui sopra é percio' da sostituire con il seguente, il quale é
molto piu efficace:
StringBuffer pippo = new StringBuffer();
String pluto = "x";
for(int i=0; i<1000;i++) {
    pippo.append(pluto);
}
bye
Carlo

 StringBuffer é in un'iterazione:
Si, in effetti il mio esempio era valido unicamente nel caso di somma tra
stringhe all'interno della stessa espressione, probabilmente sono stato poco
chiaro. Nell'iterazione, come Carlo e MindFlavor han fatto notare, è ovvio che
occorre usare uno StringBuffer, come è ovvio che conviene usarlo nel caso
dobbiate fare una somma tra stringhe 'in più parti',come ad esempio:
StringBuffer sb = new StringBuffer("SELECT ");
if (scegliNome)
  sb.append("Nome, ");
if (scegliCognome)
  sb.append("Cognome, ");
sb.append(resto_della_query);
resultset = statement.executeQuery(sb.toString());


infatti i programmi hanno sempre funzionato.
Non e' affatto una stronzata. String e' un carrier: definisci il suo valore a 
tempo di costruzione, e poi non puoi modificarlo. Al piu' puoi *usarlo* per 
costruire altre string.
StringBuffer incapsula una stringa modificabile.
Come Integer e int. Ovvio che puoi sempre "estrarre" l'intero da un Integer, ma 
non puoi modificare lo *stesso* oggetto.
I programmi funzionano perche' - semanticamente - costruire due stringhe S1 e 
S2 immodificabili e concatenarle per ottenerne una terza S3 (un *diverso* 
oggetto) e' equivalente a costruire una stringa S1 modificabile e attaccarci 
S2, *se tutto quello che ti interessa e' la stringa finale*, com'e' spesso il 
caso. 
Se invece ti interessano gli effetti sulle stringhe di partenza, e' diverso.
Tipicamente questo e' il caso se vuoi passare la stringa ad un metodo che la 
modifichi: con String non puoi. Ad esempio, con la sola "String" non puoi 
costruire 
 void concatena(S destinazione, S a, S b)
se il tipo S e' "String". 
-- 
God is Real, unless declared Integer.
ObjectZone - 
http://space.tin.it/computer/csadun


scusa ma sono un attimo confuso.
e se io faccio
String PIPPO="pippo";
PIPPO=pippo+"pluto";
possono esserci problemi ?
cioč, PIPPO viene definita lunga 5 , ma se aggiungo "pluto" la lunghezza
diventa 10, quindi č modificabile.
io in un programma ho fatto qualcosa del genere, anzi veramente facevo il
contrario
pippo=pippo.substring(10); na roba del genere insomma, e funzionava.
ciao
frolix8

 wrote in message
obtar8...@luke.casa.mattia...


diventa 10, quindi č modificabile.
E' proprio questo il punto: non e' cosi'. :)
PIPPO e' un reference ad un oggetto string, allocato, per dire, all'indirizzo 
X. Un puntatore, di cui pero' - a livello Java - non puoi sapere il valore; in 
altre parole e' a sua volta una variabile allocata, sempre per dire, 
all'indirizzo Y.
Quando scrivi PIPPO= PIPPO+PLUTO *non* modifichi l'oggetto all'indirizzo X, ma 
- crei un nuovo oggetto stringa ad un indirizzo Z, che incapsula "pippo pluto"
- riassegni PIPPO (cioe' il valore della memoria all'indirizzo Y) in modo che
punti ("si riferisca") a questo nuovo oggetto.
- l'oggetto a cui precedentemente PIPPO puntava (quello all'indirizzo X) non 
e' piu' accessibile tramite alcun reference: e' andato "fuori scope" - e quindi 
e' marcato garbage e suscettibile di garbage collection.
Come dicevo, in molte situazioni e' possibile non essere consapevoli di cio' 
(possibili ottimizzazioni escluse). Ma prova a costruire un metodo
 void modificaStringa(String s)
che modifichi s, in modo da poterlo chiamare con
 String s = "pippo";
 modificaStringa(s);
 System.out.println(s); // stringa modificata
In Java non lo puoi fare, proprio perche' String e' un carrier.


 StringBuffer vanno meglio.
Diciamo che se devi sommare tutte le getString(1) tra di loro conviene che g sia
uno stringbuffer, e fai g.append(rs.getString(1)); altrimenti se devi stampare
la stringa (1) e poi buttarla via, o comunque non modificarla più, conviene che
g sia una stringa (in quanto poi saresti costretto a chiamare il getString(),
esplicitamente o meno, per poter fare operazioni che non siano di append allo
stringbuffer, tipo appunto stamparlo ecc).


aiutare a chiarire le idee). 
In effetti il punto e' su quanto e' ottimizzabile a compiletime e quanto no.
Uno snippet come quello che hai testato comprende stringhe costanti, etc, e 
quindi e' ottimizzabile facilmente a compiletime: e infatti il compilatore 
standard, appunto, ottimizza. :)
Diverso il discorso per espressioni piu' complesse: ogni ottimizzatore ha un 
limite per l'analisi semantica oltre cui non va (per alcuni compilatori questo 
limite e' "0", ma questo e' solo un caso particolare :).
Ad esempio, per
 String line, s="";
 while((line=reader.readLine())!=null)
  s+=line;
javac 1.3 Sun/win32 (senza nessuno switch) genera
   0 ldc #2 <String "">
   2 astore_2
   3 goto 25
   6 new #3 <Class java.lang.StringBuffer>
   9 dup
  10 invokespecial #4 <Method java.lang.StringBuffer()>
  13 aload_2
  14 invokevirtual #5 <Method java.lang.StringBuffer append(java.lang.String)>
  17 aload_1
  18 invokevirtual #5 <Method java.lang.StringBuffer append(java.lang.String)>
  21 invokevirtual #6 <Method java.lang.String toString()>
  24 astore_2
  25 aload_0
  26 invokevirtual #7 <Method java.lang.String readLine()>
  29 dup
  30 astore_1
  31 ifnonnull 6
  34 return
mentre per
 String line;
 StringBuffer sb=new StringBuffer();
 while((line=reader.readLine())!=null)
  sb.append(line);
genera:
   0 new #3 <Class java.lang.StringBuffer>
   3 dup
   4 invokespecial #4 <Method java.lang.StringBuffer()>
   7 astore_2
   8 goto 17
  11 aload_2
  12 aload_1
  13 invokevirtual #5 <Method java.lang.StringBuffer append(java.lang.String)>
  16 pop
  17 aload_0
  18 invokevirtual #7 <Method java.lang.String readLine()>
  21 dup
  22 astore_1
  23 ifnonnull 11
  26 return
Con un'invocazione in meno di append, e uno StringBuffer in meno - 
l'ottimizzatore non "capisce" la semantica della definizione esterna al ciclo.
Se poi vogliamo migliorare ancora, e scriviamo
 StringBuffer sb=new StringBuffer();
 while(reader.ready()) 
   sb.append(reader.readLine());
otteniamo
   0 new #3 <Class java.lang.StringBuffer>
   3 dup
   4 invokespecial #4 <Method java.lang.StringBuffer()>
   7 astore_1
   8 goto 20
  11 aload_1
  12 aload_0
  13 invokevirtual #7 <Method java.lang.String readLine()>
  16 invokevirtual #5 <Method java.lang.StringBuffer append(java.lang.String)>
  19 pop
  20 aload_0
  21 invokevirtual #8 <Method boolean ready()>
  24 ifne 11
  27 return
Cioe' codice piu' o meno equivalente. Se il compilatore fosse completamente non 
ottimizzato, anche questi due ultimi spezzoni sarebbero diversi.


 standard, appunto, ottimizza. :)
Confesso che in effetti il mio esperimento era dato dalla mia necessità di
costruire a runtime delle query; e visto che la maggior parte delle volte che si
costruisce una query si sommano stringhe, lo scopo ultimo era scoprire se posso
scrivere
"SELECT * FROM " + tableBase + "_Tokens ..."
più o meno con le stesse prestazioni di
new StringBuffer("SELECT * FROM ").append(tableBase).append("_Tokens
...").toString()
visto che il primo modo è di gran lunga più leggibile... ;) E pare proprio di si.


,

Sent via Deja.com 
http://www.deja.com/
Before you buy.

proprio di si. 
Si', infatti (avevo dimenticato i complimenti per l'idea :).



azz..  mi tocca pormi dei problemi su almeno un paio di applet.
cosě a occhio devo aver creato per ogni esecuzione 2000*20 stringhe, tutto
perchč mi seccava usare stringtokenizer per leggere un file di 2000 records
per 20 campi
molte grazie per la spiegazione
frolix8

 Anche il numero di oggetti da garbage collect e' lo stesso?
Si, perchè se nel caso esplicito fai query = new
StringBuffer(...).toString(), quindi non tenendo riferimenti allo sb esso
viene immediatamente marcato come 'delendum' per il gc; nel caso implicito
"a" + "b", viene istanziato uno sb del quale tu non puoi salvare un
riferimento perchè creato nel bytecode, e quindi marcato subito dopo per
il gc.


 In Java non lo puoi fare, proprio perche' String e' un carrier.
E perchč le "handle" vngono passate per riferimento.
Simone