Да, спецификация говорит, что она не определена, но компилятор испускает localloc
CIL intruction для stackalloc
.И это то, что ECMA Specs говорит о localloc
:
Команда localloc выделяет размер (тип native unsigned int) байтов из пула локальной динамической памяти и возвращает адрес (управляемый указатель, тип &) первого выделенного байта.Возвращенный блок памяти инициализируется равным 0, только если флаг инициализации метода имеет значение true (см. Раздел I).Область памяти вновь выделена.Когда текущий метод возвращает локальный пул памяти, доступен для повторного использования.
Флаг инициализации, также известный как флаг localsinit
, генерируется компилятором для каждого метода, поскольку он требуется для проверяемого кода..
Пожалуйста, посмотрите на эту проблему на 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 "";
}
}
}
Метод без инициализации в пять раз быстрее, чем инициализированный один.