Praktyczne użycie słowa kluczowego `stackalloc`

134

Czy ktoś kiedykolwiek faktycznie używał stackallocpodczas programowania w C #? Zdaję sobie sprawę z tego, co się dzieje, ale jedyny raz, kiedy pojawia się to w moim kodzie, to przypadek, bo Intellisense podpowiada to np. Gdy zaczynam pisać static.

Chociaż nie jest to związane ze scenariuszami użytkowania programu stackalloc, w rzeczywistości wykonuję znaczną ilość starszej współpracy w moich aplikacjach, więc od czasu do czasu mogę uciekać się do używania unsafekodu. Niemniej jednak zazwyczaj znajduję sposoby, aby unsafecałkowicie tego uniknąć .

A ponieważ rozmiar stosu dla pojedynczego wątku w .Net wynosi ~ 1 MB (popraw mnie, jeśli się mylę), jestem jeszcze bardziej ograniczony do używania stackalloc.

Czy są jakieś praktyczne przypadki, w których można by powiedzieć: „jest to dokładnie taka ilość danych i przetwarzania, które są dla mnie niebezpieczne i używane stackalloc”?

Groo
źródło
5
właśnie zauważyłem, że System.Numbersużywa go dużo referenceource.microsoft.com/#mscorlib/system/…
Slai

Odpowiedzi:

150

Jedynym powodem użycia stackallocjest wydajność (do obliczeń lub międzyoperacyjności). Używając stackalloczamiast tablicy przydzielonej na sterty, tworzysz mniejszy nacisk GC (GC musi działać mniej), nie musisz przypinać tablic, alokacja jest szybsza niż tablica sterty, a metoda jest automatycznie zwalniana exit (tablice przydzielone do sterty są zwalniane tylko po uruchomieniu GC). Ponadto, używając stackalloczamiast natywnego alokatora (takiego jak malloc lub odpowiednik .Net), zyskujesz również prędkość i automatyczne cofanie alokacji przy wyjściu z zakresu.

Jeśli chodzi o wydajność, jeśli używasz stackallocciebie, znacznie zwiększasz prawdopodobieństwo trafienia pamięci podręcznej do procesora ze względu na lokalizację danych.

Pop Catalin
źródło
26
Lokalizacja danych, słuszna uwaga! Właśnie to rzadko osiąga pamięć zarządzana, gdy chcesz przydzielić kilka struktur lub tablic. Dzięki!
Groo
22
Alokacje sterty są zwykle szybsze w przypadku obiektów zarządzanych niż w przypadku obiektów niezarządzanych, ponieważ nie ma wolnej listy do przechodzenia; CLR tylko zwiększa wskaźnik sterty. Jeśli chodzi o lokalizację, alokacje sekwencyjne z większym prawdopodobieństwem będą znajdować się w kolokacji dla długotrwałych zarządzanych procesów z powodu kompaktowania sterty.
Shea
1
„alokacja jest szybsza niż tablica sterty” Dlaczego tak jest? Tylko lokalizacja? Tak czy inaczej to tylko uderzenie wskaźnika, nie?
Max Barraclough
2
@MaxBarraclough, ponieważ koszt GC jest dodawany do alokacji sterty w okresie istnienia aplikacji. Całkowity koszt alokacji = alokacja + cofnięcie alokacji, w tym przypadku bump wskaźnika + GC Heap, vs bump wskaźnika + dekrementacja wskaźnika Stos
Pop Catalin
35

Użyłem stackalloc do przydzielenia buforów do pracy DSP [prawie] w czasie rzeczywistym. To był bardzo specyficzny przypadek, w którym wydajność musiała być jak najbardziej spójna. Zauważ, że istnieje różnica między spójnością a ogólną przepustowością - w tym przypadku nie przejmowałem się zbyt wolnym alokowaniem sterty, tylko brak determinizmu czyszczenia pamięci w tym punkcie programu. Nie użyłbym go w 99% przypadków.

Jim Arnold
źródło
25

stackallocdotyczy tylko niebezpiecznego kodu. W przypadku kodu zarządzanego nie możesz zdecydować, gdzie przydzielić dane. Typy wartości są domyślnie przydzielane na stosie (chyba że są częścią typu referencyjnego, w którym to przypadku są przydzielane na stercie). Typy odwołań są przydzielane na stercie.

Domyślny rozmiar stosu dla zwykłej waniliowej aplikacji .NET to 1 MB, ale można to zmienić w nagłówku PE. Jeśli jawnie uruchamiasz wątki, możesz również ustawić inny rozmiar za pomocą przeciążenia konstruktora. W przypadku aplikacji ASP.NET domyślny rozmiar stosu to tylko 256 KB, o czym należy pamiętać, jeśli przełączasz się między dwoma środowiskami.

Brian Rasmussen
źródło
Czy można zmienić domyślny rozmiar stosu z programu Visual Studio?
konfigurator
@konfigurator: O ile mi wiadomo, nie.
Brian Rasmussen
17

Inicjalizacja spansów metodą Stackalloc. W poprzednich wersjach języka C # wynik stackalloc mógł być przechowywany tylko w zmiennej lokalnej wskaźnika. Począwszy od C # 7.2, stackalloc może być teraz używany jako część wyrażenia i może być przeznaczony dla zakresu, a można to zrobić bez użycia niebezpiecznego słowa kluczowego. Tak więc zamiast pisać

Span<byte> bytes;
unsafe
{
  byte* tmp = stackalloc byte[length];
  bytes = new Span<byte>(tmp, length);
}

Możesz po prostu napisać:

Span<byte> bytes = stackalloc byte[length];

Jest to również niezwykle przydatne w sytuacjach, w których potrzebujesz trochę miejsca na zarysowania do wykonania operacji, ale chcesz uniknąć przydzielania pamięci sterty dla stosunkowo małych rozmiarów

Span<byte> bytes = length <= 128 ? stackalloc byte[length] : new byte[length];
... // Code that operates on the Span<byte>

Źródło: C # - All About Span: Exploring a New .NET Mainstay

anth
źródło
4
Dzięki za wskazówkę. Wygląda na to, że każda nowa wersja C # zbliża się nieco do C ++, co jest w rzeczywistości dobrą rzeczą IMHO.
Groo,
1
Jak widać tutaj i tutaj , Spanniestety nie jest dostępny w .NET framework 4.7.2, a nawet nie w 4.8 ... Tak więc nowa funkcja językowa jest w tej chwili ograniczona.
Frederic
2

Jest kilka świetnych odpowiedzi na to pytanie, ale chcę tylko na to zwrócić uwagę

Stackalloc może również służyć do wywoływania natywnych interfejsów API

Wiele funkcji natywnych wymaga, aby obiekt wywołujący przydzielił bufor w celu uzyskania zwracanego wyniku. Na przykład funkcja CfGetPlaceholderInfo w cfapi.hma następujący podpis.

HRESULT CfGetPlaceholderInfo(
HANDLE                    FileHandle,
CF_PLACEHOLDER_INFO_CLASS InfoClass,
PVOID                     InfoBuffer,
DWORD                     InfoBufferLength,
PDWORD                    ReturnedLength);

Aby wywołać to w C # przez interop,

[DllImport("Cfapi.dll")]
public static unsafe extern HResult CfGetPlaceholderInfo(IntPtr fileHandle, uint infoClass, void* infoBuffer, uint infoBufferLength, out uint returnedLength);

Możesz skorzystać z stackalloc.

byte* buffer = stackalloc byte[1024];
CfGetPlaceholderInfo(fileHandle, 0, buffer, 1024, out var returnedLength);
fjch1997
źródło