2015-08-04 22 views
12

Ho un'applicazione che utilizza una grande quantità di stringhe. Quindi ho qualche problema di utilizzo della memoria. So che una delle migliori soluzioni in questo caso è usare un DB, ma non posso usarlo per il momento, quindi sto cercando altre soluzioni.String VS Byte [], utilizzo memoria

In stringa C# sono memorizzati in Utf16, ciò significa che ho perso metà dell'utilizzo della memoria rispetto a Utf8 (per la maggior parte delle mie stringhe). Quindi ho deciso di usare l'array di byte della stringa utf8. Ma con mia sorpresa questa soluzione ha avuto due volte più spazio di memoria rispetto alle semplici stringhe nella mia applicazione.

Quindi ho fatto qualche semplice test, ma voglio sapere l'opinione degli esperti per essere sicuro.

Test 1: allocazione stringhe di lunghezza fissa

var stringArray = new string[10000]; 
var byteArray = new byte[10000][]; 
var Sb = new StringBuilder(); 
var utf8 = Encoding.UTF8; 
var stringGen = new Random(561651); 
for (int i = 0; i < 10000; i++) { 
    for (int j = 0; j < 10000; j++) { 
     Sb.Append((stringGen.Next(90)+32).ToString()); 
    } 
    stringArray[i] = Sb.ToString(); 
    byteArray[i] = utf8.GetBytes(Sb.ToString()); 
    Sb.Clear(); 
} 
GC.Collect(); 
GC.WaitForFullGCComplete(5000); 

utilizzo della memoria

00007ffac200a510  1  80032 System.Byte[][] 
00007ffac1fd02b8  56  152400 System.Object[] 
000000bf7655fcf0  303  3933750  Free 
00007ffac1fd5738 10004 224695091 System.Byte[] 
00007ffac1fcfc40 10476 449178396 System.String 

Come possiamo vedere, i byte array prendono due volte meno spazio in memoria, nessuna vera sorpresa qui.

Test 2: Random allocazione stringa di formato (con una lunghezza realistica)

var stringArray = new string[10000]; 
var byteArray = new byte[10000][]; 
var Sb = new StringBuilder(); 
var utf8 = Encoding.UTF8; 
var lengthGen = new Random(2138784); 
for (int i = 0; i < 10000; i++) { 
    for (int j = 0; j < lengthGen.Next(100); j++) { 
     Sb.Append(i.ToString()); 
     stringArray[i] = Sb.ToString(); 
     byteArray[i] = utf8.GetBytes(Sb.ToString()); 
    } 
    Sb.Clear(); 
} 
GC.Collect(); 
GC.WaitForFullGCComplete(5000); 

utilizzo della memoria

00007ffac200a510  1  80032 System.Byte[][] 
000000be2aa8fd40  12  82784  Free 
00007ffac1fd02b8  56  152400 System.Object[] 
00007ffac1fd5738  9896  682260 System.Byte[] 
00007ffac1fcfc40 10368  1155110 System.String 

String prende un po 'meno spazio rispetto al tempo il doppio dello spazio di memoria di array di byte . Con una stringa più breve mi aspettavo un overhead maggiore per le stringhe. Ma sembra che sia il contrario, perché?

Test 3: modello di stringa corrispondente alla mia domanda

var stringArray = new string[10000]; 
var byteArray = new byte[10000][]; 
var Sb = new StringBuilder(); 
var utf8 = Encoding.UTF8; 
var lengthGen = new Random(); 
for (int i=0; i < 10000; i++) { 
    if (i%2 == 0) { 
     for (int j = 0; j < lengthGen.Next(100000); j++) { 
      Sb.Append(i.ToString()); 
      stringArray[i] = Sb.ToString(); 
      byteArray[i] = utf8.GetBytes(Sb.ToString()); 
      Sb.Clear(); 
     } 
    } else { 
     stringArray[i] = Sb.ToString(); 
     byteArray[i] = utf8.GetBytes(Sb.ToString()); 
     Sb.Clear(); 
    } 
} 
GC.Collect(); 
GC.WaitForFullGCComplete(5000); 

utilizzo della memoria

00007ffac200a510  1  80032 System.Byte[][] 
00007ffac1fd02b8  56  152400 System.Object[] 
00007ffac1fcfc40  5476  198364 System.String 
00007ffac1fd5738 10004  270075 System.Byte[] 

Qui stringhe prendono molto meno spazio di memoria di byte. Questo può essere sorprendente, ma suppongo che la stringa vuota sia riferita solo una volta. È? Ma non so se questo può spiegare tutta questa enorme differenza. È qualche altra ragione? Qual è la soluzione migliore?

risposta

5

Questo può essere sorprendente, ma suppongo che la stringa vuota sia riferita una sola volta.

Sì, un vuoto StringBuilder rendimenti string.Empty come risultato. Il frammento di codice di seguito stampe True:

var sb = new StringBuilder(); 
Console.WriteLine(object.ReferenceEquals(sb.ToString(), string.Empty)); 

Ma io non so se questo può spiegare tutto ciò che differenza enorme.

Sì, questo lo spiega perfettamente. Stai salvando su 5.000 oggetti string. La differenza di byte è di circa 270.000 (198.000/2), quindi circa 170 kByte.Dividendo per 5 ottieni 34 byte per oggetto, che è grosso modo la dimensione di un puntatore su un sistema a 32 bit.

Qual è la soluzione migliore?

Fate la stessa cosa: farsi un private static readonly array vuoto, e usarlo ogni volta che si ottiene da string.Emptysb.ToString():

private static readonly EmptyBytes = new byte[0]; 
... 
else 
{ 
    stringArray[i] = Sb.ToString(); 
    if (stringArray[i] == string.Empty) { 
     byteArray[i] = EmptyBytes; 
    } else { 
     byteArray[i] = utf8.GetBytes(Sb.ToString()); 
    } 
    Sb.Clear(); 
} 
+0

Perché non usare 'String.IsNullOrEmpty (StringArray [i])' ? –

+0

@MarkJansen È solo un'illustrazione: so per certo che 'stringArray [i]' è vuoto nel ramo 'else' del condizionale' if (i% 2 == 0) ', quindi avrei potuto saltare il confronto con 'string.Empty' del tutto. – dasblinkenlight

+0

Interessante, infatti, utilizzare un riferimento a byte vuoto per migliorare l'utilizzo della memoria molto. Ho dimenticato di dire nel mio post, che ero a 64 bit e l'unità è byte. Comunque questo non cambia l'idea della tua spiegazione, anche se ho trovato che 34 byte per un puntatore sono molti, (anche di più con i 26 byte di ogni stringa). Ho già perso la dimensione del puntatore 10K (80032 KB ovvero il 25% della dimensione della memoria utile) con System.Byte [] []. C'è un modo per evitare l'uso di così tanto indirezione? Forse non con la matrice di byte. – Edeen