Bad LOH allocations when deregistering from IOptionsMonitor
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 35/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Stale
- Tech stack
- csharp
- Domain
- performance
Research direction
Start at IOptionsMonitor.Dispose and the delegate deregistration described in the issue. Run the provided BenchmarkDotNet AddRemove benchmark and compare allocations as the registered delegate count grows. Done means addressing the deregistration allocation problem without changing registration behavior.
Written by the indexing model from the issue text.
Description
Description
We have an Orleans cluster and in the silo, each grain depends on global configuration. Since that can change during runtime, we are using IOptionsMonitor and when the grain is done, we dispose the handle we got from the OnChange call. That last dispose call is the lone source of some hefty LOH allocations. The grains come and go constantly and at a certain load on the silo (around 2500 grains), LOH allocations start to happen.
As far as I can tell, IOptionsMonitor.Dispose only deregisters from the internal multicast delegate and this deregistration is the main culprit: The multicast delegate allocates a new invocation list for each -=, so memory consumption becomes O(n) while it's amortized O(1) for += due to exponential growth.
This small benchmark shows the problem. The allocations grow with the number of already registered delegates.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkRunner.Run<Benchmark>();
[SimpleJob(iterationCount: 5)]
[MemoryDiagnoser]
public class Benchmark
{
private Action? _methods;
[Params(8, 10, 12, 14)]
public int Power { get; set; }
[GlobalSetup]
public void Setup()
{
_methods += Method;
for (var i = 0; i < Power; ++i)
{
_methods += _methods;
}
}
[Benchmark]
public void AddRemove()
{
var m = new Action(Method);
_methods += m;
_methods -= m;
}
private void Method() { }
}
| Method | Power | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated |
|---------- |------ |-----------:|-----------:|-----------:|---------:|---------:|---------:|----------:|
| AddRemove | 8 | 1.512 us | 0.8605 us | 0.2235 us | 0.7629 | 0.0172 | - | 6.23 KB |
| AddRemove | 10 | 5.376 us | 2.8169 us | 0.4359 us | 2.9602 | 0.1831 | - | 24.23 KB |
| AddRemove | 12 | 18.840 us | 1.0445 us | 0.1616 us | 11.7493 | 1.4648 | - | 96.23 KB |
| AddRemove | 14 | 135.817 us | 74.9703 us | 19.4695 us | 124.8779 | 124.8779 | 124.8779 | 384.28 KB |
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from dotnet/runtime
-
agentic-workflows untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
area-System.Reflection blocking-clean-ci-optional Known Build Error os-mac-os-x untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
area-CodeGen-coreclr untriaged
Difficulty 1/5 Under an hour Newbie friendliness 92/100
-
agentic-workflows untriaged
Difficulty 1/5 Under an hour Newbie friendliness 78/100
-
area-VM-meta-mono untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
-
:watch: Not Triaged 11.0 fundamentals/subsvc
Difficulty 2/5 1-3 hours Newbie friendliness 92/100
dotnet/AspNetCore.Docs#37699 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
SubtitleEdit/subtitleedit#15108 · 1 comment ·
-
area/docs-content Bug pulumi/docs
Difficulty 1/5 1-3 hours Newbie friendliness 94/100
-
Create parent directories only after the containment check in InstallHelper.TryExtractToDirectory Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
PowerShell/PSResourceGet#2056 ·