Skip to content

perf: reduce collection humanization allocations - #2

Open
artl93 wants to merge 1 commit into
mainfrom
artl93/perf-bench
Open

perf: reduce collection humanization allocations#2
artl93 wants to merge 1 commit into
mainfrom
artl93/perf-bench

Conversation

@artl93

@artl93 artl93 commented Feb 12, 2026

Copy link
Copy Markdown
Owner

Summary

Write built-in collection formats directly into the final result string for collections with three or more displayable items. This removes the intermediate StringBuilder, its backing buffer, and the intermediate joined string while preserving the existing two-item fast path and custom-format fallback.

This fork PR was reduced from 12 unrelated files to one coherent public API path after rebasing onto main at f9292aa90948de0aea2d4fa7d6549b1b2432c0fb.

Scope

Retained and redesigned

  • DefaultCollectionFormatter: direct string.Create output for the two built-in formats ({0} {1} {2} and Oxford {0}, {1} {2}).
  • Focused tests for four-item Oxford and non-Oxford output.

Dropped

  • String and TimeSpan LINQ changes: overlap conceptually with fork PR perf: skip metric name normalization for symbol inputs #1.
  • English fixed array: increased allocations for common small values.
  • Locale-specific NumberToWords/Frozen collection changes: cold/init or narrow paths without sufficient realistic evidence.
  • Truncator concatenation changes: too small and disconnected from the selected optimization.

Benchmark methodology

Temporary change-validation harness outside the repository, using BenchmarkDotNet 0.15.8 and the default out-of-process toolchain (not the repo benchmark project's global InProcessEmitToolchain).

  • Baseline: saved Humanizer.dll from f9292aa90948de0aea2d4fa7d6549b1b2432c0fb
  • Candidate: same worktree contents later committed as 3245e30044359fe8ecea61d0a49cd50b027d775d
  • Both DLLs built with SDK 11.0.100-preview.3.26207.106
  • Both benchmark jobs built with SDK 10.0.300 and run on .NET 10.0.8, Arm64 RyuJIT, concurrent workstation GC
  • Apple M5 Pro, macOS Tahoe 26.5.2
  • Job.MediumRun: 2 process launches, 10 warmups, 15 measured iterations
  • MemoryDiagnoser, identical jobs, full JSON/CSV/GitHub Markdown exports
  • Public API: string[].Humanize() under English (Oxford) and French (non-Oxford) cultures
  • Sizes: 2 (regression guard), 3 (common), 10 (medium), 100 (large)
  • Repeated workload: 100 calls distributed 60% size 2, 25% size 3, 10% size 5, 5% size 10; reported per operation

Exact final command:

dotnet run -c Release --no-build -- --filter '*' --artifacts ./artifacts-final --noOverwrite

--apples was also attempted, but BenchmarkDotNet 0.15.8 crashed after its pilot phase on this parameterized suite with Sequence contains more than one matching element; final claims use the successful MediumRun above.

Full results

Culture Items Baseline Candidate Ratio Baseline alloc Candidate alloc Alloc ratio
en-US 2 46.06 ± 1.340 ns 44.98 ± 0.758 ns 0.98 192 B 192 B 1.00
fr-FR 2 48.51 ± 1.261 ns 46.20 ± 0.751 ns 0.95 192 B 192 B 1.00
en-US 3 90.34 ± 0.859 ns 52.65 ± 1.392 ns 0.58 384 B 224 B 0.58
fr-FR 3 63.51 ± 0.721 ns 48.09 ± 1.519 ns 0.76 376 B 216 B 0.57
en-US 10 166.42 ± 2.798 ns 94.66 ± 1.026 ns 0.57 1,104 B 392 B 0.36
fr-FR 10 138.85 ± 0.625 ns 86.39 ± 0.714 ns 0.62 1,096 B 384 B 0.35
en-US 100 859.13 ± 14.793 ns 724.62 ± 15.736 ns 0.84 7,072 B 2,736 B 0.39
fr-FR 100 798.18 ± 18.921 ns 618.15 ± 2.738 ns 0.78 7,064 B 2,728 B 0.39
en-US distribution repeated 75.22 ± 2.453 ns 56.50 ± 2.837 ns 0.75 387 B 282 B 0.73

The unchanged two-item path has identical allocation and timing within/near measurement noise. No measured case regressed. Collections of 3+ items improve 16–43% in mean throughput and reduce allocation by 42–65%; the representative distribution improves 25% with 27% fewer allocated bytes.

Validation

  • Focused collection tests: 50 passed
  • Humanizer.Tests, net10.0: 56,561 passed
  • Humanizer.Tests, net8.0: 56,561 passed
  • Current documented pack command was run. The exact command is blocked on synchronized main by pre-existing CRLF IDE0055 errors in untouched EnumDehumanizeExtensions.cs and PolyfillShims.cs.
  • Re-running dotnet pack src/Humanizer/Humanizer.csproj -c Release -o <path> while suppressing only pre-existing IDE0055, CS1573, and CS1591 diagnostics built all five library TFMs and produced Humanizer.3.5.0-preview.52.gf9292aa909.nupkg.

No upstream PR, issue comment, or other submission was created.

Write built-in collection formats directly into the result string, avoiding the intermediate StringBuilder and joined string for collections with three or more displayable items.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ad56389e-2890-49a6-a52f-76ee42a29891
@artl93
artl93 force-pushed the artl93/perf-bench branch from dc8274c to 3245e30 Compare July 17, 2026 22:11
@artl93 artl93 changed the title perf: reduce allocations and GC pressure in hot paths perf: reduce collection humanization allocations Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant