Skip to content

Commit 71d2f57

Browse files
arttu-peltonenEvergreen
authored andcommitted
Reduce NRP RenderGraph compiler memory usage
This PR fixes a memory usage regression reported in https://jira.unity3d.com/browse/UUM-60476. The PR that caused the regression was RG Compilation Caching https://github.cds.internal.unity3d.com/unity/unity/pull/40721, which for NRP RG Compiler creates 20 instances of `CompilerContextData` ([see code](https://github.cds.internal.unity3d.com/unity/unity/blob/trunk/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraphCompilationCache.cs#L36-L43)) which in its constructor reserves native list capacity according to worst case, resulting in many megabytes of reserved memory. This multiplied by 20 causes tens of MBs of upfront memory allocation whenever RG Compilation Caching is enabled (which it is by default). The fix removes the upfront allocation from `ResourcesData` constructor based on a couple of observations: - I only touched `ResourcesData` because by my count, `ResourcesData.readerData` accounts for ~90% of all memory allocated in `CompilerContextData` constructor. - This change should be 100% safe & correct, because `ResourcesData.Initialize()` is called as the first step of NRP Compilation (by `NativePassCompiler.SetupContextData`), and that function resizes each array to the size it needs to be. - As a side effect of above, it is not required to estimate the amount of resources, since it is known by the time `Initialize` is called. This reduces unnecessary allocation of unused memory. - These are native memory allocations and do not generate garbage. When a new graph hash is encountered (meaning we must compile a graph), we take a new `CompilerContextData` from the pool and the resourceData arrays are allocated at that point. This is expected to have some small overhead, but likely negligible compared to the overhead already incurred by having to compile the graph. - In addition, it's good to be aware that `NativeList` always rounds requested capacity to the next power-of-two (`newCapacity = math.ceilpow2(newCapacity)`). So if you resize to 100000 elements, it will actually reserve memory for 131072 elements. Note that memory usage can clearly be improved further - I want to make the low-risk simple fix first and improve upon it afterwards.
1 parent 2d7d2b6 commit 71d2f57

File tree

4 files changed

+12
-11
lines changed

4 files changed

