Autore: Michele
Locuratolo
Mi capita spesso che mi venga richiesto come fare per migliorare le
performance di una applicazione e, altrettanto spesso, scopro che viene usato il
semplice concatenamento di stringhe per comporre dei messaggi. Si va dal
semplice messaggio di poche parole (che concatena 4 o 5 stringhe) a veri e
propri testi.
Questo modo di fare causa spesso problemi di performance.
Scopriamo insieme il perchè.
Usiamo allo scopo questo semplice codice:
1 string myString = string.Empty;
2 for (int i = 0; i < 10000; i++) {
3 myString += i.ToString();
4 }
Il funzionamento è estremamente semplice: la nostra stringa sarà il risultato
del concatenamento di 10.000 numeri.
Internamente però, questa operazione è
decisamente pesante. Partendo dal concetto che una stringa è un oggetto
immutabile e che, ad ogni modifica di essa, ne deve essere creata una copia che
contiene il nuovo valore, non è difficile immaginare cosa accade all'interno del
nostro ciclo.
Al primo "giro", verrà creata una nuova stringa che conterrà il
valore precedente (string.Empty)
ed il nuovo (0). Al secondo cliclo, ancora una volta, verrà creata una nuova
stringa che contenente il valore precedente ed il nuovo (1) e così via. Ne
consegue che, per 10.000 cicli, verranno create 10.000 stringhe! Tutte le
stringhe precedenti vengono poi eliminate dal Garbage Collector.
Se mandiamo
in esecuzione il codice di sopra aggiungendo un paio di timer, otterremo il
seguente risultato:
Avvio ciclo di concatenamento
Tempo impiegato:
00:00:00.5107344
Praticamente 1/2 secondo per concatenare 10.000 stringhe. Apparentemente
potrebbe sembrare poco ma vediamo cosa accade usando un oggetto realizzato
apposta per questo scopo: lo StringBuilder:
1 StringBuilder sb = new StringBuilder(10000);
2 for (int i = 0; i < 10000; i++) {
3 sb.Append(i.ToString());
4 }
Grazie a questo oggetto, il concatenamento non avviene più creando
una copia della stringa. StringBuilder contiene un vettore di caratteri ed
il concatenamento avviene direttamente sul vettore evitando inutili copie
di oggetti. Se mandiamo in esecuzione il codice
otterremo:
Avvio ciclo con StringBuilder
Tempo impiegato:
00:00:00.0100144
50 volte più veloce! Ma la velocità è solo la minima
conseguenza. Se usiamo un tool come CLR Profiler (esiste sia
per la versione 1.1 che 2.0 del framework), possiamo
vedere quanto sia più pesante da eseguire il primo codice rispetto al secondo.
Di seguito un report:
| |
Concatenamento |
StringBuilder |
Allocated bytes: Relocated
bytes: Final Heap bytes: Objects finalized: Critical objects
finalized: Gen 0 collections: Gen 1 collections: Gen 2
collections: Induced collections: Gen 0 Heap bytes: Gen 1 Heap
bytes: Gen 2 Heap bytes: Large Object Heap bytes: Handles
created: Handles destroyed: Handles surviving: Heap
Dumps: Comments: |
379.311.360 20.536.640 1.575.328 0 0 449 47 0 0 847.516 390.678 12 8.784 27 0 27 0 0
|
544.756 0 544.756 0 0 0 0 0 0 Unknown Unknown Unknown Unknown 27 0 27 0 0
|
Come è evidente, tanto la memoria allocata quanto il lavoro fatto dal Garbage
Collector sono notevolmente ridotti dallo StringBuilder.
Personalmente, se
devo concatenare più di 3 stringhe, uso il secondo codice. Fino a 3, non c'è
quasi differenza tra i 2 sistemi.