Skip to content

Reduce allocations in Microsoft.Extensions.Configuration - #133494

Merged
rosebyte merged 3 commits into
dotnet:mainfrom
prozolic:configuration
Sep 11, 2026
Merged

rosebyte merged 3 commits into
dotnet:mainfrom
prozolic:configuration

Conversation

@prozolic

@prozolic prozolic commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Give the provider lists an initial capacity so List<T> does not reallocate its backing array while it grows in the Microsoft.Extensions.Configuration APIs.

Changed:

  • ConfigurationBuilder.Build
  • ConfigurationManager.ReloadSources
  • ReferenceCountedProviderManager.AddProvider

Benchmark

Method Job Toolchain SourceCount Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
Builder_Build Job-TDYACH \main\corerun.exe 4 918.1 ns 17.28 ns 19.90 ns 1.00 0.00 0.2956 0.0019 2480 B 1.00
Builder_Build Job-DXRHTD \pr\corerun.exe 4 838.9 ns 16.61 ns 35.76 ns 0.91 0.04 0.2956 0.0019 2480 B 1.00
Manager_AddSources Job-TDYACH \main\corerun.exe 4 2,032.7 ns 40.63 ns 93.36 ns 1.00 0.00 0.5379 0.0057 4512 B 1.00
Manager_AddSources Job-DXRHTD \pr\corerun.exe 4 1,812.1 ns 31.30 ns 41.79 ns 0.89 0.05 0.5093 0.0057 4264 B 0.95
Manager_ReloadSources Job-TDYACH \main\corerun.exe 4 1,329.2 ns 26.31 ns 70.69 ns 1.00 0.00 0.3548 0.0038 2968 B 1.00
Manager_ReloadSources Job-DXRHTD \pr\corerun.exe 4 1,261.4 ns 24.85 ns 37.19 ns 0.95 0.06 0.3452 0.0038 2888 B 0.97
Builder_Build Job-TDYACH \main\corerun.exe 16 3,169.3 ns 62.90 ns 92.20 ns 1.00 0.00 1.1024 0.0343 9248 B 1.00
Builder_Build Job-DXRHTD \pr\corerun.exe 16 2,959.3 ns 58.84 ns 130.39 ns 0.93 0.05 1.0872 0.0343 9104 B 0.98
Manager_AddSources Job-TDYACH \main\corerun.exe 16 5,783.2 ns 114.55 ns 301.78 ns 1.00 0.00 1.9989 0.0687 16752 B 1.00
Manager_AddSources Job-DXRHTD \pr\corerun.exe 16 6,129.9 ns 122.14 ns 268.11 ns 1.06 0.07 1.7090 0.0610 14296 B 0.85
Manager_ReloadSources Job-TDYACH \main\corerun.exe 16 3,882.5 ns 97.60 ns 286.25 ns 1.00 0.00 1.1749 0.0572 9832 B 1.00
Manager_ReloadSources Job-DXRHTD \pr\corerun.exe 16 3,892.0 ns 81.11 ns 239.16 ns 1.01 0.09 1.1253 0.0534 9416 B 0.96
Builder_Build Job-TDYACH \main\corerun.exe 64 13,280.9 ns 260.36 ns 482.59 ns 1.00 0.00 4.3182 0.5035 36176 B 1.00
Builder_Build Job-DXRHTD \pr\corerun.exe 64 12,785.1 ns 253.18 ns 371.11 ns 0.96 0.04 4.2419 0.5035 35600 B 0.98
Manager_AddSources Job-TDYACH \main\corerun.exe 64 34,694.8 ns 628.64 ns 672.64 ns 1.00 0.00 11.9324 1.5259 99984 B 1.00
Manager_AddSources Job-DXRHTD \pr\corerun.exe 64 25,616.3 ns 512.28 ns 1,246.95 ns 0.74 0.04 7.8430 1.0071 65656 B 0.66
Manager_ReloadSources Job-TDYACH \main\corerun.exe 64 15,124.2 ns 281.92 ns 301.65 ns 1.00 0.00 4.4250 0.7324 37144 B 1.00
Manager_ReloadSources Job-DXRHTD \pr\corerun.exe 64 14,977.4 ns 299.17 ns 766.89 ns 0.99 0.05 4.2419 0.7019 35528 B 0.96
Benchmark source
public sealed class ByteSizeConfig : ManualConfig
{
    public ByteSizeConfig()
    {
        SummaryStyle = SummaryStyle.Default.WithSizeUnit(SizeUnit.B);
        AddDiagnoser(MemoryDiagnoser.Default);
    }
}

