Skip to content

perf: reduce runtime Refit memory allocations#2263

Merged
glennawatson merged 1 commit into
mainfrom
perf/runtime-allocations
Jul 20, 2026
Merged

perf: reduce runtime Refit memory allocations#2263
glennawatson merged 1 commit into
mainfrom
perf/runtime-allocations

Conversation

@glennawatson

@glennawatson glennawatson commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Performance / refactor of the runtime Refit library. No behavioural change and no public API change.

What is the new behavior?

Reduced memory allocations across the runtime request/response/serialization hot paths, guided by a new GcVerbose micro-benchmark suite and verified behaviour-identical:

  • Reflection form mapping caches per-type attribute metadata (FormValueMultimap.Create 2736 -> 776 B).
  • Path building escapes catch-all sections inline from the source span under the default formatter (RoundTripEscapePath 1416 -> 72 B).
  • Parameter formatting renders values without a composite string.Format and skips the query-attribute array when none is defined (string values 328 -> 0 B; the int/date/guid/enum paths drop sharply).
  • Enum handling builds the camelCase name in a single allocation, builds the serialized-name map without a per-field iterator, and scans the enum's fields once for all three converter maps (ToCamelCase 104 -> 56 B; converter construction 1624 -> 1178 B).
  • Adds a Refit.Benchmarks GcVerbose micro-benchmark suite (97 benchmarks across 10 components) covering the hot path, plus guard tests pinning each optimised method's functional contract; the relevant private members are widened to internal (fields stay private) so the benchmarks can reach them.

What is the current behavior?

The runtime paths above materialised more short-lived allocations than necessary - composite-format boxing, per-section substrings, repeated reflection reads, and intermediate strings in the enum converter build.

What might this PR break?

None. Behaviour and serialised output are unchanged, verified by the full Refit.Tests suite (1161 passing) plus new guard tests pinning each optimised method's contract. No public API changes; the widened members are internal.

Checklist

  • I have read the Contribute guide
  • Tests have been added or updated (for bug fixes / features)
  • Docs have been added or updated (for bug fixes / features)
  • Changes target the main branch
  • PR title follows Conventional Commits

Additional information

Allocation measured with BenchmarkDotNet (Allocated, deterministic) plus EventPipe GcVerbose traces. Selected per-method reductions (bytes/op, net10):

Method Before After
FormValueMultimap.Create 2736 776
RoundTripEscapePath 1416 72
DefaultUrlParameterFormatter.Format (string) 328 0
DefaultUrlParameterFormatter.Format (Guid) 440 128
ToCamelCase 104 56
Enum converter construction 1624 1178

A reflection census confirmed the runtime library is source-gen-first (the generated System.Text.Json path is reflection-free); the residual reflection is confined to dynamic by-Type entry points and deliberate fallbacks and is cached to zero allocation per request. Remaining allocations were each measured and left only where irreducible (for example a value-type box forced by the object-returning reflection API on the fallback path, or framework HttpHeaders value storage).

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (8cc1c37) to head (7f7b971).

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #2263   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          181       183    +2     
  Lines         9723      9739   +16     
  Branches      1861      1865    +4     
=========================================
+ Hits          9723      9739   +16     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Cut allocations across the runtime request/response/serialization hot paths,
behaviour-identical (1161 tests green), guided by GcVerbose micro-benchmarks.

- Reflection form mapping: cache per-type attribute metadata; FormValueMultimap.Create
  2736 -> 776 B.
- Path building: escape catch-all sections inline from the source span under the
  default formatter; RoundTripEscapePath 1416 -> 72 B.
- Parameter formatting: render values without a composite string.Format and skip the
  query-attribute array when none is defined; string values 328 -> 0 B, and the
  int/date/guid/enum paths drop sharply.
- Enum handling: build the camelCase name in a single allocation, build the
  serialized-name map without a per-field iterator, and scan the enum's fields once
  for all three converter maps; ToCamelCase 104 -> 56 B, converter construction
  1624 -> 1178 B.
- Add a Refit.Benchmarks GcVerbose micro-benchmark suite (97 benchmarks across 10
  components) covering the hot path, plus guard tests pinning each optimised method's
  functional contract, and widen the relevant private members to internal (fields
  stay private) so the benchmarks can reach them.

No public API change; behaviour and serialised output are unchanged.
@glennawatson
glennawatson force-pushed the perf/runtime-allocations branch from 9750cd2 to 7f7b971 Compare July 20, 2026 05:44
@sonarqubecloud

Copy link
Copy Markdown

@glennawatson
glennawatson merged commit 8cea3f1 into main Jul 20, 2026
17 checks passed
@glennawatson
glennawatson deleted the perf/runtime-allocations branch July 20, 2026 07:36
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.

2 participants