使用stackalloc分配内存的初始化

16
如果我在C#中使用stackalloc分配内存,那么这段内存是否会被初始化(使用0?文档没有说明,只是说保留了正确的数量。
在我的测试中,这种内存默认为0,但并不意味着它是有保证的。
2个回答

17

从规范中可以看出:

18.8 栈内存分配

新分配的内存内容是未定义的。


3
现在,使用Span<T>的“安全”上下文可以使用stackalloc吗?这是否仍然成立? - JBeurer

9

是的,规范说它是未定义的,但编译器会为stackalloc发出localloc CIL指令。这就是ECMA规范关于localloc的说法:

localloc指令从本地动态内存池中分配大小(类型本机无符号整数)字节,并返回第一个分配字节的地址(托管指针,类型&)。返回的内存块仅在方法上的initialize标志为true(请参见Partition I)时初始化为0。内存区域是新分配的。当当前方法返回时,本地内存池可供重用。

编译器为每个方法发出initialize标志,也称为localsinit标志,因为对于可验证的代码是必需的。

请查看coreclr上的this issue,要求停止在stackalloc上清零内存。在问题的末尾,jkotas说:

当前的计划是: C#默认保持零初始化。更改默认设置会破坏太多东西。我们开了一系列问题,以使JIT执行的零初始化更加高效或减少对它的需求(#13827,#13823,#13825)。真正想通过避免零初始化获得最后一点性能的人可以在知道自己在做什么时使用自定义ILLinker步骤(mono/linker#159)。我们今天为CoreLib这样做(通过VM hack,但我们应该切换到ILLinker),并计划在CoreFX(dotnet/corefx#25956)中尝试此功能。根据这些实验的结果,我们可能会考虑在未来引入更简化的方法。如果您认为这将有所帮助,则应在CoreFXLab中尝试进行实验。@ahsonkhan 请参阅此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 "";
        }
    }
}

未初始化方法比已初始化方法快五倍。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接