3

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                                                                              
recursive
  • 83,943
  • 34
  • 151
  • 241
  • What do you got if you use `GC.GetTotalMemory(true)` instead of the [`GetAllocatedBytesForCurrentThread`](https://learn.microsoft.com/dotnet/api/system.gc.getallocatedbytesforcurrentthread) method ? I got ~4MB on .NET 4.7.2 (VS2017). –  Sep 17 '20 at 18:43
  • 1
    That gives me much more reasonable numbers, but seems to include other threads as well. This minimal reproduction doesn't have any other threads, but I'd like to use this technique in less trivial scenarios if possible. – recursive Sep 17 '20 at 18:46
  • I can't test on my computer, but did you try to change some cells because here you do nothing with the array... perhaps it is why the GetAllocatedBytesForCurrentThread see 0. –  Sep 17 '20 at 18:48
  • Not yet. But the array clearly gets allocated, since the allocation shows in the `TotalMemory` call. But I will enhance my test program to include all your suggestions. – recursive Sep 17 '20 at 18:50
  • Why don't use performance counters or ETW events for measurement? – Pavel Anikhouski Sep 17 '20 at 18:52
  • 2
    @OlivierRogier Neither touching the array nor feeding it to `GC.KeepAlive` does anything. Allocating a `List` with capacity of 1m consumes 32 bytes. – GSerg Sep 17 '20 at 18:54
  • @PavelAnikhouski: The first reason is that I'm not familiar with those things. From what I'm seeing now though it doesn't seem to usable from within the process. I'd like to use these numbers inside the application being measured. – recursive Sep 17 '20 at 18:55
  • 1
    I wonder what happens if you follow https://stackoverflow.com/a/61285051/11683? – GSerg Sep 17 '20 at 19:02

1 Answers1

0

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://learn.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.