If you were asked where objects greater than or equal to 85,000 bytes are allocated in .NET, you would no doubt say on the Large Object Heap (LOH). What would you say if you were asked where an array of 1000 doubles would be allocated? Currently, as of .NET 4.0, it will be allocated on the Large Object Heap!
William Wegerson (aka OmegaMan), a C# MVP, posted this item to Connect: Large Object Heap (LOH) does not behave as expected for Double array placement that describes and reproduces the behaviour:
byte[] arrayLessthan85K = new byte[84987]; // Note: 12 byte object overhead 84987 + 12 = 84999
Console.WriteLine("byteArrayLessthan85K: {0}", GC.GetGeneration(arrayLessthan85K)); // Returns 0
byte[] array85K = new byte[85000];
Console.WriteLine("byteArray85K: {0}", GC.GetGeneration(array85K)); // Returns 2
double[] array999Double = new double[999];
Console.WriteLine("array999Double: {0}", GC.GetGeneration(array999Double)); // Returns 0
double[] array1000double = new double[1000];
Console.WriteLine("array1000double: {0}", GC.GetGeneration(array1000double)); // Returns 2
By looking at the garbage collection generation on object creation (GC.GetGeneration), we can identify if objects reside the LOH or not. If immediately created in generation 2 then that suggests we are in the LOH.
The reason why double arrays with 1000 or more items are allocated on the LOH is performance, due to the fact that the LOH is aligned on 8 byte boundaries. This allows faster access to large arrays and the trade-off point was determined to be 1 thousand doubles.
According to Claudio Caldato, CLR Performance and GC Program Manager, “there’s no benefit to applying this heuristic on 64-bit architectures because doubles are already aligned on an 8-byte boundary”. Subsequent changes have been made to this heuristic that should appear in a future release of the .NET Framework.
As a side note, the expected behaviour is seen if you use Array.CreateInstance() :
// As noticed by @Romout in the comments to that post, the same behaviour is not seen when using Array.CreateInstance()
double[] array1000doubleCreateInstance = (double[])Array.CreateInstance(typeof(double), 1000); // Returns 0
Console.WriteLine("With array-create: " + GC.GetGeneration(array1000doubleCreateInstance));
// Indeed, the expected tipping point into the LOH occurs when using Array.CreateInstance
// (85000 / 8) = 10625, need 12 bytes for object overhead, nearest is 16 (2*8) bytes so effectively 10623 doubles
double[] array1000doubleCreateInstance2 = (double[])Array.CreateInstance(typeof(double), 10623); // Returns 0
Console.WriteLine("With array-create: " + GC.GetGeneration(array1000doubleCreateInstance2));
double[] array1000doubleCreateInstance3 = (double[])Array.CreateInstance(typeof(double), 10624); // Returns 2
Console.WriteLine("With array-create: " + GC.GetGeneration(array1000doubleCreateInstance3));
Thanks Mitch! I didn't know that one.
I updated my blog post Tribal Knowledge: Large Object Heap and Double Arrays in .Net to reflect that this has not changed in .Net 4; but it should have because 64 bit operations are already aligned. Check out my blog for the response that MS gave!