Commit d40e31f
[Text] Unicode and text layout optimizations (AvaloniaUI#21400)
* perf(text): O(1) width queries on ShapedBuffer, cached LB-class lookup
- ShapedBuffer: lazy, pooled cluster-prefix cache shared across Split;
adds TotalGlyphAdvance / MeasureCharactersThatFit.
- GlyphRun/TextFormatterImpl/Skia GlyphRunImpl: read the cache, drop
duplicate scans; SplitTextRuns now returns firstLength via out.
- LineBreakEnumerator: cache Next/PreviousClass per advance; remove
unused LineBreakPairTable.
- Tests + benchmarks for the new paths and a dotnet-trace harness.
~1.32x faster / -5% alloc on the emoji-wrap micro-benchmark; no
observable behaviour or public API change.
* perf(TextFormatting): inline LineBreakEnumerator rule dispatch
Replace the BreakUnitDelegate[] s_rules array dispatch in
LineBreakEnumerator.ExecuteRules with a sequence of direct static
calls and a `goto Done` early-exit. This removes 42 indirect calls
per codepoint and lets the JIT reason across rule boundaries, which
in turn makes [MethodImpl(AggressiveInlining)] meaningful — JIT
cannot inline through delegate.Invoke, so the attribute was a no-op
in the previous shape.
Selectively apply AggressiveInlining to the small single-condition
rules (LB03, LB04, LB06, LB07, LB08a, LB11–LB15d, LB18, LB20,
LB21b, LB22, LB29, LB31) and let the JIT decide on the larger ones
to avoid bloating the merged ExecuteRules.
The static BreakUnitDelegate[] s_rules array is removed.
Benchmarks (BDN default job, --inProcess, N=13–22, rel. StdDev <1.5%):
UnicodeBreakEnumeratorBenchmark.LineBreakEnumerator_Sequence
Ascii 154.6 µs -> 24.84 µs (6.22x)
Bmp 190.2 µs -> 32.81 µs (5.80x)
Supplementary 227.6 µs -> 37.56 µs (6.06x)
TextLayoutProfile.BuildEmojisWrapped
Before (branch, pre-inline) 804.5 µs / 570.15 KB
After (branch, post-inline) 547.0 µs / 570.15 KB (-32.0%)
vs upstream/master (1061 µs) -> ~1.94x total speedup
No allocation change; CPU-only dispatch reshape. All
LineBreakEnumerator unit tests pass (5/5).
* Refactor ShapedBuffer to share pool storage via IRef + generation counter
Wrap the ArrayPool-rented glyph and cluster-cache arrays in a small
PooledArray<T> disposable and expose them through IRef<T>. Split children
and WithBidiLevel aliases now Clone() the refs instead of borrowing raw
pool arrays, so the backing storage survives until every sibling has
been disposed - eliminating the UAF risk that existed when a parent was
disposed before its children.
Add a per-glyph-holder generation counter (Volatile.Read / Interlocked
.Increment). The indexer setter bumps the counter on every write, and
EnsureClusterCache compares its recorded generation against the holder's
current value, rebuilding on mismatch. This lets us drop the previous
"no mutation after Split/WithBidiLevel" contract: mutations performed
through any sibling now propagate to the others' caches transparently.
Dispose is made idempotent via a _disposed guard so overlapping cache
eviction and TextLine teardown only release the IRefs once.
Adds ShapedBufferSharedStorageTests covering sibling lifetime,
Dispose idempotency, and generation-driven cache invalidation across
Split children and WithBidiLevel aliases.
* test(ShapedBuffer): cover cluster-cache sharing across Split/WithBidiLevel aliases
Add a regression test hook (`ClusterPrefix`) exposing the
backing cluster-prefix array reference, plus two tests that mutate a
parent buffer before aliasing it and assert the alias reuses the
parent's pooled prefix array instead of rebuilding. Guards against
forgetting to propagate `_cacheGeneration` to alias buffers, which
would silently defeat the cached-split fast path.
* Correctly use MathUtilities.LessThanOrClose
---------
Co-authored-by: Julien Lebosquain <julien@lebosquain.net>1 parent 1a43ec2 commit d40e31f
23 files changed
Lines changed: 3462 additions & 356 deletions
File tree
- src
- Avalonia.Base/Media
- TextFormatting
- Unicode
- Skia/Avalonia.Skia
- tests
- Avalonia.Base.UnitTests/Media/TextFormatting
- Avalonia.Benchmarks
- Text
- Avalonia.Skia.UnitTests/Media/TextFormatting
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
224 | 224 | | |
225 | 225 | | |
226 | 226 | | |
| 227 | + | |
| 228 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
684 | 684 | | |
685 | 685 | | |
686 | 686 | | |
687 | | - | |
688 | 687 | | |
689 | 688 | | |
690 | 689 | | |
691 | | - | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
692 | 700 | | |
693 | | - | |
| 701 | + | |
694 | 702 | | |
695 | | - | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
696 | 707 | | |
697 | 708 | | |
698 | 709 | | |
| |||
Large diffs are not rendered by default.
Lines changed: 109 additions & 87 deletions
Large diffs are not rendered by default.
Lines changed: 202 additions & 150 deletions
Large diffs are not rendered by default.
Lines changed: 0 additions & 74 deletions
This file was deleted.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
5 | 4 | | |
6 | 5 | | |
7 | 6 | | |
| |||
39 | 38 | | |
40 | 39 | | |
41 | 40 | | |
42 | | - | |
43 | | - | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
44 | 46 | | |
45 | 47 | | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
| 48 | + | |
54 | 49 | | |
55 | 50 | | |
56 | 51 | | |
| |||
67 | 62 | | |
68 | 63 | | |
69 | 64 | | |
70 | | - | |
71 | 65 | | |
72 | 66 | | |
73 | | - | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
74 | 70 | | |
75 | | - | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
76 | 77 | | |
77 | | - | |
78 | | - | |
79 | | - | |
80 | | - | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
81 | 83 | | |
82 | | - | |
| 84 | + | |
83 | 85 | | |
84 | | - | |
85 | | - | |
86 | | - | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
87 | 90 | | |
88 | | - | |
89 | | - | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
90 | 98 | | |
91 | 99 | | |
92 | 100 | | |
| |||
0 commit comments