Allocations and the Garbage Collector
The Garbage Collector and Generations
The Garbage Collector and Generations
When running a .NET application, the runtime allocates a big chunk of memory in which it manages (de)allocations of objects.
Allocations are done whenever a new object instance is created, deallocations are handled by the Garbage Collector (GC).
The Garbage Collector and Generations
The 2 components that belong to GC are the allocator and the collector.
The allocator is responsible for getting more memory and triggering the collector when appropriate.
The collector reclaims garbage, or the memory of objects that are no longer in use by the program.
There are other ways that the collector can get called, such as manually calling GC.Collect or the finalizer thread receiving an asynchronous notification of the low memory (which triggers the collector).
The Garbage Collector and Generations
When is memory from the heap allocated?
When is memory from the heap allocated?
Memory from the heap is allocated when you new an object.
Executing var person = new Person(); will allocate a Person object on the heap, consuming memory.
No memory is allocated when making use of value types, such as int or bool, a custom struct.
The garbage collector never has to collect these, as they are not “pointers towards a value elsewhere in memory”, like objects. They just contain the value directly.
When is memory from the heap allocated?
int i = 42;
// boxing - wraps the value type in an "object box"
// (allocating a System.Object)
object o = i;
// unboxing - unpacking the "object box" into an int again
// (CPU effort to unwrap)
int j = (int)o;
When is memory from the heap allocated?
There are some other interesting cases where you would not expect allocations. Let’s see how we can detect them:
- Staring at Intermediate Language
- Using plugins for Visual Studio and/or ReSharper
- Profiling
How to detect allocations
Staring at Intermediate Language
Staring at Intermediate Language
Tools:
- ReSharper’s IL viewer
- ildasm.exe
Quick note:
When using any IL viewer to look at allocations, make sure to build using the Release build configuration.
Debug builds usually do not run any/all compiler optimizations, making for different IL code from what you would see with a Release build.
Staring at Intermediate Language
Console.WriteLine(string.Concat("Answer", 42, true));
How about this line of code?
Staring at Intermediate Language
In IL, this is compiled to:
Staring at Intermediate Language
private static void ParamsArray()
{
ParamsArrayImpl();
}
private static void ParamsArrayImpl(params string[] data)
{
foreach (var x in data)
{
Console.WriteLine(x);
}
}
Working with params arrays in methods.
Have a look at this code:
Staring at Intermediate Language
There’s a hidden allocation in here…
The call to ParamsArrayImpl() looks like this in IL:
Quick note:
Starting with .NET 4.6, no empty array will be allocated. Instead, Array.Empty<T> will be passed which is a cached, empty array.
Staring at Intermediate Language
private static double AverageWithinBounds(
int[] inputs,
int min,
int max)
{
var filtered = from x in inputs
where (x >= min) && (x <= max)
select x;
return filtered.Average();
}
Using LINQ and anonymous functions
Staring at Intermediate Language
Using LINQ and anonymous functions
Staring at Intermediate Language
Using LINQ and anonymous functions
There are many more examples, try looking at this piece of code’s IL:
var strings = new string[] { "x", "y"};
foreach (var s in strings)
{
Task.Run(() => Console.WriteLine(s));
}
(spoiler alert: a new <>c__DisplayClass0_0 will be allocated, capturing s for use in the System.Action)
How to detect allocations
Using plugins for Visual Studio and/or ReSharper
Using plugins for Visual Studio and/or ReSharper
How to detect allocations
Profiling
Profiling
[
{
"name": "Westmalle Tripel",
"brewery": "Brouwerij der Trappisten van Westmalle",
"rating": 99.9,
"votes": 256974
},
...
]
JSON array:
Requirements:
- Read this file into a multi-dimensional dictionary (Dictionary<string, Dictionary<string, double>>)
- Reload this dictionary every couple of minutes
Profiling
public static void LoadBeers()
{
Beers = new Dictionary<string, Dictionary<string, double>>();
using (var reader = new JsonTextReader(new StreamReader(File.OpenRead("beers.json"))))
{
while (reader.Read())
{
if (reader.TokenType == JsonToken.StartObject)
{
// Load object from the stream
var beer = JObject.Load(reader);
var breweryName = beer.Value<string>("brewery");
var beerName = beer.Value<string>("name");
var rating = beer.Value<double>("rating");
// Add beers per brewery dictionary if it does not exist
Dictionary<string, double> beersPerBrewery;
if (!Beers.TryGetValue(breweryName, out beersPerBrewery))
{
beersPerBrewery = new Dictionary<string, double>();
Beers.Add(breweryName, beersPerBrewery);
}
// Add beer
if (!beersPerBrewery.ContainsKey(beerName))
{
beersPerBrewery.Add(beerName, rating);
}
}
}
}
}
Profiling
for (var i = 0; i < 10; i++)
{
BeerLoader.LoadBeers();
Console.ReadLine();
}
Profiling
No GCs for your allocations?
No GCs for your allocations?
Short answer:
.NET already have such a feature and that’s called the NoGCRegion.
GC.TryStartNoGCRegion API allows you to tell us the amount of allocations you’d like to do and when you stay within it, no GCs will be triggered.
GC.EndNoGCRegion will revert back to doing normal GCs
No GCs for your allocations?
Long answer:
There’s currently limitations on how much you can allocate with NoGCRegion
If you are using Server GC you are able to ask for a lot more memory on SOH because its SOH segment size is a lot larger.
Currently (and this has been the case for a long time) the default seg size on 64-bit for Server GC is 4GB
When you have > 4 procs it means the SOH segment size is 1GB each.
If have > 8 procs it means the SOH segment size is 1GB each.
No GCs for your allocations?
On Desktop you have ways to make the SOH segment size larger –
use the gcSegmentSize in app config
Don’t optimize what should not be optimized
Don’t optimize what should not be optimized
There is an old adage in IT that says “don’t do premature optimization”.
In other words: maybe some allocations are okay to have, as the GC will take care of cleaning them up anyway.
Resources
Resources
Allocations and the Garbage Collector
By Pavel Nasovich
Allocations and the Garbage Collector
The .NET Garbage Collector (GC) slides based on several articles.
- 1,043