Skip to content
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

A delegate object is created for each IOptionsMonitor<T>.Get call #61086

Closed
Tracked by #93172
rayao opened this issue Nov 2, 2021 · 6 comments · Fixed by #66688
Closed
Tracked by #93172

A delegate object is created for each IOptionsMonitor<T>.Get call #61086

rayao opened this issue Nov 2, 2021 · 6 comments · Fixed by #66688
Labels
area-Extensions-Options help wanted [up-for-grabs] Good issue for external contributors tenet-performance Performance related issue
Milestone

Comments

@rayao
Copy link

rayao commented Nov 2, 2021

return _cache.GetOrAdd(name, () => _factory.Create(name));

It could be avoided if the delegate is a member, or simply Add a TryGetValue call before calling GetOrAdd.
The object allocation is much more expensive than the getting itself, when CurrentValue/Get is used in hot code path, the cost will be unexpected.

@dotnet-issue-labeler dotnet-issue-labeler bot added area-Extensions-Options untriaged New issue has not been triaged by the area owner labels Nov 2, 2021
@ghost
Copy link

ghost commented Nov 2, 2021

Tagging subscribers to this area: @maryamariyan
See info in area-owners.md if you want to be subscribed.

Issue Details

return _cache.GetOrAdd(name, () => _factory.Create(name));

It could be avoided if the delegate is a member, or simply Add a TryGetValue call before calling GetOrAdd.
The object allocation is much more expensive than the getting itself, when CurrentValue/Get is used in hot code path, the cost will be unexpected.

Author: rayao
Assignees: -
Labels:

untriaged, area-Extensions-Options

Milestone: -

@davidfowl
Copy link
Member

We can fix this by not using the IOptionsCache. It's possible we can use the concrete type if it hasn't been overridden as an optimization.

@rayao
Copy link
Author

rayao commented Nov 7, 2021

@davidfowl I didn't get why we need the IOptionsMonitorCache service, if OptionsMonitor maintains the cache itself, everything will be much easier. I didn't find IOptionsMonitorCache is used anywhere else, could we just remove it or add some more methods?

@NickCraver
Copy link
Member

For what it's worth, I came across this as a hot path allocation today in an application using .CurrentValue quite a bit on every request. Can we switch to the TArg overload here?

@stephentoub
Copy link
Member

Can we switch to the TArg overload here?

There isn't one:

public interface IOptionsMonitorCache<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions>
where TOptions : class
{
/// <summary>
/// Gets a named options instance, or adds a new instance created with <paramref name="createOptions"/>.
/// </summary>
/// <param name="name">The name of the options instance.</param>
/// <param name="createOptions">The func used to create the new instance.</param>
/// <returns>The options instance.</returns>
TOptions GetOrAdd(string name, Func<TOptions> createOptions);
/// <summary>
/// Tries to adds a new option to the cache, will return false if the name already exists.
/// </summary>
/// <param name="name">The name of the options instance.</param>
/// <param name="options">The options instance.</param>
/// <returns>Whether anything was added.</returns>
bool TryAdd(string name, TOptions options);
/// <summary>
/// Try to remove an options instance.
/// </summary>
/// <param name="name">The name of the options instance.</param>
/// <returns>Whether anything was removed.</returns>
bool TryRemove(string name);
/// <summary>
/// Clears all options instances from the cache.
/// </summary>
void Clear();
}

@maryamariyan maryamariyan added this to the Future milestone Dec 1, 2021
@maryamariyan maryamariyan removed the untriaged New issue has not been triaged by the area owner label Dec 1, 2021
@maryamariyan maryamariyan added tenet-performance Performance related issue help wanted [up-for-grabs] Good issue for external contributors labels Dec 1, 2021
madelson added a commit to madelson/runtime that referenced this issue Mar 16, 2022
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Mar 16, 2022
@NinoFloris
Copy link
Contributor

Isn't this a clear case where a new interface method with a default implementation would be perfect? It seems really simple to add a TArg version that would just allocate and call the existing GetOrAdd in the default implementation.

eerhardt pushed a commit that referenced this issue Mar 29, 2022
…66688)

* Avoid allocating a delegate in OptionsMonitor.Get() when possible.

Fix #61086

* Address feedback from #66688 (comment)

* Use locals to avoid unnecessary closure allocations.

* Remove another closure allocation in OptionsMonitor and add test for #61086.
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Mar 29, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Apr 28, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-Extensions-Options help wanted [up-for-grabs] Good issue for external contributors tenet-performance Performance related issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants