Detecting and Removing LOH Thrashing with GcDebug - bryanedds/Nu GitHub Wiki
Most reasonable uses of .NET GC have acceptable latency costs. For the most part, as long as you use the .NET GC in a reasonable way, your game's performance won't be impacted. However, there is one exception and that is LOH thrashing, which is when you temporarily allocate arrays of equal to or greater than 85k every frame or so. This will result in LOH collections that can have noticeable game latency impact. Therefore, we provide the GcDebug feature to help you detect and remove these unreasonable GC usage patterns.
What is LOH Thrashing?
The Large Object Heap (LOH) is a special region in .NET's garbage collector where objects larger than 85,000 bytes (approximately 85KB) are allocated. Unlike the regular heap, the LOH is not compacted by default, which can lead to memory fragmentation. LOH thrashing occurs when your application repeatedly allocates and deallocates large objects, leading to:
- Memory fragmentation
- Increased garbage collection overhead
- Performance degradation
- Higher memory consumption
- Potential out-of-memory exceptions
In game development, LOH thrashing is particularly problematic because it can cause:
- Frame rate drops and stuttering
- Unpredictable GC pauses during gameplay
- Increased memory pressure on resource-constrained platforms
Enabling GcDebug
Nu Game Engine provides a built-in diagnostic tool called GcDebug that monitors and logs all allocations to the LOH. This helps you identify which objects are causing LOH allocations so you can refactor your code to avoid them.
Configuration
To enable GcDebug, add the following to your project's App.config file:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="GcDebug" value="True" />
<!-- Other configuration settings -->
</appSettings>
</configuration>
Note: The App.config file is typically located in the root directory of your game project.
Verifying GcDebug is Enabled
When GcDebug is enabled, Nu will initialize the GcEventListener at startup. You should see log messages in your log file (typically located at the path specified by Constants.Paths.LogFilePath) whenever an object of 85KB or larger is allocated.
Understanding GcDebug Output
When GcDebug is active and an object is allocated to the LOH, you'll see log entries like:
Allocated object of type 'System.Byte[]' of size 90000 on the LOH.
Allocated object of type 'YourNamespace.YourType' of size 100000 on the LOH.
Each log entry contains:
- Type name: The fully qualified type name of the allocated object
- Size: The size of the allocation in bytes
- Context: Indicates this is an LOH allocation
When you enabled this feature, you will notice a lot of LOH notifications at the start of a Nu game or when loading and unloading asset packages when entering or exiting game areas. THESE ARE OKAY. These are one-time allocations. The allocations that cause GC latency issues are the temporary ones that happens on a frame basis that cause the LOH to get compacted repeatedly during active gameplay.
Common Fixes for LOH Allocations in Nu
By way of the Prime library, Nu provides S(egmented)Collections (SList, SHashSet, SDictionary) that can be temporarily allocated and do not touch the LOH even when their collections grow over 85k. If you need a temporary collection on a frame basis, consider using one of these. Alternatively, you can use a long-lived collection such as a Dictionary that only gets allocated once but gets cleared every frame. When a .NET Dictionary is cleared, it keeps resident the underlying memory rather than releasing it back to the GC.
Finally, there might be some extremely niche usage patterns in Nu that lead to thrashing (usage patterns that are generally considered unreasonable but work anyway, albeit with thrashing). In these cases, we try to log a warning that these usage patterns might end up thrashing LOH, such as -
High MMCC entity content count: having a large number of MMCC entities (> 2048) in a single entity parent may thrash the LOH.
Additional Resources
- .NET Large Object Heap Documentation
- Memory Performance Best Practices
- Nu's
World.fs- Contains theGcEventListenerimplementation - Nu's
WorldContent.fs- Contains LOH warnings for MMCC collections
Summary
GcDebug is a reliable tool for identifying and fixing LOH thrashing in Nu game projects. By monitoring LOH allocations during development, you can:
- Identify unreasonable allocation patterns early.
- Improve frame stability and overall performance.
- Create more efficient and scalable game architectures.
Remember to test with GcDebug enabled regularly during development, and always disable it for release builds to avoid the monitoring overhead.