Last active
July 16, 2018 12:34
-
-
Save airbreather/42e0f54491b719de62a716467ac1ac45 to your computer and use it in GitHub Desktop.
Does the .NET garbage collector REALLY move tenured objects around? I mean, REALLY?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Project Sdk="Microsoft.NET.Sdk"> | |
<PropertyGroup> | |
<OutputType>Exe</OutputType> | |
<TargetFramework>net471</TargetFramework> | |
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | |
</PropertyGroup> | |
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime; | |
using System.Runtime.CompilerServices; | |
static class Program | |
{ | |
// for more fun, try setting this to: | |
// - really low values, like 1 or 50 or so | |
// - the neighborhood of 1050 | |
// - big enough for it to go on the LOH (84976 on x64) | |
const int Length = 1300; | |
static byte[] arr1 = new byte[Length]; | |
static byte[] arr2 = new byte[Length]; | |
static byte[] arr3 = new byte[Length]; | |
[MethodImpl(MethodImplOptions.NoOptimization)] | |
static unsafe void Main() | |
{ | |
// if our arrays are big enough to be allocated on the LOH, then we'll | |
// need to specify the flag that says to compact it. | |
bool onLargeObjectHeap = GC.GetGeneration(new byte[Length]) == GC.MaxGeneration; | |
// set it up so that the second digit of each byte value says which | |
// array it came from. this will sometimes give us a little insight | |
// into what the GC is doing (e.g., when Length is anywhere between | |
// 1200 or so and just under the LOH threshold, on .NET Fat). | |
for (int i = 0; i < Length; i++) | |
{ | |
arr1[i] = 111; | |
} | |
for (int i = 0; i < Length; i++) | |
{ | |
arr2[i] = 121; | |
} | |
for (int i = 0; i < Length; i++) | |
{ | |
arr3[i] = 131; | |
} | |
// tenure all the arrays so that they move around as rarely as possible | |
while (GC.GetGeneration(arr1) != GC.MaxGeneration || | |
GC.GetGeneration(arr2) != GC.MaxGeneration || | |
GC.GetGeneration(arr3) != GC.MaxGeneration) | |
{ | |
GC.Collect(); | |
} | |
// grab a pointer to the first element of arr2. | |
byte correctValue; | |
byte* pArr2; | |
fixed (byte* pArr2Fixed = arr2) | |
{ | |
pArr2 = pArr2Fixed; | |
correctValue = pArr2[0]; | |
} | |
// at this point, people will tell you that the GC can legally move | |
// arr2 so that pArr2 no longer points to the first element of arr2. | |
// let's test to see if it actually does that. | |
for (ulong cnt = 1; cnt != ulong.MaxValue; ++cnt) | |
{ | |
byte curr = pArr2[0]; | |
if (curr != correctValue) | |
{ | |
Console.WriteLine("After {0} read(s), pArr2[0] was {1} (expected {2})", cnt, curr, correctValue); | |
return; | |
} | |
arr1 = new byte[Length]; | |
if (onLargeObjectHeap) | |
{ | |
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; | |
} | |
GC.Collect(); | |
} | |
Console.WriteLine("We did a LOT of reads, and pArr2[0] was always {0}. Congratulations!", correctValue); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment