Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GC behavior when pinning an object

While browsing through the code of PinnableObjectCache from mscorlib, I've encountered the following code:

for (int i = 0; i < m_restockSize; i++) {     // Make a new buffer.     object newBuffer = m_factory();      // Create space between the objects.  We do this because otherwise it forms      // a single plug (group of objects) and the GC pins the entire plug making      // them NOT move to Gen1 and Gen2. By putting space between them     // we ensure that object get a chance to move independently (even if some are pinned).       var dummyObject = new object();     m_NotGen2.Add(newBuffer); } 

It got me wondering what the reference to a plug means? While trying to pin an object in memory, wouldn't the GC pin the specific address specified for the object? What is this plug behavior actually doing and why is there a need to "space out" between the objects?

like image 234
Yuval Itzchakov Avatar asked Nov 14 '14 09:11

Yuval Itzchakov


People also ask

How GC decides if objects are live?

GC checks the below information to check if the object is live: It collects all handles of an object that are allocated by user code or by CLR. Keeps track of static objects, as they are referenced to some other objects. Use stack provided by stack walker and JIT.

What is a pinned object?

A pinned object is one that has a set location in memory. Normally the garbage collector will compact the managed heap, which changes the location of the objects in memory. If you have some unmanaged code that refers to some C# object you have created you may want to be able to reference the memory location absolutely.

What does the GC do with pinned local variables?

In effect the GC has to get out of the way and leave the pinned local variable alone for the life-time of the method. Normally the GC is concerned about which objects are live or dead so that it knows what it has to clean up.

What are pinned object heaps in C#?

What are Pinned Object Heaps in C#? A dedicated Pinned Object Memory, a fairly new type of managed heap segment, was added to the GC in .NET 5. (we have Small and Large Object Heaps so far). Pinning comes with its own set of drawbacks, as it causes fragmentation, and complicates object compaction a lot.

What is the advantage of pinning objects during the compact phase?

Generally the GC likes to relocate objects around during the Compact Phase to make memory allocations cheap, but pinning prevents that as the object is being accessed via a pointer and therefore its memory address has to remain the same.

What happens when a pinned local variable is executed?

While a method with a pinned local variable is executing, the VES shall not relocate the object to which the local refers. That is, if the implementation of the CLI uses a garbage collector that moves objects, the collector shall not move objects that are referenced by an active pinned local variable.


Video Answer


1 Answers

Ok, so after several attempts to get official replies from people with "inside knowledge", I decided to experiment a little myself.

What I tried to do is re-produce the scenario where I have a couple of pinned objects and some unpinned objects between them (i used a byte[]) to try and create the effect where the unpinned objects don't move the a higher generation inside the GC heap.

The code ran on my Intel Core i5 laptop, inside a 32bit console application running Visual Studio 2015 both in Debug and Release. I debugged the code live using WinDBG.

The code is rather simple:

private static void Main(string[] args) {     byte[] byteArr1 = new byte[4096];     GCHandle obj1Handle = GCHandle.Alloc(byteArr1 , GCHandleType.Pinned);     object byteArr2 = new byte[4096];     GCHandle obj2Handle = GCHandle.Alloc(byteArr2, GCHandleType.Pinned);     object byteArr3 = new byte[4096];     object byteArr4 = new byte[4096];     object byteArr5 = new byte[4096];     GCHandle obj4Handle = GCHandle.Alloc(byteArr5, GCHandleType.Pinned);     GC.Collect(2, GCCollectionMode.Forced); } 

I started out with taking a look at the GC heap address space using !eeheap -gc:

generation 0 starts at 0x02541018  generation 1 starts at 0x0254100c generation 2 starts at 0x02541000   ephemeral segment allocation context: none  segment      begin      allocated   size  02540000     02541000   02545ff4    0x4ff4(20468) 

Now, I step through the code running and watch as the objects get allocated:

0:000> !dumpheap -type System.Byte[] Address     MT          Size 025424e8    72101860    4108      025434f4    72101860    4108      02544500    72101860    4108      0254550c    72101860    4108      02546518    72101860    4108   

Looking at the addresses I can see they're all currently at generation 0 as it starts at 0x02541018. I also see that the objects are pinned using !gchandles:

Handle     Type      Object      Size    Data Type   002913e4   Pinned    025434f4    4108    System.Byte[] 002913e8   Pinned    025424e8    4108    System.Byte[] 

Now, I step through the code untill i get to the line which runs GC.Collect:

0:000> p eax=002913e1 ebx=0020ee54 ecx=00000002 edx=00000001 esi=025424d8 edi=0020eda0 eip=0062055e esp=0020ed6c ebp=0020edb8 iopl=0  nv up ei pl nz na pe nc cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b  efl=00000206 0062055e e80d851272      call    mscorlib_ni+0xa28a70 (GC.Collect) (72748a70) 

And now, anticipating what happens, i check the GC generation address again using !eeheap -gc and i see the following:

Number of GC Heaps: 1 generation 0 starts at 0x02547524 generation 1 starts at 0x0254100c generation 2 starts at 0x02541000 

The starting address for generation 0 has been moved from 0x02541018 to 0x02547524. Now, i check the address of the pinned and none pinned byte[] objects:

0:000> !dumpheap -type System.Byte[] Address  MT           Size 025424e8 72101860     4108      025434f4 72101860     4108      02544500 72101860     4108      0254550c 72101860     4108      02546518 72101860     4108    

And I see they have all stayed at the same address. But, the fact that generation 0 now starts at 0x02547524 means they've all been promoted to generation 1.

Then, I remember reading something about that behavior in the book Pro .NET Performance, it states the following:

Pinning an object prevents it from being moved by the garbage collector. In the generational model, it prevents promotion of pinned objects between generations. This is especially significant in the younger generations, such as generation 0, because the size of generation 0 is very small. Pinned objects that cause fragmentation within generation 0 have the potential of causing more harm than it might appear from examining pinned before we introduced generations into the the picture. Fortunately, the CLR has the ability to promote pinned objects using the following trick: if generation 0 becomes severely fragmented with pinned objects, the CLR can declare the entire space of generation 0 to be considered a higher generation and allocate new objects from a new region of memory that will become generation 0. This is achieved by changing the ephemeral segment.

And this actually explains the behavior i'm seeing inside WinDBG.

So, to conclude and until anyone has any other explanation, I think the comment isn't correct and doesn't really capture what is really happening inside the GC. If anyone has anything to elaborate, I'd be glad to add.

like image 143
Yuval Itzchakov Avatar answered Sep 25 '22 19:09

Yuval Itzchakov