-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bad LOH allocations when deregistering from IOptionsMonitor #112127
Comments
@MihaZupan this issue is complaining about the perf of the delegate registration and deregistration. It is not related to the extension's options. |
A multicast delegate is immutable. Adding/removing items will be slow as the collection grows. The issue is complaining that registering and unregistering from |
From the benchmark numbers, it looks to me the allocated memory is exponentially growing as you increase the registration/deregistration. Sure, we can optimize the Options part, but this is not going to help in the general case for users using such multicast delegates. |
The benchmark is increasing the number of registered baseline delegates exponentially (2^ |
Since the multicast delegate is immutable, there is an optimization along the |
@Tragetaschen does your concern regarding |
In our scenario, this turned up only along the |
Tagging subscribers to this area: @dotnet/area-extensions-options |
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
BenchmarkRunner.Run<Benchmark>();
[SimpleJob(iterationCount: 5)]
[MemoryDiagnoser]
public class Benchmark
{
private IOptionsMonitor<MyOptions> _optionsMonitor = null!;
[Params(256, 1024, 4096, 8192)]
public int BaseRegistrations { get; set; }
[GlobalSetup]
public void Setup()
{
var collection = new ServiceCollection();
collection.AddOptions<MyOptions>();
collection.Configure<MyOptions>(x => x.Value = 42);
var services = collection.BuildServiceProvider();
_optionsMonitor = services.GetRequiredService<IOptionsMonitor<MyOptions>>();
for (var i = 0; i < BaseRegistrations; ++i)
{
// Registrations don't need to be removed
_optionsMonitor.OnChange(new Action<MyOptions>(Method));
}
}
[Benchmark]
public void AddRemove()
{
var registration = _optionsMonitor.OnChange(new Action<MyOptions>(Method));
registration?.Dispose();
}
private void Method(MyOptions _) { }
private class MyOptions
{
public int Value { get; set; }
}
}
|
Multicast delegates are not optimized for this scenario by design. Optimizing multicast delegates for this scenario would be very difficult and it would hurt the common multicast delegate use cases with just a few subscribers that are not changing frequently. |
I already tagged the issue back with Options area and will be scoped to |
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 theOnChange
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.
The text was updated successfully, but these errors were encountered: