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

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.