Инициализация памяти, выделенной с помощью stackalloc
Если я выделяю память stackalloc
в C# эта память инициализирована (с 0
)
Документация не говорит об этом, а только говорит о том, что зарезервировано правильное количество.
В моих тестах память по умолчанию 0
, но это не значит, что это гарантировано.
2 ответа
Из спецификации:
18.8 Распределение стека
Содержимое вновь выделенной памяти не определено.
Да, спецификация говорит, что она не определена, но компилятор выдает localloc
CIL внедрение для stackalloc
, И это то, что ECMA Specs говорит о localloc
:
Инструкция localloc выделяет размер (тип native unsigned int) байтов из локального динамического пула памяти и возвращает адрес (управляемый указатель, тип &) первого выделенного байта. Возвращенный блок памяти инициализируется равным 0, только если флаг инициализации метода имеет значение true (см. Раздел I). Область памяти вновь выделена. Когда текущий метод возвращает локальный пул памяти, доступен для повторного использования.
Флаг инициализации, также известный как localsinit
flag, испускается компилятором для каждого метода, потому что он необходим для проверяемого кода.
Пожалуйста, посмотрите на эту проблему на coreclr с просьбой прекратить обнуление памяти в stackalloc. В конце выпуска jkotas говорит:
Текущий план:
C# будет держать ноль инициализации по умолчанию. Изменение значения по умолчанию будет слишком разрушительным. У нас есть ряд вопросов, открытых для того, чтобы JIT-инициализация, выполненная JIT, была более эффективной или уменьшить потребность в ней (#13827, #13823, #13825). Люди, которые действительно хотят получить последний бит производительности, избегая нулевой инициализации, могут использовать пользовательский шаг ILLinker (моно / линкер #159), когда они знают, что делают. Мы делаем это для CoreLib сегодня (через взлом VM, но мы должны переключиться на ILLinker), и мы планируем поэкспериментировать с этим в CoreFX (dotnet/corefx#25956). Основываясь на результатах этих экспериментов, мы можем рассмотреть возможность введения более обтекаемого способа сделать это в будущем. @ahsonkhan Вам следует также поэкспериментировать с ним в CoreFXLab, если вы считаете, что это поможет.
И увидеть это предложение csharplang
Итак, вывод таков: на практике память инициализируется нулями
Однако в компиляторе есть ошибка / функция, которая препятствует генерации localsinit
флаг. Небезопасные методы, которые не объявляют другие переменные и используют выделенную переменную стека только для передачи ее другому методу, не помечаются localsinit
флаг.
Вот пример такой ошибки / функции:
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace InformalTests
{
class Program
{
const int n = 100_000_000;
static unsafe void Main(string[] args)
{
var watch = Stopwatch.StartNew();
for (int i =0; i < n; i++)
{
ThisMethodDoes_NOT_InitializeStackAllocatedMemory();
}
watch.Stop();
Console.WriteLine($"NOT INITIALIZED elapsed time {watch.Elapsed}");
watch.Restart();
for (int i = 0; i < n; i++)
{
ThisMethodInitializeStackAllocatedMemory();
}
watch.Stop();
Console.WriteLine($"INITIALIZED Elapsed time {watch.Elapsed}");
}
private static unsafe string ThisMethodDoes_NOT_InitializeStackAllocatedMemory()
{
// avoid declaring other local vars, or doing work with stackalloc
// to prevent the .locals init cil flag , see: https://github.com/dotnet/coreclr/issues/1279
char* pointer = stackalloc char[256];
return CreateString(pointer, 256);
}
private static unsafe string ThisMethodInitializeStackAllocatedMemory()
{
//Declaring a variable other than the stackallocated, causes
//compiler to emit .locals int cil flag, so it's slower
int i = 256;
char* pointer = stackalloc char[256];
return CreateString(pointer, i);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe string CreateString(char* pointer, int length)
{
return "";
}
}
}
Неинициализированный метод работает в пять раз быстрее, чем инициализированный.