From a62bfd7ee0d29cb817f6ce55fc41f67f62813b54 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 25 Nov 2020 15:39:25 -0500 Subject: [PATCH 1/3] Reduce allocation from OptionsCache's concurrent dictionary This type is primarily used for getting and rarely mutated after startup; we don't need to pay for lots of lock objects to optimize for mutation. --- src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs b/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs index b4094fe54de20a..e18b5a75b9bb88 100644 --- a/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs +++ b/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs @@ -15,7 +15,7 @@ public class OptionsCache<[DynamicallyAccessedMembers(Options.DynamicallyAccesse IOptionsMonitorCache where TOptions : class { - private readonly ConcurrentDictionary> _cache = new ConcurrentDictionary>(StringComparer.Ordinal); + private readonly ConcurrentDictionary> _cache = new ConcurrentDictionary>(concurrencyLevel: 1, capacity: 31, StringComparer.Ordinal); /// /// Clears all options instances from the cache. From dd0b9961b711bd78866231198dc67b447c60e3cc Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 25 Nov 2020 15:50:37 -0500 Subject: [PATCH 2/3] Avoid closure/delegate allocations in `OptionsManager.Value` --- .../src/OptionsCache.cs | 18 ++++++++++++++++++ .../src/OptionsManager.cs | 19 ++++++++++--------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs b/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs index e18b5a75b9bb88..55f97a44202c9a 100644 --- a/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs +++ b/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs @@ -38,6 +38,24 @@ public virtual TOptions GetOrAdd(string name, Func createOptions) return _cache.GetOrAdd(name, new Lazy(createOptions)).Value; } + /// + /// Gets a named options instance, if available. + /// + /// The name of the options instance. + /// The options instance. + /// true if the options were retrieved; otherwise, false. + internal bool TryGetValue(string name, out TOptions options) + { + if (_cache.TryGetValue(name ?? Options.DefaultName, out Lazy lazy)) + { + options = lazy.Value; + return true; + } + + options = default; + return false; + } + /// /// Tries to adds a new option to the cache, will return false if the name already exists. /// diff --git a/src/libraries/Microsoft.Extensions.Options/src/OptionsManager.cs b/src/libraries/Microsoft.Extensions.Options/src/OptionsManager.cs index 71b5a134f35139..83fc0ecc8e9050 100644 --- a/src/libraries/Microsoft.Extensions.Options/src/OptionsManager.cs +++ b/src/libraries/Microsoft.Extensions.Options/src/OptionsManager.cs @@ -29,13 +29,7 @@ public OptionsManager(IOptionsFactory factory) /// /// The default configured instance, equivalent to Get(Options.DefaultName). /// - public TOptions Value - { - get - { - return Get(Options.DefaultName); - } - } + public TOptions Value => Get(Options.DefaultName); /// /// Returns a configured instance with the given . @@ -44,8 +38,15 @@ public virtual TOptions Get(string name) { name = name ?? Options.DefaultName; - // Store the options in our instance cache - return _cache.GetOrAdd(name, () => _factory.Create(name)); + if (!_cache.TryGetValue(name, out TOptions options)) + { + // Store the options in our instance cache. Avoid closure on fast path by storing state into scoped locals. + IOptionsFactory localFactory = _factory; + string localName = name; + options = _cache.GetOrAdd(name, () => localFactory.Create(localName)); + } + + return options; } } } From fb01382e3a7c49c4917b94e1087302f3a0af82bb Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 25 Nov 2020 16:16:49 -0500 Subject: [PATCH 3/3] Update src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs Co-authored-by: David Fowler --- src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs b/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs index 55f97a44202c9a..29c38881eec080 100644 --- a/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs +++ b/src/libraries/Microsoft.Extensions.Options/src/OptionsCache.cs @@ -15,7 +15,7 @@ public class OptionsCache<[DynamicallyAccessedMembers(Options.DynamicallyAccesse IOptionsMonitorCache where TOptions : class { - private readonly ConcurrentDictionary> _cache = new ConcurrentDictionary>(concurrencyLevel: 1, capacity: 31, StringComparer.Ordinal); + private readonly ConcurrentDictionary> _cache = new ConcurrentDictionary>(concurrencyLevel: 1, capacity: 31, StringComparer.Ordinal); // 31 == default capacity /// /// Clears all options instances from the cache.