Zero-alloc fast path for single-line text wrapping (Part of #1934)#3538
Merged
Conversation
The RelativeToChildren natural-size measurement re-wraps a Text every layout frame by toggling its Width (unconstrained to read natural size, then back). IWrappedTextExtensions.UpdateLines re-tokenized/Split/concatenated the string on every call — allocating even when the text fits on one line and the wrapped result is the raw text itself. Add a fast path: when a string has no explicit newline and already fits within the wrap width, add it as the single line directly and return, with zero managed allocation. The word-by-word algorithm reproduces the raw string verbatim in that case (interior/leading/trailing spaces included, per #2617), so this is behavior-preserving. MeasureString is allocation-free for text without inline runs. Impact: realistic Forms full-relayout drops 1,584 -> 240 bytes/frame (85%); a single-line UpdateWrappedText drops 288 -> 0 bytes/call. Ratchet in RealisticLayoutAllocationTests tightened 2,200 -> 400; new red-green unit test TextTests.UpdateWrappedText_WhenTextFitsOnOneLine_DoesNotAllocate pins the zero-alloc contract. Boyscout: removed dead 'stringToUse' recompute and unused 'returnString' local. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Part of #1934 (runtime allocation pass). Continues the ratchet after the idle
Drawpass (#3514); this one targets the full-relayout Text path the handoff comment flagged as next.Finding
Profiling the realistic-Forms full-relayout scenario showed 1,504 of 1,584 bytes/frame (95%) came from text wrapping, from just 4 wrap calls/frame — all the same unchanged
TextBoxstring, re-wrapped with itsWidthtoggling ∞↔90 by theRelativeToChildrennatural-size measurement (measure unconstrained, then restore).IWrappedTextExtensions.UpdateLinesre-tokenized/Split/concatenated that string on every call even though it fits on one line and the result is the raw string itself.Fix
A single-line fast path in
UpdateLines: when a string has no explicit newline and already fits within the wrap width, add it as the one line and return — zero managed allocation. The word-by-word algorithm reproduces the raw string verbatim in that case (interior/leading/trailing spaces included, per #2617), so it's behavior-preserving.MeasureStringis allocation-free for text without inline runs (it sums glyph advances).Impact
UpdateWrappedTexton a fitting single lineThe residual 240 B/f is the one genuinely-wrapping Text (the
TextBoxcontent re-wrapping at its constrained width) — the next ratchet target (making the word-by-word path itself allocation-free).Tests (TDD, red→green)
TextTests.UpdateWrappedText_WhenTextFitsOnOneLine_DoesNotAllocate— asserted288 → 0(failed red before the fast path).RealisticLayoutAllocationTeststightened 2,200 → 400 (pins 240).Boyscout: removed a dead
stringToUserecompute (Replaceran twice) and an unusedreturnStringlocal in the same method.