perf: enumerate result pages without boxing - #7
Closed
msallin wants to merge 1 commit into
Closed
Conversation
SvgResult and PngResult forwarded GetEnumerator to the list behind their Pages property. The list hands its enumerator back as an IEnumerator<T>, so every foreach boxed the struct: 80 bytes measured for one pass over an SVG result and a PNG result. A struct enumerator, which foreach binds to in preference to the interface, removes that. The interface implementations stay for LINQ and IEnumerable callers.
msallin
force-pushed
the
perf/result-struct-enumerators
branch
from
September 6, 2026 19:16
e24d3f8 to
44d24bf
Compare
Member
Author
|
Reopened upstream against the parent repository: evolvedlight#50 |
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.
SvgResultandPngResultimplementIReadOnlyList<T>by forwardingGetEnumerator()to the list behind theirPagesproperty. BecausePagesis typed as the interface, the list hands its enumerator back as anIEnumerator<T>, so the struct is boxed on everyforeach. Measured: 80 bytes for one pass over anSvgResultand aPngResult, 40 each.A struct enumerator, which
foreachbinds to in preference to the interface, takes that to zero. The interface implementations remain, now explicit, so LINQ andIEnumerablecallers are unaffected.Adds one public type,
PageEnumerator<T>, shared by both results rather than duplicated as two nested structs.Compatibility
Source-compatible:
foreach, LINQ,Countand the indexer all keep compiling, andresult.GetEnumerator()still converts toIEnumerator<T>where a caller assigned it to one.Binary-breaking: the public
IEnumerator<T> GetEnumerator()becomes an explicit interface implementation. This needs a version bump rather than a patch release, so it should be scheduled rather than merged alongside the other allocation fixes.Tests
EnumeratingResultPagesDoesNotAllocateasserts zero allocations across one pass over both result types. Confirmed failing at exactly 80 bytes against the previous implementation.ResultPagesEnumerateInIndexOrderThroughBothPathspins that the struct enumerator agrees with the indexer, and that the interface path LINQ uses still yields the same pages.58/58 tests pass. Builds clean on net8, net9 and net10 with zero warnings.