[Config(typeof(ByteSizeConfig))]
public class ConfigBench
{
    [Params(4, 16, 64)]
    public int SourceCount { get; set; }

    private MemoryConfigurationSource[] _sources;
    private ConfigurationBuilder _builder;
    private ConfigurationManager _managerForReload;

    [GlobalSetup]
    public void Setup()
    {
        Assembly asm = typeof(ConfigurationBuilder).Assembly;
        Console.WriteLine($"[assembly] {asm.Location}");
        Console.WriteLine($"[mvid]     {asm.ManifestModule.ModuleVersionId}");

        _sources = new MemoryConfigurationSource[SourceCount];
        for (int i = 0; i < SourceCount; i++)
        {
            _sources[i] = new MemoryConfigurationSource();
        }

        _builder = new ConfigurationBuilder();
        foreach (MemoryConfigurationSource source in _sources)
        {
            _builder.Add(source);
        }

        _managerForReload = new ConfigurationManager();
        foreach (MemoryConfigurationSource source in _sources)
        {
            _managerForReload.Sources.Add(source);
        }
    }

    [GlobalCleanup]
    public void Cleanup() => _managerForReload?.Dispose();

    [Benchmark]
    public IConfigurationRoot Builder_Build() => _builder.Build();

    [Benchmark]
    public ConfigurationManager Manager_AddSources()
    {
        var manager = new ConfigurationManager();
        for (int i = 0; i < _sources.Length; i++)
        {
            manager.Sources.Add(_sources[i]);
        }
        return manager;
    }

    [Benchmark]
    public ConfigurationManager Manager_ReloadSources()
    {
        _managerForReload.Sources[0] = _sources[0];
        return _managerForReload;
    }
}

Give the provider lists an initial capacity so `List<T>` does not
reallocate its backing array while it grows
in the Microsoft.Extensions.Configuration APIs.

Changed:
- ConfigurationBuilder.Build
- ConfigurationManager.ReloadSources
- ReferenceCountedProviderManager.AddProvider
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Sep 9, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-extensions-configuration
See info in area-owners.md if you want to be subscribed.

@prozolic
prozolic marked this pull request as ready for review September 9, 2026 13:23
Copilot AI lite review requested due to automatic review settings September 9, 2026 13:23
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The changes are small, localized, and preserve existing behavior while applying a standard allocation-avoidance pattern (pre-sizing lists when the final count is known).

Pull request overview

This PR reduces transient allocations in Microsoft.Extensions.Configuration by pre-sizing provider lists based on the known source count, avoiding List<T> growth reallocations in common paths.

Changes:

  • Pre-allocates List<IConfigurationProvider> capacity in ConfigurationBuilder.Build() using _sources.Count.
  • Pre-allocates List<IConfigurationProvider> capacity in ConfigurationManager.ReloadSources() using _sources.Count.
  • Updates ReferenceCountedProviderManager.AddProvider() to copy providers into a new list with Count + 1 capacity before appending.
File summaries
File Description
src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationBuilder.cs Pre-sizes providers list to _sources.Count before building providers.
src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs Pre-sizes the rebuilt providers list during ReloadSources() to _sources.Count.
src/libraries/Microsoft.Extensions.Configuration/src/ReferenceCountedProvidersManager.cs Avoids list growth reallocation by allocating providers.Count + 1 before copying and adding a provider.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Lite

…CountedProvidersManager.cs

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
Copilot AI review requested due to automatic review settings September 10, 2026 13:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The changes are low-risk, preserve existing behavior, and align with the intended allocation reduction by pre-sizing provider lists where the final size is already known.

Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@rosebyte rosebyte left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Thank you for the contribution!

@rosebyte
rosebyte enabled auto-merge (squash) September 10, 2026 13:45
Copilot AI review requested due to automatic review settings September 11, 2026 07:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

One or more issues must be addressed before approval.

Review details

Suppressed comments (1)

src/libraries/Microsoft.Extensions.Configuration/src/ReferenceCountedProvidersManager.cs:71

  • The supplied benchmark shows a throughput regression for this path at the intermediate size: Manager_AddSources goes from 5,783.2 ns to 6,129.9 ns for 16 sources (ratio 1.06), even though allocations improve. Please benchmark an explicit List<IConfigurationProvider> with capacity existing.Count + 1, followed by AddRange and Add, to retain the allocation win without the collection-expression lowering overhead before merging.
                _refCountedProviders.Providers = [.. _refCountedProviders.Providers, provider];
  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@rosebyte
rosebyte merged commit d13c226 into dotnet:main Sep 11, 2026
77 of 81 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-Extensions-Configuration community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants