C# and .Net Garbage collector performance - c#

I am trying to make a game in C# and .NET, and I was planning to implement messages that update the game objects in the game world. These messages would be C# reference objects.
I want this approach because doing it this way would be easier to send them over a network if I want the game to be multiplayer.
But if I have a lot of messages, won't it be quite stressful for the garbage collector? And won't that affect gameplay? The message classes themselves are quite small with 4 or 5 members at most.
These messages will be generated a couple of times per second for each object in the game world.

In .NET the garbage collector has 3 generations, generation 0, generation 1 and generation 2. Every Time the GC fails to collect an object in a generation, that object will be promoted to the next generation.
You could potentially run into issues if your objects are larger than 85kb. These objects will be automatically stored in the large object heap. The large object heap will be automatically collected in the following situations:
Allocations exceed the large object heaps threshold.
System is in a low memory situation.
System.GC.Collect is called on generation 2.
The problem is that when the large object heap is collected, the memory for the objects is deallocated but the LOH is not compacted. Because the LOH is fragmented, you could potentially get SystemOutOfMemory exceptions thrown if there isn't a space big enough for your object on the LOH.
Techniques such as object pooling are commonly used to improve performance of the LOH.
http://en.wikipedia.org/wiki/Object_pool_pattern
Source: http://msdn.microsoft.com/en-us/magazine/cc534993.aspx
UPDATE: .Net 4.5.1 will allow you to do on-demand compaction of the LOH within your application using the GC.Collect API.

The GC in later versions, but more precisely 4.5, runs asynchronously for generations level 0 and 1. This has highly reduced the impact of GC.
If your objects are short-lived they should not pass from generation level 0 most of the time. Level 0 is the fastest generation to clean up by the GC.
Bottom line, I would not consider prematurely optimizing my code for fear of GC performance.
Premature optimization is the root of all evil by
DonaldKnuth
Personally, i'd recommend this article for a deeper understanding

Related

Defragmentation of heap in c#?

I want to understand full complete working with heap in C#. I understand how the stack and heap work, but I didn't find any explanation (if it is possible) of heap defragmentation.
I read a lot about a problem with fragmentation when GC is allocating and deallocating memory blocks on heap.
So if someone can explain to me or give some good article about this concern and heap (memory) defragmentation.
If you know about how the heap works, I assume you know that there are several different kinds of heaps. See my answer here - Stack vs. Heap in .NET
So the 2 you are speaking of out of those I mention in that answer are the Large Object Heap (LOH) and the GC Heap (also called Ephemeral Heap).
Generally don't need to worry about heap fragmentation for .NET. GC for .NET works in 3 steps: mark, sweep, compact. Mark - scans for all rooted references and makes a list of those that are rooted - these are not eligible for garbage collection and will not be touched. Sweep - clears the memory for those items not on the list and clears the "marked bit" for items that were marked. Compact - moves the memory for the remaining rooted objects so it is in a contiguous block. One caveat to the Compact phase is that the LOH is NOT compacted at least as of the latest version of .NET 4.6.2. This was a design decision the CLR GC team made because of performance reasons and time it would take to move all of the memory to a contiguous block. There have been many, many performance improvements since .NET 1.0 so GC isn't the beast it used to be. In any case, the Heap for Gen 0, 1, and 2 are compacted. Thus, no need for worry about fragmentation there. For the most part, the LOH survives without fragmentation problems with the algorithm it implements. There are cases where you can get fragmentation on the LOH. This can be caused by several things - some of which are bad allocation patterns, frequent Full GC collections, etc. This can be combated by improving allocation patterns, allocating large chunks of memory as close together (programmatically) as possible, and object pooling.
As of .NET 4.5.1, there is a way to compact the LOH manually though I would strongly recommend against it for the reason that it is a huge performance hit for your app for 2 reasons:
it is time consuming
it clears any of the allocation pattern algorithm that the GC has collected over the lifetime of your app. While your app is running, the GC actually tunes itself by learning how your app allocates memory. As such, it becomes more efficient (to a certain point) the longer your app runs. When you execute GC.Collect() (or any overload of it), it clears all of the data the GC has learned - so it must start over. You can read more about how to manually compact the LOH here: https://blogs.msdn.microsoft.com/mariohewardt/2013/06/26/no-more-memory-fragmentation-on-the-net-large-object-heap/ (again, I recommend against it)
Info about GC mark, sweep, compact - https://blogs.msdn.microsoft.com/abhinaba/2009/01/30/back-to-basics-mark-and-sweep-garbage-collection/
Info about LOH allocation algorithm:
https://www.red-gate.com/simple-talk/dotnet/net-framework/the-dangers-of-the-large-object-heap/

