2015-08-04 21 views
12

Mam aplikację, która używa dużej ilości ciągów. Mam więc problem z wykorzystaniem pamięci. Wiem, że jednym z najlepszych rozwiązań w tym przypadku jest użycie DB, ale nie mogę tego teraz użyć, więc szukam innych rozwiązań.String VS Byte [], wykorzystanie pamięci

W ciągu C# są przechowywane w Utf16, co oznacza, że ​​straciłem połowę użycia pamięci w porównaniu do Utf8 (dla większej części moich ciągów). Postanowiłem więc użyć tablicy bajtowej ciągu utf8. Ale ku mojemu zaskoczeniu to rozwiązanie wymagało dwukrotnie więcej miejsca w pamięci niż proste napisy w mojej aplikacji.

Przeprowadziłem prosty test, ale chcę się dowiedzieć opinii ekspertów.

Test 1: przydział ciągów stała długość

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); 

Memory Usage

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

Jak widzimy, bajty tablice wziąć dwa razy mniej miejsca w pamięci, nie ma tu prawdziwą niespodziankę.

Test 2: Losowy przydział rozmiar string (realistyczny długości)

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); 

Memory Usage

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 trwa trochę mniej miejsca niż dwukrotność czasu przestrzeń pamięci tablicy bajtów . Przy krótszym łańcuchu spodziewałem się większego nakładu na struny. Ale wydaje się, że przeciwieństwem jest, dlaczego?

Test 3: model String odpowiadający mojej aplikacji

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); 

wykorzystanie pamięci

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

Tutaj ciągi wziąć dużo mniej miejsca w pamięci niż bajt. Może to być zaskakujące, ale przypuszczam, że ten pusty łańcuch odnosi się tylko raz. Czy to jest? Ale nie wiem, czy to może wyjaśnić całą ogromną różnicę. Czy to jakiś inny powód? Jakie jest najlepsze rozwiązanie?

Odpowiedz

5

Może to być zaskakujące, ale przypuszczam, że pusty łańcuch jest przywoływany tylko raz.

Tak, pusty StringBuilder zwraca string.Empty jako wynik. Fragment kodu poniżej wydruków True:

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

Ale ja nie wiem, czy to może wyjaśnić wszystko, ogromną różnicę.

Tak, to doskonale tłumaczy. Zapisujesz na 5000 obiektach string. Różnica w bajtach wynosi około 270 000 (198,000/2), czyli około 170 kilobajtów.Dzieląc przez 5, otrzymujesz 34 bajty na obiekt, który jest mniej więcej wielkością wskaźnika w systemie 32-bitowym.

Jakie jest najlepsze rozwiązanie?

Czy to samo: zrobić sobie private static readonly pusta tablica, i używać go za każdym razem, że masz string.Empty z sb.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

Dlaczego nie używać 'string.IsNullOrEmpty (stringArray [i])' ? –

+0

@MarkJansen To tylko ilustracja: wiem na pewno, że 'stringArray [i]' jest puste w gałęzi 'else' warunku' if (i% 2 == 0) ', więc mogłem pominąć porównanie z 'string.Empty' łącznie. – dasblinkenlight

+0

Interesujące, w rzeczy samej, użycie referencji pustej bajtów znacznie poprawia wykorzystanie pamięci. Zapomniałem powiedzieć w moim poście, niż byłem w 64 bitach, a jednostka to bajty. W każdym razie nie zmienia to idei twojego wyjaśnienia, nawet gdybym znalazł 34 bajty dla wskaźnika, to dużo (nawet więcej z 26 bajtowym narzutem każdego ciągu). Już straciłem rozmiar wskaźnika 10K (80032 KB, tj. 25% użytecznego rozmiaru pamięci) z System.Byte [] []. Czy istnieje sposób na uniknięcie użycia tak dużej ilości informacji? Może nie z tablicą bajtów. – Edeen

Powiązane problemy