2014-10-23 20 views
5

Mam klienta sieciowego, który przetwarza dane z serwera.Używanie stringowania w celu zmniejszenia użycia pamięci przez klienta sieciowego

Dane są wysyłane jako seria wiadomości, które same są kolekcjami klucz/wartość, podobnie jak koncept do nagłówków HTTP (z wyjątkiem braku "treści wiadomości"), tutaj jest typowy komunikat jednokierunkowy (linie oddzielone \r\n):

Response: OK 
Channel: 123 
Status: OK 
Message: Spectrum is green 
Author: Gerry Anderson 
Foo123: Blargh 

Moje prace klienckie protokołu przez odczyt z NetworkStream, znak po znaku za pomocą StreamReader i while((nc = rdr.Read()) != -1) i wykorzystuje parser najnowocześniejsze maszyny i StringBuilder wystąpienie do wypełnienia Dictionary<String,String> instancji. Te instancje słownika są następnie zapisywane w strukturach w pamięci w celu dalszego przetwarzania, z reguły mają one przydatny czas życia około 10 minut każdy.

Mój klient otrzymuje tysiące takich wiadomości na godzinę, a proces klienta trwa długo - jest to problem, ponieważ mój proces klienta często zużywa ponad 2 GB pamięci z tych instancji String - użyłem windbg, aby zobaczyć gdzie Całe wspomnienie się skończyło. Jest to problem, ponieważ kod działa na maszynie wirtualnej Azure z tylko 3,5 GB pamięci. Nie widzę powodu, dla którego mój program powinien zużywać co najwyżej kilkaset MB pamięci RAM. Często umieszczam maszynę wirtualną i obserwuję zużycie pamięci mojego procesu w czasie i stopniowo wzrośnie ona do około 2 GB, a następnie nagle spadnie do około 100 MB, gdy GC zacznie pobierać kolekcję, a następnie wzrośnie ponownie. Czasy mogą się różnić między seriami GC, bez żadnej przewidywalności.

Ponieważ tak wiele z tych ciągów są identyczne (takie jak klucze Response, Status, etc), jak również znanych wartościach jak OK i Fail można używać internowanie łańcuchów w celu zmniejszenia zużycia, tak:

// In the state-machine parser after having read a Key name: 

String key = stringBuilder.ToString(); 
key = String.Intern(key); 

// etc... after reading value 
messageDictionary.Add(key, value); 

Problem polega na tym, że widzę miejsce na dodatkową optymalizację: sb.ToString() zamierza przydzielić nową instancję napisów, która będzie używana do interwencji, a po drugie: internowane ciągi będą trwać przez całe życie aplikacji i niestety niektóre klucze wygrały " t zobacz ponownie użyć i faktycznie marnować pamięć, takie jak Foo123 w moim przykładzie protokołu. Jednym z rozwiązań, o którym myślałem, że nie jest użycie interakcji z ciągiem znaków, a zamiast tego jest klasa zawierająca static readonly pól tekstowych, które są znanymi kluczami, a następnie używają normalnych, nie internowanych ciągów znaków - które ostatecznie mogłyby zostać wygenerowane GC, więc nie ryzykują wypełnienia do puli strun strun z jednostajnymi ciągami. Chciałbym następnie porównać instancję StringBuilder z tymi znanymi ciągami, a jeśli tak, użyć ich zamiast wywoływać sb.ToString(), pomijając w ten sposób kolejną alokację napisów.

Jednakże, jeśli zdecyduję się na internowanie każdego ciągu, puli intern będzie nadal rosła, i niestety .NET nie wydaje się mieć metoda .Chlorinate() dla puli ciągów, czy istnieje sposób, aby usunąć jednorazowego użytku ciągi z puli intern, jeśli kontynuuję z podejściem String.Intern, czy raczej korzystam z własnych statycznych instancji tylko do odczytu?

+0