Does CLR reuse object in LOH by default?

I read some posts and books about .Net/C#/CLR and so on, and found following slide in Microsoft's presentation of 2005 year:
GC takes time – “% time in GC” counter
If objects die in gen0 (survival rate is 0) it’s the ideal situation
The longer the object lives before being dead, the worse (with exceptions)
Gen0 and gen1 GCs should both be relatively cheap; gen2 GCs could cost a lot
LOH – different cost model
Temporary large objects could be bad
Should reuse if possible
My question is what does it mean Should reuse if possible ? Is it that CLR reuse allocated memory for new object in LOH or that user (developer in our case) should do it ?
I think that is a note to us, as implementers, not a note on how Microsoft works (so no, it does not automatically reuse objects). If you have a object on the LOH, and you immediate dispose it, the LOH can get fragmented very soon. That is why it says "Temporary large objects could be bad".
The other thing is in the same line: if you have a large object and you can reuse it, you prevent recreating that object and thus improve performance. This is true because you prevent the LOH to get fragmented faster and you lower the memory pressure. One specific thing that comes in mind here are large string objects. Those are ideal to reuse. The string intern pool is located on the LOH, so if you intern frequently used large strings, you do what it asks.
I agreed with Patrick's statement; CLR doesn't reuses object in LOH. These guidelines are for our implementation purposes.
Gen2 garbage collection process is very costly, so we need to avoid this. So we can do this by reusing the objects, because fragmentation process also performed and it will take more time in case of LOH. We can reuse these objects by using object pool.

Who is responsible for C# memory allocation?

What part of the .NET framework takes responsibility to allocate memory. Is it GC?
It is the CLR but in close cooperation with the GC. And the GC is a part of the CLR so it's not such a clear division.
Allocation takes place at the start of the free section of the Heap, it is a very simple and fast operation. Allocation on the Large Object Heap (LOH) is slightly more complicated.
Do Visit http://www.codeproject.com/Articles/38069/Memory-Management-in-NET
Allocation of Memory
"Generally .NET is hosted using Host process, during debugging .NET
creates a process using VSHost.exe which gives the programmer the
basic debugging facilities of the IDE and also direct managed memory
management of the CLR. After deploying your application, the CLR
creates the process in the name of its executable and allocates memory
directly through Managed Heaps.
When CLR is loaded, generally two managed heaps are allocated; one is
for small objects and other for Large Objects. We generally call it as
SOH (Small Object Heap) and LOH (Large Object Heap). Now when any
process requests for memory, it transfers the request to CLR, it then
assigns memory from these Managed Heaps based on their size.
Generally, SOH is assigned for the memory request when size of the
memory is less than 83 KBs( 85,000 bytes). If it is greater than this,
it allocates memory from LOH. On more and more requests of memory .NET
commits memory in smaller chunks."
Upon reading further this paragraphs, Its the CLR with the help of Windows (32bit or 64Bit) it "allocates" the memory.
The "De-allocation" is managed by GC.
"The relationships between the Object and the process associated with
that object are maintained through a Graph. When garbage collection is
triggered it deems every object in the graph as garbage and traverses
recursively to all the associated paths of the graph associated with
the object looking for reachable objects. Every time the Garbage
collector reaches an object, it marks the object as reachable. Now
after finishing this task, garbage collector knows which objects are
reachable and which aren’t. The unreachable objects are treated as
Garbage to the garbage collector."
Despite the name, many kinds of modern "garbage collectors" don't actually collect garbage as their primary operation. Instead, they often identify everything in an area of memory that isn't garbage and move it somewhere that is known not to contain anything of value. Unless the area contained an object that was "pinned" and couldn't be moved (in which case things are more complicated) the system will then know that the area of memory from which things were moved contains nothing of value.
In many such collectors, once the last reference to an object has disappeared, no bytes of memory that had been associated with that object will ever again be examined prior to the time that they get blindly overwritten with new data. If the GC expects that the next use of the old region of memory will be used to hold new objects, it will likely zero out all the bytes in one go, rather than doing so piecemeal to satisfy allocations, but if the GC expects that it will be used as a destination for objects copied from elsewhere it may not bother. While objects are guaranteed to remain in memory as long as any reference exists, once the last reference to an object has ceased to exist there may be no way of knowing whether every byte of memory that had been allocated to that object has actually been overwritten.
While .NET does sometimes have to take affirmative action when certain objects (e.g. those whose type overrides Finalize) are found to have been abandoned, in general I think it's best to think of the "GC" as being not a subsystem that "collects" garbage, but rather as a garbage-collected memory pool's manager, that needs to at all times be kept informed of everything that isn't garbage. While the manager's duties include the performance of GC cycles, they go far beyond that, and I don't think it's useful to separate GC cycles from the other duties.

