Skip to content

Resolve the config section once per type (P5) - #26

Merged
guy-lud merged 1 commit into
masterfrom
perf/p5-resolve-section-once
Jul 13, 2026
Merged

Resolve the config section once per type (P5)#26
guy-lud merged 1 commit into
masterfrom
perf/p5-resolve-section-once

Conversation

@guy-lud

@guy-lud guy-lud commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

P5 — resolve the config section once per type

ConfigurationBinder.BindPropertySettings called _configuration.GetSection(...) on every property, though the section is constant per settings type. This caches the resolved IConfigurationSection per section name and reuses it.

The change

  • ConfigurationBinder gains a private readonly ConcurrentDictionary<string, IConfigurationSection>; BindPropertySettings resolves via a zero-capture GetOrAdd(context.Section, static (name, self) => self.ResolveSection(name), this) — a capturing lambda would allocate a fresh 64 B delegate per call (measured).
  • Reload-safe: GetSection returns a live view over the configuration root, so the cached section re-reads the providers on each access (locked by a test).
  • Dropped the dead ?. (GetSection is contractually non-null); stripped a stray UTF-8 BOM.

Why an internal cache, not a contract change

The plan was reviewed by architect / perf / security specialists up front. Threading a resolved section through the public ISectionBinder / BindingContext contract would be a layering violation — Core must not reference Microsoft.Extensions.Configuration — and the optimization is single-implementer (env / command-line / in-memory binders are flat (section, key) lookups). So the cache lives on the binder.

Proof

New gated ConfigBinderBenchmark (ShortRun, net10):

Before After Δ
BindNoRoot 80 B 40 B −50%
BindWithRoot 144 B 56 B −61%

Matches the perf review's predicted −40 B / −88 B deltas. The residual is the irreducible "Section:Key" path string (provider dictionaries are string-keyed; values can't be cached since config is reload-live).

Tests

+3 in ConfigurationBinderCacheTests.cs: multi-property (with + without RootSection) proving every property resolves from the cached section, and Bind_CachedSection_ReflectsLaterConfigChange proving the cache stays a live view. Suite: 71 per TFM.

Also in this PR

  • Wired P4's ConvertArrayBenchmark into the CI filter — it existed but was never actually gated.
  • Added Microsoft.Extensions.Configuration to the benchmark project.
  • Refreshed handoff/fix-plan and logged a pre-existing security finding from the review (S1): Resources.cs:34-36 interpolates the raw bound value into SettingsPropertyValueException, which can leak secrets into logs — a separate follow-up, not addressed here.

Review

Plan reviewed by architect / perf / security; code reviewed via /code-review (clean).

Like other new benchmarks, ConfigBinderBenchmark has no gh-pages baseline until the first master run, so this PR isn't gated on it; before/after captured above.

ConfigurationBinder called _configuration.GetSection(...) on every property,
though the section is constant per settings type. Cache the resolved
IConfigurationSection per section name in a ConcurrentDictionary, using a
zero-capture GetOrAdd (static factory + factoryArgument) so no per-call
delegate is allocated. Reload-safe: GetSection returns a live view over the
configuration root, so the cached section re-reads providers on each access.
Drop the dead ?. (GetSection never returns null) and strip a stray BOM.

Chosen over threading the section through the ISectionBinder contract: that
would be a layering violation (Core must not reference
Microsoft.Extensions.Configuration) and the optimization is single-implementer.
Plan reviewed by the architect/perf/security agents; code reviewed via
/code-review.

Proof (new gated ConfigBinderBenchmark): BindNoRoot 80->40 B (-50%),
BindWithRoot 144->56 B (-61%). Adds 3 tests (multi-property with/without
RootSection, plus a cached-section-reflects-later-change live-view test). Also
wires P4's ConvertArrayBenchmark into the CI filter (it was never gated) and
adds Microsoft.Extensions.Configuration to the benchmark project.

Refreshes SESSION-HANDOFF.md + FIX-PLAN.md and logs a pre-existing secret-leak
finding surfaced by the security review (S1: Resources.cs interpolates the raw
bound value into the SettingsPropertyValueException message).

Suite: 71 tests per TFM.
@guy-lud
guy-lud merged commit 498fc81 into master Jul 13, 2026
2 checks passed
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