In a game currently in development, I’ve received a request to run GC at 500ms or less per frame to reduce GC hitches.
Based on a UDN thread, I made the following adjustments to meet the request, except for GC start.
gc.AssetClusteringEnabled=True
gc.IncrementalReachabilityTimeLimit = 0.0001 ; sets the soft time limit to 100μ
gc.IncrementalGatherTimeLimit = 0.0001
gc.IncrementalGCTimePerFrame = 0.0001
gc.TimeBetweenPurgingPendingKillObjects = 0.001
However, setting AllowIncrementalReachability to True caused unexplained freezes in Level Streaming, so I compromised on the 7ms load at GC start. However, recently, I’ve received requests to do something about this load.
To begin with, I think that running GC too thinly in UE5.4 creates unnecessary load and has a negative impact on memory, but is this actually the case?
Currently, this too thin GC is causing crashes when object destruction cannot keep up with the limit on creation, and it also increases the load on the game thread, so I’m not sure how to properly adjust the GC parameters.
It looks like the freeze you’re experiencing is directly related to how GC is currently configured. The extremely small per-frame time slices you’re assigning (100 μs) are likely preventing the GC from keeping up with object cleanup, especially during level streaming where object destruction tends to spike.
In the Unreal Insights capture, we can see that LevelStreamingPendingPurgeCount reaches 2147483647 (INT_MAX). This strongly suggests that the GC isn’t able to process the backlog of pending-kill objects, which triggers a full purge that ends up blocking the game thread.
I recommend trying a configuration that allows GC to work more effectively without running every frame. For example:
gc.IncrementalGCTimePerFrame=0.002 ; 2 ms per frame total GC time
gc.IncrementalReachabilityTimeLimit=0.001 ; 1 ms for reachability
gc.IncrementalGatherTimeLimit=0.001 ; 1 ms for gather phase
gc.TimeBetweenPurgingPendingKillObjects=3.0-5.0 ; Avoid purging every frame
This will allow the GC to gather and purge less frequently but more effectively, which should reduce overhead on the game thread. However, keep in mind that if the rate of object creation and destruction exceeds what the system can process, memory pressure will still be a concern.
Additional Recommendations
Regarding the 7 ms GC spike you’re seeing at the start: you might consider manually calling CollectGarbage() right before level streaming begins, or during a loading screen/transition. This helps front-load the cost at a time when gameplay performance isn’t critical.
Also, if your project spawns and discards many objects frequently, implementing an object pooling system could help reduce reliance on GC altogether, though this approach depends entirely on your project.
If you’re open to sharing a project log, we’d be happy to take a deeper look.
Just a heads-up that this feature is marked as experimental, and Epic recommends using it with caution. It has been improving with each Unreal Engine release, but it’s still under active development.
If possible, you could also try updating your project to a newer engine version to see whether there are any improvements or gains. Even testing it in a newer version might help confirm whether the behavior has changed.
I tried adjusting the GC values as a reference, but the freezing was not resolved.
I think the problem is that the load is too high due to the large number of objects overall, and the warning that is appearing with Level Streaming is also a problem, but we are just about to reach master so we cannot make any major changes.
First, I would like to address the issue by improving the 7ms load when GC starts.
Regarding CollectGarbage(), it seems to be effective, but it does not seem to be enough in time when the screen is dark, etc., so I am currently investigating other solutions.
Perfect Yuki, we will try to reproduce this issue in a project with a huge amount of items to see if we can trigger a similar behaviour. Will update in the following working days.
This time, the background assets were updated and it started freezing.
Again, it doesn’t freeze if gc.AllowIncrementalReachability=false, so I think it’s a problem with AllowIncrementalReachability and LevelStreaming.
However, it doesn’t seem to be related to LowMemoryPendingPurgeCount like last time, so I’m currently looking for other ways to deal with it. [Image Removed] [Image Removed]