Managed heap OutOfMemory

EDIT: I reformulated it to be a question and moved the answer to the answers part...
In a relatively complex multithreaded .NET application I experienced OutOfMemoryException even in the cases I could think there is no reason for it.
The situation:
The application is 32bit.
The application creates lot of (thousands) short lived objects that are considered small (less than approx. 85kB).
Additionaly it creates some (hundreds) short lived objects that are considered large (greater than approx. 85kb). This implies these objects are allocated at LOH (large object heap).
Both classes for these objects define finalizer (~MyFinalizer(){...}).
The symptoms:
OutOfMemoryException
Looking at the app via memory profiler, there are thousands of the small objects eligible for collection, but not collected and thus block large amount of memory.
The questions:
Why the app exhausts entire heap?
Why there is lot of "dead" objects still present in the memory?
After some deep investigation I have found the reason. As it took some time, I would like to make it easy for others suffering the same problem.
The reasons:
App has only approx 2GB of virtual address space.
The LOH is by design not compacted and thus might get fragmented very quickly, but for the mentioned count of large objects it should not be any problem.
Due to the design of the Garbage Collector, if there is an object defining the finalizer (even just empty), it is considered as Gen2 object and is placed into GC's finalization queue. This implies, until it is finalized (the MyFinalizer called) it just blocks the memory. In the case of mentioned app the GC thread running the finalizers didn't get the chance to do its work as quickly as needed and thus the heap was exhausted.
The Solution:
Do not use the finalizer for such "dynamic" objects (high volume, short life), workaround the finalization code in other way...
Very useful sources:
The Dangers of the Large Object Heap
Garbage Collection
Will the Garbage Collector call Dispose for me?
Try using a profiler, such as:
ANTS Memory Profiler
ANTS Performance Profiler
Jetbrains Performance Profiler
For LOH force a GC with:
GC.Collect();
GC.WaitForPendingFinalizers();

Large Object Heap Compaction, when is it good?

First off, how big is considered large? Is there anyway to determine how large an object is in heap?
.Net 4.5.1 comes with this LargeObjectHeapCompactionMode:
After the LargeObjectHeapCompactionMode property is set to
GCLargeObjectHeapCompactionMode.CompactOnce, the next full blocking
garbage collection (and compaction of the LOH) occurs at an
indeterminate future time. You can compact the LOH immediately by
using code like the following:
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
From what I've heard, it's a bad thing to compact LOH! So, which one is worst? Compact LOH or having LOH fragmentation?
Allocations >= 85 KB go onto the LOH. Compacting the LOH is not bad -- it's just that LOH fragmentation isn't something the great majority of apps need to worry about, so for them it's not worth the expense of compacting.
Fragmentation occurs when you allocate several large objects and they all get taken from the same page of address space, then let some of those objects get collected. The remaining free space in that page might be unusable because it is too small, or even simply "forgotten" in the sense that the allocator won't ever reconsider using it again.
Eventually there are fewer and fewer clean pages to use, so the allocator will start to slow down as it forcibly moves objects or even start throwing OutOfMemory exceptions. Compaction moves those objects to new pages, reclaiming that free space.
Does your app have this object usage pattern? Most don't. And on 64-bit platforms, you might not even notice it as there's quite a bit more address space to fragment before it becomes a huge issue.

Categories