Случай, когда stackalloc быстрее:
private static volatile int _dummy; // just to avoid any optimisations
// that have us measuring the wrong
// thing. Especially since the difference
// is more noticable in a release build
// (also more noticable on a multi-core
// machine than single- or dual-core).
static void Main(string[] args)
{
System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch();
Thread[] threads = new Thread[20];
sw1.Start();
for(int t = 0; t != 20; ++t)
{
threads[t] = new Thread(DoSA);
threads[t].Start();
}
for(int t = 0; t != 20; ++t)
threads[t].Join();
Console.WriteLine(sw1.ElapsedTicks);
System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch();
threads = new Thread[20];
sw2.Start();
for(int t = 0; t != 20; ++t)
{
threads[t] = new Thread(DoHA);
threads[t].Start();
}
for(int t = 0; t != 20; ++t)
threads[t].Join();
Console.WriteLine(sw2.ElapsedTicks);
Console.Read();
}
private static void DoSA()
{
Random rnd = new Random(1);
for(int i = 0; i != 100000; ++i)
StackAllocation(rnd);
}
static unsafe void StackAllocation(Random rnd)
{
int size = rnd.Next(1024, 131072);
int* p = stackalloc int[size];
_dummy = *(p + rnd.Next(0, size));
}
private static void DoHA()
{
Random rnd = new Random(1);
for(int i = 0; i != 100000; ++i)
HeapAllocation(rnd);
}
static void HeapAllocation(Random rnd)
{
int size = rnd.Next(1024, 131072);
int[] a = new int[size];
_dummy = a[rnd.Next(0, size)];
}
Важные различия между этим кодом и тем, что в вопросе:
У нас работает несколько потоков. При выделении стека они размещаются в своем собственном стеке. При выделении кучи они выделяются из кучи, совместно используемой другими потоками.
Выделены большие размеры.
Разные размеры выделяются каждый раз (хотя я посеял генератор случайных чисел, чтобы сделать тесты более детерминированными). Это повышает вероятность фрагментации кучи, делая распределение кучи менее эффективным, чем при одинаковых распределениях каждый раз.
Кроме того, стоит также отметить, что stackalloc
часто используется в качестве альтернативы использованию fixed
для закрепления массива в куче. Закрепление массивов плохо сказывается на производительности кучи (не только для этого кода, но и для других потоков, использующих ту же кучу), поэтому влияние на производительность будет еще выше, если заявленная память будет использоваться в течение любого разумного промежутка времени.
Хотя мой код демонстрирует случай, когда stackalloc
дает выигрыш в производительности, этот вопрос, вероятно, ближе к большинству случаев, когда кто-то может охотно «оптимизировать» его использование. Надеемся, что два фрагмента кода вместе показывают, что целое число stackalloc
может дать прирост, а также может сильно снизить производительность.
Как правило, вы даже не должны рассматривать stackalloc
, если только вам не понадобится использовать закрепленную память для взаимодействия с неуправляемым кодом в любом случае, и это следует рассматривать как альтернативу fixed
, а не альтернативу общему выделению кучи , Использование в этом случае все еще требует осторожности, предусмотрительности перед началом и профилирования после завершения.
Использование в других случаях может принести пользу, но оно должно быть далеко внизу списка улучшений производительности, которые вы бы попробовали.
Edit:
Чтобы ответить на часть 1 вопроса. Stackalloc концептуально очень много, как вы описываете. Он получает кусок памяти стека, а затем возвращает указатель на этот кусок. Он не проверяет, подходит ли память как таковая, но скорее если он попытается получить память в конец стека - который защищен .NET при создании потока - тогда это заставит ОС возвратить исключение во время выполнения , который затем превращается в управляемое исключение .NET. Примерно то же самое происходит, если вы просто выделяете один байт в методе с бесконечной рекурсией - если только вызов не был оптимизирован, чтобы избежать такого выделения стека (иногда это возможно), то один байт в конечном итоге будет суммироваться, чтобы вызвать исключение переполнения стека.