Może można symulować zachowanie tabeli referencyjnej bazy danych, używając jednego słownika z nazwą klucza i identyfikatorem, a drugiego z identyfikatorem i wartością? W ten sposób możesz naprawdę usunąć klucze na zawsze. – nvoigt

+0

Użyj własnego interningu w razie potrzeby - nie ma sposobu na "unintern" napisów internowanych przez 'string.Intern'. Używanie odnośników do książek kodowych jest również dobrym sposobem na obniżenie zużycia pamięci. Zwróć uwagę, że stałe łańcuchowe są automatycznie internowane. – Luaan

+2

Nie, interning nie jest rozwiązaniem tego problemu. I tak, zużycie pamięci powinno wynosić megabajty, a nie gigabajty. W twoim programie jest coś poważnie nie tak, trudno odgadnąć, co to może być. Nigdy nie rozprawiaj się z poważnymi problemami. Punktem wyjścia jest spojrzenie na kolekcje GC z Perfmon.exe, przyzwoity profiler pamięci jest lepszy. Skup się na utkniętym wątku finalizera lub przypiętych obiektach, będziesz chciał dowiedzieć się więcej o "asynchronicznych przypiętych uchwytach". –

Odpowiedz

2

Internowanie nie pomoże tutaj, z powodów, które zacytowałeś. W rzeczywistości pogorszyłoby to sytuację, ponieważ internowane ciągi nie są już przedmiotem zbierania śmieci. I nie, nie ma metody usuwania internowanych łańcuchów z puli.

Opisałeś, że GC robi dokładnie to samo, co GC, więc nie jest dla mnie jasne, że masz problem. Przyjmowanie interningów oznaczałoby handel odpadami (co nie jest problemem) dla ciągle rosnącego zapotrzebowania na pamięć (co stanowi problem).

Jeśli obawiasz się, że GC nie działa wystarczająco często, aby utrzymać zużycie pamięci poniżej pewnego progu, który masz na myśli, możesz rozważyć monitorowanie użycia pamięci i wywołanie metody GC.Collect() po osiągnięciu tego progu.

Jeśli wzór zachowania GC rzeczywiście powoduje problem (poza wyglądem dziwnym), możesz spróbować przełączyć się z domyślnego trybu GC "stacja robocza" na tryb "serwer" GC, ponieważ są one dostrojone inaczej. (Ale znowu, nie jestem wcale przekonany, że rzeczywiście mają problem.)

Niektóre z tych różnic są objęte w tych dwóch stron:

http://msdn.microsoft.com/en-us/library/ee787088(v=vs.110).aspx#workstation_and_server_garbage_collection

http://blogs.msdn.com/b/dotnet/archive/2012/07/20/the-net-framework-4-5-includes-new-garbage-collector-enhancements-for-client-and-server-apps.aspx

Należy jednak zauważyć, że rzeczywiste różnice zmieniają się wraz z każdą wersją frameworka, ponieważ osoby odpowiedzialne za te rzeczy nieustannie się uczą i dokonują ulepszeń.

a tryb GC jest regulowane przez app config:

http://msdn.microsoft.com/en-us/library/cc165011(v=office.11).aspx

<configuration 
    <runtime> 
     <gcServer enabled="true"/> 
    </runtime> 
</configuration> 

Można również wybrać ten poradnik rozwiązywania problemów użyteczne, albo przynajmniej interesujący:

http://msdn.microsoft.com/en-us/library/ee851764(v=vs.110).aspx#Issue_TooMuchMemory

+0

Po kilku kopaniu znalazłem faktyczną prawdziwą przyczynę użycia dużej pamięci: był kod przechowujący każdy znak otrzymany z połączenia w jednej instancji 'StringBuilder' (która zakończyła się posiadaniem łańcucha o rozmiarze gigabajta). Nie znaczy to, że sugestie w tej kontroli jakości również nie pomogły, zrobiły to! – Dai

Powiązane problemy