Skip to content

[performance] Avoid intermediate string allocation in GetEncodedText null-terminated path - #4498

Merged
mattleibow merged 1 commit into
mainfrom
dev/perf-getencodedtext-nullterm-d58277bc562b6870
Jul 22, 2026
Merged

[performance] Avoid intermediate string allocation in GetEncodedText null-terminated path#4498
mattleibow merged 1 commit into
mainfrom
dev/perf-getencodedtext-nullterm-d58277bc562b6870

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

What changed

StringUtilities.GetEncodedText(text, encoding, addNull: true) previously appended the NUL terminator with text += "\0", allocating a whole new managed string (heap object + char copy) before encoding. This PR encodes the original text directly into a byte[byteCount + nullBytes] buffer and leaves the trailing bytes zero.

Invariant that keeps it correct: a '\0' char encodes to exactly GetCharacterByteSize zero bytes in each encoding (UTF‐8 = 1, UTF‐16 = 2, UTF‐32 = 4), and encoding the text without the trailing '\0' produces the identical prefix bytes. So the result is byte-for-byte identical, with one fewer allocation and one fewer copy. Empty/null input still returns an empty array (no terminator), matching the original !string.IsNullOrEmpty guard.

Files:

  • binding/SkiaSharp/Util.cs — the fix (body-only, ABI-safe; unused NullTerminator const removed).
  • tests/Tests/SkiaSharp/StringUtilitiesTest.cs — equivalence test.
  • benchmarks/SkiaSharp.Benchmarks/Benchmarks/GetEncodedTextBenchmark.cs — New-vs-Old benchmark.

Proof — faster

net10.0, AMD EPYC 7763, BenchmarkDotNet 0.15.8, UTF‐8 with addNull, two runs agree:

dotnet run -c Release --project benchmarks/SkiaSharp.Benchmarks -- --filter '*GetEncodedTextBenchmark*'
Text Old New Ratio Old alloc New alloc
Arial 23.38 ns 15.77 ns 0.67 72 B 32 B
Segoe UI Symbol 26.48 ns 18.85 ns 0.71 96 B 40 B
`(redacted) [35] 31.25 ns 21.14 ns 0.68 160 B 64 B

~30 % faster, ~55–60 % fewer bytes allocated, no allocation regression.

Proof — identical

StringUtilitiesTest compares the new path byte-for-byte against the original text + '\0' semantics across UTF‐8/16/32 and edge inputs: null, empty, embedded \0, valid \uD83D\uDE00 and lone surrogates, BOM, and a 1000-char string. It also asserts the addNull vs non-addNull outputs differ by exactly one encoded null character, so the test catches a deliberately-wrong result.

Scope note

Managed-C#-only, binding/SkiaSharp/Util.cs; no public signature change (method body only), ABI-safe. Numbers are empirically measured on the named hardware/TFM. Repo NuGet feeds (Azure DevOps) were firewall-blocked in the workflow environment, so both proofs were run with standalone projects containing the verbatim Old/New bodies; the committed test + benchmark run in CI. Behaviour is unchanged — SkiaSharp still renders identically.

Produced by the performance-fixer agentic workflow + performance-fixer skill.

Fixes #4497

Generated by Fixer - Performance · ● 24.5M ·

…d path

Encode text directly into a byte[byteCount + nullBytes] buffer instead of
concatenating a '\0' onto the source string before encoding. The trailing
zero bytes match the encoding of a '\0' exactly (UTF-8=1, UTF-16=2,
UTF-32=4 bytes), so output is byte-identical while removing one managed
string allocation and copy per call on the native-marshalling hot path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added partner/agentic-workflows Issues and PRs created by SkiaSharp agentic workflows. perf/allocations Excessive managed allocations or per-frame GC/heap churn. Implies tenet/performance. tenet/performance Performance related issues labels Jul 21, 2026
@mattleibow
mattleibow marked this pull request as ready for review July 22, 2026 21:48
@mattleibow
mattleibow merged commit 90c625d into main Jul 22, 2026
86 checks passed
@mattleibow
mattleibow deleted the dev/perf-getencodedtext-nullterm-d58277bc562b6870 branch July 22, 2026 21:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

partner/agentic-workflows Issues and PRs created by SkiaSharp agentic workflows. perf/allocations Excessive managed allocations or per-frame GC/heap churn. Implies tenet/performance. tenet/performance Performance related issues

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[performance] Avoid intermediate string allocation in StringUtilities.GetEncodedText null-terminated path

1 participant