runtime: IOptionsSnapshot is very slow

This came up after a perf investigation on a recently refactored endpoint. Moving us back from IOptionsSnapshot to IOptionsMonitor knocked off ~100us, nothing else changed.

This option type of ours is fairly costly to create and looking into OptionsManager<TOptions> showed an obvious clue why we saw issues. OptionsManager does not just cache the instance for the duration of the DI scope but it also creates from scratch an instance through the factory per scope.

Looking through the docs this is vaguely alluded to (though there are contra-indicators) but after doing a search on SO and blogs on the subject most users seem to focus entirely on the ‘you get a constant value during a scope/request’ aspect of it.

As nobody (famous last words) seems to care whether it is entirely recreated my question would be: Why doesn’t OptionsManager<TOptions> cache the result of an IOptionsMonitor.Get call? (leaving out the backwards compatibility part for the sake of argument)

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 3
  • Comments: 40 (27 by maintainers)

Most upvoted comments

Right. We should consider changing the implementation so that it caches the currently cached value so scoped services are a consistent view of the data but there’s no need to recompute if nothing changes

IOptionsSnapshot can’t use IOptionsMonitor. In fact, we tried to do that in .NET 7 and it is a major breaking change. IOptionsSnapshot supports injecting scoped IConfigureOptions<T>. That’s the problem.

The combination of IOptionsSnapshot with uncached configuration binding is a performance pit of failure worth investigating. Specifically, the problem is that calling:

@deeprobin I think the bottleneck is this: #33954. And if we use IOptionsSnapshot, it runs on every request. If we use IOptionsMonitor, it runs once, and then it’s cached until the configuration is changed (and reloaded). I encourage you to try this with IConfiguration that contains several thousand key-value pairs (and nesting, etc.)

I agree, alright will be cooking it up.

Yes it’s a workaround that we can’t ship but you can use in your apps if they don’t hit this scenario

To do anything useful here we’d need something like the API I proposed in #36130 (comment). This is essentially what the new configuration source generator does today so it’s possible that is a reasonable mitigation for this.

This is a similar solution that I implemented as a workaround for issue in our code. I ended up writing an extension to provide a custom binder, and wrote some binders by hand that were written for the exact types used. It performed very well.

A source generator that will generate similar code as the one I wrote by hand sounds like a very good solution to me.

@davidfowl that was an honest question, I would like to fix it up but suggestions are welcome.

If we are talking specifically about IOptionsSnapshot, that was intended to be really the no-caching you get a really new fresh instance on every scope, so it literally was intended to recompute everything on every request