Практическое использование ключевого слова stackalloc

134

Кто-нибудь когда-либо использовал stackallocво время программирования на C #? Я знаю о том, что делает, но единственный раз, когда он появляется в моем коде, случайно, потому что Intellisense предлагает это, когда я static, например, начинаю печатать .

Хотя это не связано со сценариями использования stackalloc, я на самом деле выполняю значительное количество устаревших взаимодействий в своих приложениях, поэтому время от времени я мог прибегнуть к использованию unsafeкода. Но тем не менее я обычно нахожу способы unsafeполностью избежать .

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

Существуют ли практические случаи, когда можно было бы сказать: «это именно тот объем данных и обработки, которые мне небезопасны и используются stackalloc»?

Гру
источник
5
только что заметил, что System.Numbersиспользует его много источниковsource.microsoft.com
mscorlib/system/…

Ответы:

150

Единственная причина для использования stackalloc- производительность (для вычислений или взаимодействия). Используя stackallocвместо выделенного массива кучи, вы создаете меньшее давление GC (GC должен работать меньше), вам не нужно закреплять массивы вниз, он быстрее выделяется, чем массив кучи, и он автоматически освобождается в методе. выход (выделенные в куче массивы освобождаются только при запуске GC). Кроме того, используя stackallocвместо собственного распределителя (например, malloc или .Net-эквивалент), вы также получаете скорость и автоматическое освобождение при выходе из области.

С точки зрения производительности, если вы используете его, stackallocвы значительно увеличите вероятность попадания кэша в процессор из-за локальности данных.

Поп-каталин
источник
26
Местонахождение данных, хорошая точка! Это то, чего редко удается достичь управляемой памяти, если вы хотите выделить несколько структур или массивов. Спасибо!
Groo
22
Распределение кучи обычно быстрее для управляемых объектов, чем для неуправляемых, потому что нет свободного списка для прохождения; CLR просто увеличивает указатель кучи. Что касается локальности, то последовательное распределение с большей вероятностью может оказаться колокейшн для долго управляемых управляемых процессов из-за сжатия кучи.
Shea
1
«это быстрее выделить, чем массив кучи» Почему это? Просто местность? В любом случае, это просто удар указателем, нет?
Макс Барракло
2
@MaxBarraclough Потому что вы добавляете стоимость GC к выделению кучи в течение времени жизни приложения. Общая стоимость распределения = распределение + освобождение, в данном случае увеличение указателя + кучи GC, против увеличения указателя + уменьшение указателя Стек
Pop Catalin
35

Я использовал stackalloc для выделения буферов для работы DSP в реальном времени. Это был очень специфический случай, когда производительность должна была быть максимально последовательной. Обратите внимание, что есть разница между согласованностью и общей пропускной способностью - в этом случае я не был обеспокоен слишком медленным распределением кучи, просто недетерминированностью сборки мусора в этой точке программы. Я бы не использовал его в 99% случаев.

Джим Арнольд
источник
25

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

Размер стека по умолчанию для простого ванильного приложения .NET составляет 1 МБ, но вы можете изменить это в PE-заголовке. Если вы запускаете потоки явно, вы также можете установить другой размер через перегрузку конструктора. Для приложений ASP.NET размер стека по умолчанию составляет всего 256 КБ, что следует учитывать при переключении между двумя средами.

Брайан Расмуссен
источник
Можно ли изменить размер стека по умолчанию из Visual Studio?
конфигуратор
@configurator: не так, как я знаю.
Брайан Расмуссен
17

Stackalloc инициализация пролетов. В предыдущих версиях C # результат stackalloc мог быть сохранен только в локальной переменной указателя. Начиная с C # 7.2, stackalloc теперь может использоваться как часть выражения и может предназначаться для диапазона, и это может быть сделано без использования ключевого слова unsafe. Таким образом, вместо записи

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 # - All About Span: Изучение нового .NET Mainstay

Anth
источник
4
Спасибо за чаевые. Кажется, что каждая новая версия C # становится немного ближе к C ++, что, на самом деле, неплохо.
Groo
1
Как можно видеть здесь и здесь , Spanувы, нет в .NET Framework 4.7.2 и даже не в 4.8 ... Так что новая языковая функция все еще имеет ограниченное применение в настоящее время.
Фредерик
2

На этот вопрос есть несколько отличных ответов, но я просто хочу отметить, что

Stackalloc также можно использовать для вызова собственных API

Многие нативные функции требуют, чтобы вызывающая сторона выделяла буфер для получения возвращаемого результата. Например, функция CfGetPlaceholderInfo в cfapi.hимеет следующую подпись.

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

Чтобы вызвать его в C # через взаимодействие,

[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);
fjch1997
источник