[performance] Remove per-call char boxing in SKFourByteTag/Tag ToString - #4752
Draft
github-actions[bot] wants to merge 1 commit into
Draft
[performance] Remove per-call char boxing in SKFourByteTag/Tag ToString#4752github-actions[bot] wants to merge 1 commit into
github-actions[bot] wants to merge 1 commit into
Conversation
string.Concat(char,char,char,char) binds to Concat(object,...) and boxes every char (4 allocations/call). Write into a stackalloc buffer and materialize once via new string(char*,0,4) instead: ~6x faster, 280->32 bytes/op, bit-identical output on all TFMs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.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.
Changes
SKFourByteTag.ToString()and the general path ofHarfBuzzSharp.Tag.ToString()formatted their 4-character tag withstring.Concat((char),(char),(char),(char)). There is no 4-charstring.Concatoverload, so the call bound tostring.Concat(object, object, object, object)and boxed each char — four throwaway allocations per call on top of the result string.Both now write the four chars into a
stackallocbuffer and materialize the string once vianew string(char*, 0, 4). This constructor exists on every TFM (net10.0 / netstandard2.0 / net4x), so there is no#if. Body-only change — no public signature changed (ABI-safe).Invariant that keeps it correct: each output char is exactly
(char)(byte)(value >> shift)in the same big-endian order and the string is exactly 4 chars long — identical to the originalConcat. The HarfBuzzNone/Max/MaxSignednamed special-cases are untouched and still take their early-return path.Required skia PR
None.
Areas Affected
SKFourByteTag)Tag)Proof — faster
In-process New-vs-Old on net10.0 / Linux x64, batch of 12 OpenType tags:
string.Concat)stackalloc)Runs: 5.91x / 6.17x / 5.84x; allocations 280->32 bytes/op (the 4 char boxes removed). A committed BenchmarkDotNet harness reproduces this:
Proof — identical
New
ToStringequivalence tests compare the optimized output against the verbatim originalstring.Concatoracle:tests/Tests/SkiaSharp/SKFourByteTagTest.cs— happy path, space-padding ("a ","ab "), NUL, control chars (\t\r\n), >0xFF low-byte truncation, all-0xFF; plus a deliberately-wrong-result guard.tests/Tests/HarfBuzzSharp/HBTagTest.cs— same coverage, plus theNone/Max/MaxSignednamed special-cases must still round-trip, plus a wrong-result guard.Cross-TFM compilation verified on net10.0, netstandard2.0, and net462.
Testing
dotnet testgate and BenchmarkDotNet harness could not run in this sandbox (network/execution restrictions blockedexternals-download); the change is pure managed char/int math with no P/Invoke, so native binaries are not required to measure or validate it. Please run the full suite in CI.Produced by the performance-fixer agentic workflow +
performance-fixerskill. Empirically measured: time + allocations; statically reasoned: cross-runtime bit-exactness. No ABI impact (method bodies only). Labelperf/allocationsreflects the dominant measured driver (removed char boxing).Fixes #4751