+12
-11
lines changed

Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/CompilerContextData.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ internal static int LastIndex<T>(this ref NativeList<T> list) where T : unmanage
4444
// Datastructure that contains passes and dependencies and allow you to iterate and reason on them more like a graph
4545
internal class CompilerContextData : IDisposable, RenderGraph.ICompiledGraph
4646
{
47-
public CompilerContextData(int estimatedNumPasses, int estimatedNumResourcesPerType)
47+
public CompilerContextData(int estimatedNumPasses)
4848
{
4949
passData = new NativeList<PassData>(estimatedNumPasses, AllocatorManager.Persistent);
5050
fences = new Dictionary<int, GraphicsFence>();
@@ -53,7 +53,7 @@ public CompilerContextData(int estimatedNumPasses, int estimatedNumResourcesPerT
5353
outputData = new NativeList<PassOutputData>(estimatedNumPasses * 2, AllocatorManager.Persistent);
5454
fragmentData = new NativeList<PassFragmentData>(estimatedNumPasses * 4, AllocatorManager.Persistent);
5555
randomAccessResourceData = new NativeList<PassRandomWriteData>(4, AllocatorManager.Persistent); // We assume not a lot of passes use random write
56-
resources = new ResourcesData(estimatedNumResourcesPerType);
56+
resources = new ResourcesData();
5757
nativePassData = new NativeList<NativePassData>(estimatedNumPasses, AllocatorManager.Persistent);// assume nothing gets merged
5858
nativeSubPassData = new NativeList<SubPassDescriptor>(estimatedNumPasses, AllocatorManager.Persistent);// there should "never" be more subpasses than graph passes
5959
createData = new NativeList<ResourceHandle>(estimatedNumPasses * 2, AllocatorManager.Persistent); // assume every pass creates two resources
@@ -183,7 +183,7 @@ public bool AddToFragmentList(TextureAccess access, int listFirstIndex, int numI
183183

184184
[MethodImpl(MethodImplOptions.AggressiveInlining)]
185185
public string GetResourceVersionedName(ResourceHandle h) => GetResourceName(h) + " V" + h.version;
186-
186+
187187
// resources can be added as fragment both as input and output so make sure not to add them twice (return true upon new addition)
188188
public bool AddToRandomAccessResourceList(ResourceHandle h, int randomWriteSlotIndex, bool preserveCounterValue, int listFirstIndex, int numItems)
189189
{

Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/NativePassCompiler.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,14 @@ internal struct RenderGraphInputInfo
2626
RenderGraphCompilationCache m_CompilationCache;
2727

2828
internal const int k_EstimatedPassCount = 100;
29-
internal const int k_EstimatedResourceCountPerType = 50;
3029
internal const int k_MaxSubpass = 8; // Needs to match with RenderPassSetup.h
3130

3231
NativeList<AttachmentDescriptor> m_BeginRenderPassAttachments;
3332

3433
public NativePassCompiler(RenderGraphCompilationCache cache)
3534
{
3635
m_CompilationCache = cache;
37-
defaultContextData = new CompilerContextData(k_EstimatedPassCount, k_EstimatedResourceCountPerType);
36+
defaultContextData = new CompilerContextData(k_EstimatedPassCount);
3837
toVisitPassIds = new Stack<int>(k_EstimatedPassCount);
3938
m_BeginRenderPassAttachments = new NativeList<AttachmentDescriptor>(FixedAttachmentArray<AttachmentDescriptor>.MaxAttachments, Allocator.Persistent);
4039
}

Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/ResourcesData.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ internal class ResourcesData
198198

199199
public DynamicArray<Name>[] resourceNames;
200200

201-
public ResourcesData(int estimatedNumResourcesPerType)
201+
public ResourcesData()
202202
{
203203
unversionedData = new NativeList<ResourceUnversionedData>[(int)RenderGraphResourceType.Count];
204204
versionedData = new NativeList<ResourceVersionedData>[(int)RenderGraphResourceType.Count];
@@ -207,10 +207,12 @@ public ResourcesData(int estimatedNumResourcesPerType)
207207

208208
for (int t = 0; t < (int)RenderGraphResourceType.Count; t++)
209209
{
210-
versionedData[t] = new NativeList<ResourceVersionedData>(MaxVersions * estimatedNumResourcesPerType, AllocatorManager.Persistent);
211-
unversionedData[t] = new NativeList<ResourceUnversionedData>(estimatedNumResourcesPerType, AllocatorManager.Persistent);
212-
readerData[t] = new NativeList<ResourceReaderData>(MaxVersions * estimatedNumResourcesPerType * MaxReaders, AllocatorManager.Persistent);
213-
resourceNames[t] = new DynamicArray<Name>(estimatedNumResourcesPerType); // T in NativeList<T> cannot contain managed types, so the names are stored separately
210+
// Note: All these lists are allocated with zero capacity, they will be resized in Initialize when
211+
// the amount of resources is known.
212+
versionedData[t] = new NativeList<ResourceVersionedData>(0, AllocatorManager.Persistent);
213+
unversionedData[t] = new NativeList<ResourceUnversionedData>(0, AllocatorManager.Persistent);
214+
readerData[t] = new NativeList<ResourceReaderData>(0, AllocatorManager.Persistent);
215+
resourceNames[t] = new DynamicArray<Name>(0); // T in NativeList<T> cannot contain managed types, so the names are stored separately
214216
}
215217
}
216218

Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraphCompilationCache.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public RenderGraphCompilationCache()
3838
for (int i = 0; i < k_CachedGraphCount; ++i)
3939
{
4040
m_CompiledGraphPool.Push(new RenderGraph.CompiledGraph());
41-
m_NativeCompiledGraphPool.Push(new CompilerContextData(NativePassCompiler.k_EstimatedPassCount, NativePassCompiler.k_EstimatedResourceCountPerType));
41+
m_NativeCompiledGraphPool.Push(new CompilerContextData(NativePassCompiler.k_EstimatedPassCount));
4242
}
4343
}
4444

0 commit comments

Comments
 (0)