Initialization of memory allocated with stackalloc
Asked Answered
T

2

16

If I'm allocating memory with stackalloc in C#, is that memory initialized (with 0)?
The documentation doesn't speak of that and only tells that the correct amount is reserved.

In my tests such memory defaulted to 0, but that doesn't mean it's guaranteed though.

Thyratron answered 30/12, 2011 at 11:37 Comment(0)
R
17

From the spec:

18.8 Stack allocation

The content of the newly allocated memory is undefined.

Ruebenrueda answered 30/12, 2011 at 11:44 Comment(1)
Now that the stackalloc can be used in "safe" context with Span<T>, does this still hold true?Wethington
A
9

Yes, the spec says it is undefined, but the compiler emits localloc CIL intruction for stackalloc. And this is what ECMA Specs says about localloc:

The localloc instruction allocates size (type native unsigned int) bytes from the local dynamic memory pool and returns the address (a managed pointer, type &) of the first allocated byte. The block of memory returned is initialized to 0 only if the initialize flag on the method is true (see Partition I). The area of memory is newly allocated. When the current method returns the local memory pool is available for reuse.

The initialize flag, also known as localsinit flag, is emitted for every method by the compiler because it is required for verifiable code.

Please look at this issue on coreclr asking to stop zeroing memory on stackalloc. At the end of the issue jkotas says:

The current plan is:

C# will keep zero initializing by default. Changing the default would be too breaking. We have a set of issues opened to make the zero initialization done by the JIT more efficient or reduce need for it (#13827, #13823, #13825) Folks who really want to get the last bit of performance by avoiding zero initialization can use custom ILLinker step (mono/linker#159) when they know what they are doing. We do this for CoreLib today (via VM hack, but we should switch to the ILLinker), and we plan to experiment with this in CoreFX (dotnet/corefx#25956). Based on the results of these experiments, we may consider introducing a more streamlined way to do this in future. @ahsonkhan You should consider experimenting with it in CoreFXLab as well if you believe that it would help.

And see this csharplang proposal

So the conclusion is: the memory is initialized to zero in practice

However there is a bug/feature in the compiler that prevents from emiting localsinit flag. Unsafe methods that don't declare other variables and use the stack allocated variable only to passing it to other method, don't get marked with the localsinit flag.

Here is an example of such bug/feature:

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 "";
        }
    }
}

The not initialized method is five times faster than the initialized one.

Alderney answered 6/11, 2018 at 14:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.