Инициализация памяти, выделенной с помощью 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 "";
        }
    }
}

Неинициализированный метод работает в пять раз быстрее, чем инициализированный.

Другие вопросы по тегам