2017-08-11 63 views
6

W języku C# istnieją struktury i klasy. Struktury są zwykle (tj. Istnieją wyjątki) przydzielane do stosów, a klasy są zawsze przydzielane do sterty. Instancje klasy wywierają więc presję na GC i są uważane za "wolniejsze" niż struktury. Microsoft ma a best practice guide kiedy używać struktur ponad klasami. To mówi do rozważenia, jeśli struct:Czy istnieje dobra praktyka, gdy typ powinien być zapakowany?

  • To logicznie reprezentuje pojedynczą wartość, podobny do prymitywnych typów (int, podwójne, itp).
  • Ma rozmiar instancji poniżej 16 bajtów.
  • Jest niezmienny.
  • Nie musi być często zapakowany.

C# za pomocą przykładów struct, które są większe niż 16 bajty zwykle mówi się, że wykonują gorsze niż zbierane śmieci instancje klas (dynamicznie przydzielane).

Kiedy instancja pudełkowa (która jest przydzielana do sterty) ma lepszą pod względem szybkości niż niestandardowa instancja (która jest przydzielana do stosu)? Czy istnieje jakaś najlepsza praktyka dotycząca tego, kiedy powinniśmy dynamicznie alokować (na stercie) zamiast trzymać się domyślnej alokacji stosu?

Odpowiedz

6

TL; DR: zacznij bez boksu, a następnie profilu.


Stos Alokacja vs Boxed Alokacji

Jest to chyba bardziej jednoznaczne:

  • Kij do stosu,
  • Jeżeli wartość ta jest na tyle duża, że ​​byłoby dmuchać to.

Podczas semantycznie pisanie fn foo() -> Bar zakłada przeniesienie Bar z ramy odbierającego do ramy rozmówcy, w praktyce jest bardziej prawdopodobne, aby skończyć z równoważnikiem fn foo(__result: mut * Bar) podpisu gdzie dzwoniący przydziela miejsca na stosie i przechodzi wskaźnik do kanclerza.

To nie zawsze może być wystarczająca, aby zapobiec kopiowaniu, ponieważ niektóre wzory mogą uniemożliwić pisanie bezpośrednio w gnieździe powrotnej:

fn defeat_copy_elision() -> WithDrop { 
    let one = side_effectful(); 
    if side_effectful_too() { 
     one 
    } else { 
     side_effects_hurt() 
    } 
} 

Tu nie ma żadnej magii:

  • jeśli zastosowań kompilatora szczelinę zwrotną dla one, następnie w przypadku, gdy oddział oceni na false, musi przenieść one, a następnie utworzyć nowy WithDrop i ostatecznie zniszczyć one,
  • , jeśli kompilator utworzy na bieżącym stosie one i musi go zwrócić, to musi wykonać kopię.

Jeśli typ nie był potrzebny Drop, problem nie wystąpił.

Mimo tych dziwacznych przypadków, radzę trzymać się stosu, jeśli to możliwe, chyba że profilowanie ujawnia miejsce, w którym byłoby korzystne dla pudełka.


Inline Użytkownik lub pudełkowany Użytkownik

Ta sprawa jest znacznie bardziej skomplikowana:

  • wielkość struct/enum ma wpływu, a zatem zachowanie cache procesora wpływa:

    • mniej za stosowane równomiernie duże warianty są dobrym kandydatem do boksu (lub ich części bokserskich),
    • rzadziej dostępni wielcy członkowie są dobrym kandydatem do boksu.
  • w tym samym czasie, są koszty boksie:

    • to niezgodne z Copy typów, a pośrednio realizuje Drop (która, jak widać powyżej, wyłącza pewne optymalizacje)
    • alokację/zwalnianie pamięci ma nieograniczone opóźnienie: ,
    • uzyskiwanie dostępu do pamięci pudełkowej wprowadza zależność od danych: nie można określić, która linia pamięci podręcznej ma zażądać przed poznaniem adresu.

W rezultacie jest to bardzo dobrze balansowanie. Boksowanie lub rozpakowywanie członka może poprawić wydajność niektórych części kodu, zmniejszając przy tym wydajność innych.

Nie ma jednego uniwersalnego rozmiaru.

Tak więc, raz jeszcze, radzę unikać boksowania, dopóki profilowanie nie ujawni miejsca, w którym byłoby korzystne dla skrzynki.

Uważają, że na Linuksie, każda alokacja pamięci, dla których nie ma zapasowego pamięci w procesie może wymagać wywołania systemowego, który jeśli nie ma zapasowego pamięci w systemie operacyjnym może wywołać zabójcy OOM zabić proces, w którym to momencie pamięć zostaje uratowana i udostępniona. Prosta wersja malloc(1) może z łatwością wymagać milisekund.