How can I get total heap allocations? GC.GetAllocatedBytesForCurrentThread() doesn't do it

Viewed 191

I want to get some idea how much memory has been cumulatively allocated by a thread. From it's documentation GC.GetAllocatedBytesForCurrentThread() seems like the thing to use, but in practice, it's giving me... something else.

Here's my test program, created using the dotnet core 3.1 console application Visual Studio template.

using System;

class Program {
    static void Main() {
        long start, difference;

        start = GC.GetAllocatedBytesForCurrentThread();
        var x = new int[1_000_000];
        difference = GC.GetAllocatedBytesForCurrentThread() - start;
        Console.WriteLine("{0} bytes allocated for array construction", difference);

        start = GC.GetAllocatedBytesForCurrentThread();
        Console.WriteLine(DateTime.Now.ToString());
        difference = GC.GetAllocatedBytesForCurrentThread() - start;
        Console.WriteLine("{0} bytes allocated for date printing", difference);
    }
}

It gives me this output.

0 bytes allocated for array construction
9/17/2020 11:26:40 AM
19040 bytes allocated for date printing
Press any key to continue . . .

I don't believe that a one-million element array can be created without allocating a single byte on the heap.

So is there a way to track heap allocations for a thread? And what is this method actually doing, if anything?

Here's a longer, more comprehensive test program including all the suggestions so far. I included the sum part to verify that array actually gets created when the code runs.

using System;
using System.Linq;

class Program {
    static void Main() {
        long start, difference;

        {
            start = GC.GetAllocatedBytesForCurrentThread();
            var x = new int[1_000_000];
            x[^1] = 7;
            difference = GC.GetAllocatedBytesForCurrentThread() - start;
            Console.WriteLine("{0} bytes thread allocated for array construction (sum: {1})", difference, x.Sum());
        }

        start = GC.GetAllocatedBytesForCurrentThread();
        Console.WriteLine(DateTime.Now.ToString());
        difference = GC.GetAllocatedBytesForCurrentThread() - start;
        Console.WriteLine("{0} bytes thread allocated for date printing", difference);

        {
            start = GC.GetTotalMemory(true);
            var x = new int[1_000_000];
            x[^1] = 7;
            difference = GC.GetTotalMemory(true) - start;
            Console.WriteLine("{0} bytes total allocated for array construction (sum: {1})", difference, x.Sum());
        }

        start = GC.GetTotalMemory(true);
        Console.WriteLine(DateTime.Now.ToString());
        difference = GC.GetTotalMemory(true) - start;
        Console.WriteLine("{0} bytes total allocated for date printing", difference);

    }
}

Output:

0 bytes thread allocated for array construction (sum: 7)
9/17/2020 11:51:54 AM
19296 bytes thread allocated for date printing
4000024 bytes total allocated for array construction (sum: 7)
9/17/2020 11:51:54 AM
64 bytes total allocated for date printing                                                                              
1 Answers

Array allocation is made by this IL code:

// int[] array = new int[1000000];
IL_0008: ldc.i4 1000000
IL_000d: newarr [mscorlib]System.Int32
IL_0012: stloc.2

Reading that does not provide many help:

https://docs.microsoft.com/dotnet/api/system.reflection.emit.opcodes.newarr

But perhaps the array is created by another thread in the CLR, by the CLR thread itself in fact, and I think it is why GC.GetAllocatedBytesForCurrentThread() - start returns 0 while using GetTotalMemory returns the good and real amount.

Arrays are allocated in the heap while local value types and references are created directly in the stack of the method.

For example:

.locals init (
    [0] int64 'value'
)

IL_0001: ldc.i4.s 10
IL_0003: conv.i8
IL_0004: stloc.0

Thus in all cases, the current thread consumes nothing.

But if for example you create a List<int> and next add some items, or do anything that consumes memory like calling medthods (DateTime.Now.ToString()), the thread will consumes memory and you will see that using the GetAllocatedBytesForCurrentThread method.

But in case of arrays, if it is the CLR thread that creates them, we can't see that.

Related