Replace Array.FindIndex with for loop to avoid closure allocation#94
Open
FMsongX2 wants to merge 1 commit into
Open
Replace Array.FindIndex with for loop to avoid closure allocation#94FMsongX2 wants to merge 1 commit into
FMsongX2 wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces
Array.FindIndex(..., lambda)calls on the per-frame render path with allocation-freeforloops. The lambdas capture a loop variable, so each call allocates aPredicate<T>delegate. Behavior is unchanged; only the per-frame garbage is removed.Problem
In
CubismRenderController.OnDynamicDrawableData— invoked fromCubismModel.Update()every frame — the renderer for each drawable is located with:This method has three loops over all drawables (visibility/order/opacity, multiply color, screen color), and each runs this
FindIndexonce per drawable before any dirty check — so it executes3 × drawableCounttimes every frame. The lambda captures the loop variabledataIndex, so the compiler cannot cache it as a static delegate (only non-capturing lambdas are cached); aPredicate<T>delegate is allocated on every call.CubismRenderingInterceptController.TryDrawhas the same pattern, capturingcurrentCamera.Measurement
Compiled and run on .NET 10, Release (desktop CLR), measured with
GC.GetAllocatedBytesForCurrentThread()over 100,000 calls:Array.FindIndex+ capturing lambdaforloopArray.FindIndex+ non-capturing lambda (control)The non-capturing control confirms the cause: capturing the loop variable defeats the compiler's delegate caching. The same allocation is produced under C# 7.3, 9, and 13 (identical bytes), so this is language semantics, not a compiler-version optimization. In the same harness the explicit loop was also faster (no per-element delegate indirection), so this is not an allocation-for-speed trade-off.
Runtime note: this was measured on desktop .NET, not inside Unity. Mono / IL2CPP were not directly measured, but the generated IL contains a per-call delegate
newobj, so a per-call allocation occurs there too — only the byte size differs.Changes
CubismRenderController.OnDynamicDrawableData: the three loops perform the same search (byDrawable.UnmanagedIndexover the samerenderersarray), so they now call one shared, allocation-freeIndexOfDrawablehelper.CubismRenderingInterceptController.TryDraw: a single search over a different array (_cameraDrawStatus, keyed byCamera), with no reuse — so it is inlined as aforloop rather than sharing the helper.Why this is safe
Array.FindIndex: returns the first matching index, or-1when none matches. No change to iteration order or to the existingrendererIndex < 0/statusIndex < 0handling.-1, same as before. (renderersand_cameraDrawStatusare always initialized on these paths, so the null-array case does not arise.)Drawable.UnmanagedIndex— it does not assume positional alignment betweenrenderersanddata, so the search behavior is unchanged.