Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does GCHandle.Alloc allocate memory?

I am using .NET Memory Profiler from SciTech to reduce memory allocations rate of my program and cut frequency of garbage collections.

Surprisingly, according to the profiler, the largest amount of allocations seems to be coming from GCHandle.Alloc calls I am doing to marshall existing .NET arrays to native OpenGL.

My understanding is that calling GCHandle.Alloc does not allocate memory, it only pins existing memory on the managed heap?

Am I wrong or is the profiler wrong?

like image 359
kaalus Avatar asked Mar 11 '15 09:03

kaalus


2 Answers

.NET reference source is available for anyone to see, and you can have a look and find out for yourself.

If you dig into GCHandle.Alloc, you'll see the it calls a native method called InternalAlloc:

[System.Security.SecurityCritical]  // auto-generated
[MethodImplAttribute(MethodImplOptions.InternalCall)]
[ResourceExposure(ResourceScope.None)]
internal static extern IntPtr InternalAlloc(Object value, GCHandleType type);

Drilling down into the CLR code, you see the internal call to MarshalNative::InternalAlloc, which ends up calling:

hnd = GetAppDomain()->CreateTypedHandle(objRef, type);

Which in turns calls ObjectHandle::CreateTypedHandle -> HandleTable::HndCreateHandle -> HandleTableCache->TableAllocSingleHandleFromCache which allocates the handle if the it doesn't exist in the cache.

As @Antosha corrected me, the place of invocation isn't via ComDelegate (which actually makes little since) but via MarshalNative. An allocation does occur, not on the managed heap, but an external heap reserved by the runtime for managing handle roots into GC objects. The only allocation that does occur in the managed heap is the IntPtr which holds to pointer to the address in the table. Despite this, you should still make sure to call GCHandle.Free once you're done.

like image 61
Yuval Itzchakov Avatar answered Nov 04 '22 13:11

Yuval Itzchakov


The profiler is even assigning a specific memory amount to each GCHandle I allocate - 8 bytes. And the managed heap seems to grow 8 bytes with each GCHandle.Alloc. So it seems that it actually does allocate space on managed heap, although I have no idea what for?

I don't know how an handle could be smaller :) I've done some tests:

Console.WriteLine("Is 64 bit: {0}, IntPtr.Size: {1}", Environment.Is64BitProcess, IntPtr.Size);

int[][] objects = new int[100000][];
for (int i = 0; i < objects.Length; i++)
{
    objects[i] = new int[] { 0 };
}

long w1 = Environment.WorkingSet;

GCHandle[] handles = new GCHandle[objects.Length];

for (int i = 0; i < handles.Length; i++)
{
    //handles[i] = new GCHandle(handles);
    //handles[i] = GCHandle.Alloc(objects[i]);
    handles[i] = GCHandle.Alloc(objects[i], GCHandleType.Pinned);
}

Console.WriteLine("Allocated");
long w2 = Environment.WorkingSet;
Console.WriteLine("Used: {0}, by handle: {1}", w2 - w1, ((double)(w2 - w1)) / handles.Length);
Console.ReadKey();

It is a small program. If you run, you will see that an "empty" GCHandle (one created with new GCHandle()) occupies IntPtr.Size memory. This is clear if you use ILSpy to look at it: it has a single IntPtr field. If you pin some object, then it occupies 2*IntPtr.Size memory. This probably because it has to write something in a CLR table (of size IntPtr) plus its internal IntPtr

Taken from https://stackoverflow.com/a/18122621/613130

It uses a dedicated table of GC handles built inside the CLR. You allocate an entry into this table with GCHandle.Alloc() and release it again later with GCHandle.Free(). The garbage collector simply adds the entries in this table to the graph of objects it discovered itself when it performs a collection.

like image 44
xanatos Avatar answered Nov 04 '22 13:11

xanatos