Практичне використання ключового слова `stackalloc`


134

Хтось насправді використовував stackallocпід час програмування на C #? Мені відомо про те, що робиться, але єдиний раз, коли він з’являється у моєму коді - випадково, тому що Intellisense пропонує це, коли я, наприклад, починаю вводити текст static.

Хоча це не пов’язано зі сценаріями використання stackalloc, я фактично роблю значну кількість застарілих інтеропів у своїх додатках, тому раз у раз я можу вдатися до використання unsafeкоду. Але все ж я зазвичай знаходжу способи уникнути unsafeповністю.

А оскільки розмір стека для одного потоку в .Net дорівнює ~ 1 Мб (виправте мене, якщо я помиляюся), я ще більше зарезервований від використання stackalloc.

Чи є якісь практичні випадки, коли можна сказати: "це саме потрібний обсяг даних і обробка для мене, щоб стати небезпечною та використовувати stackalloc"?


5
щойно помітив, що System.Numbersвикористовує його багато посилань.microsoft.com
mscorlib/

Відповіді:


150

Єдиною причиною використання stackallocє продуктивність (або для обчислень, або інтероп). Використовуючи stackallocзамість виділеного масиву масиви, ви створюєте менший тиск GC (GC потрібно запускати менше), вам не потрібно притискати масиви, швидше виділяти, ніж масив купи, він автоматично звільняється за методом вихід (купа виділених масивів розміщується лише тоді, коли працює GC). Крім того, використовуючи stackallocзамість власного алокатора (наприклад, malloc або еквівалент .Net), ви також отримуєте швидкість і автоматичне розташування при виході з області дії.

Ефективність роботи, якщо ви використовуєте, stackallocви значно збільшуєте шанс звернення до кешу на процесорі через локальність даних.


26
Місцевість даних, хороший момент! Ось чого керованої пам’яті буде рідко досягти, коли потрібно виділити кілька структур або масивів. Дякую!
Груо

22
Виділення купи зазвичай швидше для керованих об'єктів, ніж для некерованих, оскільки немає вільного списку, який можна пройти; CLR просто збільшує вказівник купи. Що стосується місцевості, послідовні розподіли швидше закінчуються колонізованими для тривалих керованих процесів через ущільнення купи.
Ши

1
"швидше виділити, ніж масив" Чому це? Просто місцевість? У будь-якому випадку це просто вказівний удар, ні?
Макс Барраклу

2
@MaxBarraclough Тому що ви додаєте вартість GC до купівлі асигнувань протягом життя програми. Загальна вартість розподілу = розподіл + розстановка, в цьому випадку вказівний удар + GC Heap, vs bump покажчика + зменшення покажчика Стек
Pop Catalin

35

Я використовував stackalloc для виділення буферів для роботи [DSP] у режимі реального часу. Це був дуже специфічний випадок, коли продуктивність повинна бути максимально послідовною. Зауважте, є різниця між послідовністю та загальною пропускною спроможністю - в цьому випадку я не переймався тим, що виділення купи надто повільні, просто не детермінізм збору сміття в той момент програми. Я б не використовував його в 99% випадків.


25

stackallocстосується лише небезпечного коду. Для керованого коду ви не можете вирішити, де розподілити дані. Типи значень розподіляються у стеку за замовчуванням (якщо вони не є частиною еталонного типу; у цьому випадку вони розподіляються на купі). Довідкові типи виділяються на купі.

За замовчуванням розмір стека для звичайного додатка ванільного .NET становить 1 Мб, але ви можете змінити це в заголовку PE. Якщо ви чітко запускаєте теми, ви також можете встановити інший розмір через перевантаження конструктора. Для програм ASP.NET розмір стека за замовчуванням становить лише 256 Кб, що потрібно пам’ятати, якщо ви переходите між двома середовищами.


Чи можна змінити стандартний розмір стека за допомогою Visual Studio?
конфігуратор

@configurator: Не наскільки я знаю.
Брайан Расмуссен

17

Стаціальна ініціалізація прольотів. У попередніх версіях C # результат stackalloc міг зберігатися лише в локальній змінній покажчика. Станом на C # 7.2, stackalloc тепер може використовуватися як частина виразу і може націлювати на проміжок, і це можна зробити без використання небезпечного ключового слова. Таким чином, замість того, щоб писати

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

Ви можете просто написати:

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

Це також надзвичайно корисно в ситуаціях, коли для виконання операції потрібно трохи місця подряпин, але ви хочете уникати розподілу пам'яті купи для порівняно невеликих розмірів

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

Джерело: C # - Все про Span: Дослідження нового .NET Mainstay


4
Дякую за пораду. Здається, кожна нова версія C # трохи наближається до C ++, що насправді є хорошою справою IMHO.
Гроо

1
Як можна побачити тут і тут , Spanна жаль , це не доступно в .NET Framework 4.7.2 і навіть не в 4.8 ... Отже, ця нова функція мови поки наразі обмежена.
Фредерік

2

У цьому питанні є кілька чудових відповідей, але я просто хочу це зазначити

Stackalloc також може використовуватися для виклику власних API

Для багатьох нативних функцій абонент вимагає виділити буфер, щоб отримати результат повернення. Наприклад, функція CfGetPlaceholderInfocfapi.h має наступний підпис.

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

Щоб зателефонувати в C # через interop,

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

Ви можете використовувати stackalloc.

byte* buffer = stackalloc byte[1024];
CfGetPlaceholderInfo(fileHandle, 0, buffer, 1024, out var returnedLength);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.