From c87b685a00087242d0a04f72ff3620bd621f4a45 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 26 Mar 2021 13:04:59 -0700 Subject: [PATCH 01/28] Trying routevaluedict --- .../src/Routing/RouteValueDictionary.cs | 2 +- .../src/Internal/RequestCookieCollection.cs | 34 +++++++++++------- .../Cookies/samples/CookieSample/Startup.cs | 35 ++++++++++--------- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/Http/Http.Abstractions/src/Routing/RouteValueDictionary.cs b/src/Http/Http.Abstractions/src/Routing/RouteValueDictionary.cs index 415e4d157154..33e9baab979b 100644 --- a/src/Http/Http.Abstractions/src/Routing/RouteValueDictionary.cs +++ b/src/Http/Http.Abstractions/src/Routing/RouteValueDictionary.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Routing public class RouteValueDictionary : IDictionary, IReadOnlyDictionary { // 4 is a good default capacity here because that leaves enough space for area/controller/action/id - private const int DefaultCapacity = 4; + private readonly int DefaultCapacity = 4; internal KeyValuePair[] _arrayStorage; internal PropertyStorage? _propertyStorage; diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs index f7ae17212887..6b30d4390f9f 100644 --- a/src/Http/Http/src/Internal/RequestCookieCollection.cs +++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs @@ -5,7 +5,12 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +<<<<<<< HEAD using Microsoft.Extensions.Primitives; +======= +using System.Linq; +using Microsoft.AspNetCore.Routing; +>>>>>>> 41cfee2cfe (Trying routevaluedict) using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Http @@ -19,22 +24,25 @@ internal class RequestCookieCollection : IRequestCookieCollection private static readonly IEnumerator> EmptyIEnumeratorType = EmptyEnumerator; private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator; - private Dictionary? Store { get; set; } - - public RequestCookieCollection() - { - } + private RouteValueDictionary Store { get; set; } public RequestCookieCollection(Dictionary store) { - Store = store; + Store = new RouteValueDictionary(); + //Store = RouteValueDictionary.FromArray(store.ToArray()); + //Store = new RouteValueDictionary(; } - public RequestCookieCollection(int capacity) + public RequestCookieCollection() { - Store = new Dictionary(capacity, StringComparer.OrdinalIgnoreCase); + Store = new RouteValueDictionary(); } + //public RequestCookieCollection(int capacity) + //{ + // Store = new RouteValueDictionary(capacity); + //} + public string? this[string key] { get @@ -121,7 +129,9 @@ public bool TryGetValue(string key, [MaybeNullWhen(false)] out string? value) value = null; return false; } - return Store.TryGetValue(key, out value); + var res = Store.TryGetValue(key, out var objValue); + value = objValue as string; + return res; } /// @@ -172,10 +182,10 @@ IEnumerator IEnumerable.GetEnumerator() public struct Enumerator : IEnumerator> { // Do NOT make this readonly, or MoveNext will not work - private Dictionary.Enumerator _dictionaryEnumerator; + private RouteValueDictionary.Enumerator _dictionaryEnumerator; private bool _notEmpty; - internal Enumerator(Dictionary.Enumerator dictionaryEnumerator) + internal Enumerator(RouteValueDictionary.Enumerator dictionaryEnumerator) { _dictionaryEnumerator = dictionaryEnumerator; _notEmpty = true; @@ -197,7 +207,7 @@ public KeyValuePair Current if (_notEmpty) { var current = _dictionaryEnumerator.Current; - return new KeyValuePair(current.Key, current.Value); + return new KeyValuePair(current.Key, (string)current.Value!); } return default(KeyValuePair); } diff --git a/src/Security/Authentication/Cookies/samples/CookieSample/Startup.cs b/src/Security/Authentication/Cookies/samples/CookieSample/Startup.cs index a91791070a06..1d1f1fc73427 100644 --- a/src/Security/Authentication/Cookies/samples/CookieSample/Startup.cs +++ b/src/Security/Authentication/Cookies/samples/CookieSample/Startup.cs @@ -14,31 +14,34 @@ public class Startup public void ConfigureServices(IServiceCollection services) { // This can be removed after https://github.com/aspnet/IISIntegration/issues/371 - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; - }).AddCookie(); + //services.AddAuthentication(options => + //{ + // options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; + // options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; + //}).AddCookie(); } public void Configure(IApplicationBuilder app) { - app.UseAuthentication(); + //app.UseAuthentication(); app.Run(async context => { - if (!context.User.Identities.Any(identity => identity.IsAuthenticated)) - { - var user = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "bob") }, CookieAuthenticationDefaults.AuthenticationScheme)); - await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, user); + var cookie = context.Request.Cookies[".AspNetCore.Cookies"]; + + await context.Response.WriteAsync($"Hello World {cookie}"); + //if (!context.User.Identities.Any(identity => identity.IsAuthenticated)) + //{ + // var user = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "bob") }, CookieAuthenticationDefaults.AuthenticationScheme)); + // await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, user); - context.Response.ContentType = "text/plain"; - await context.Response.WriteAsync("Hello First timer"); - return; - } + // context.Response.ContentType = "text/plain"; + // await context.Response.WriteAsync("Hello First timer"); + // return; + //} - context.Response.ContentType = "text/plain"; - await context.Response.WriteAsync("Hello old timer"); + //context.Response.ContentType = "text/plain"; + //await context.Response.WriteAsync("Hello old timer"); }); } } From 485474b02abd48f0987c3237e630cea398e4e823 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Sat, 27 Mar 2021 13:56:09 -0700 Subject: [PATCH 02/28] New dictionary --- .../Http/src/Microsoft.AspNetCore.Http.csproj | 1 + .../Dictionary/SmallCapacityDictionary.cs | 740 ++++++++++++++++++ 2 files changed, 741 insertions(+) create mode 100644 src/Shared/Dictionary/SmallCapacityDictionary.cs diff --git a/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj index 18e06e184ca4..d2281f33202c 100644 --- a/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj +++ b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Shared/Dictionary/SmallCapacityDictionary.cs b/src/Shared/Dictionary/SmallCapacityDictionary.cs new file mode 100644 index 000000000000..0b951df7f241 --- /dev/null +++ b/src/Shared/Dictionary/SmallCapacityDictionary.cs @@ -0,0 +1,740 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Microsoft.AspNetCore.Internal.Dictionarys +{ + /// + /// An type for route values. + /// + internal class SmallCapacityDictionary : IDictionary, IReadOnlyDictionary + { + // Threshold for size of array to use. + private static readonly int DefaultArrayThreshold = 4; + + internal KeyValuePair[] _arrayStorage; + private int _count; + private Dictionary? _backup; + private int _threshold = DefaultArrayThreshold; + + /// + /// Creates a new from the provided array. + /// The new instance will take ownership of the array, and may mutate it. + /// + /// The items array. + /// A new . + public static SmallCapacityDictionary FromArray(KeyValuePair[] items) + { + if (items == null) + { + throw new ArgumentNullException(nameof(items)); + } + + if (items.Length > DefaultArrayThreshold) + { + // Don't use dictionary for large arrays. + var dict = new Dictionary(); + foreach (var item in items) + { + dict[item.Key] = item.Value; + } + + return new SmallCapacityDictionary() + { + _backup = dict + }; + } + + // We need to compress the array by removing non-contiguous items. We + // typically have a very small number of items to process. We don't need + // to preserve order. + var start = 0; + var end = items.Length - 1; + + // We walk forwards from the beginning of the array and fill in 'null' slots. + // We walk backwards from the end of the array end move items in non-null' slots + // into whatever start is pointing to. O(n) + while (start <= end) + { + if (items[start].Key != null) + { + start++; + } + else if (items[end].Key != null) + { + // Swap this item into start and advance + items[start] = items[end]; + items[end] = default; + start++; + end--; + } + else + { + // Both null, we need to hold on 'start' since we + // still need to fill it with something. + end--; + } + } + + return new SmallCapacityDictionary() + { + _arrayStorage = items!, + _count = start, + }; + } + + /// + /// Creates an empty . + /// + public SmallCapacityDictionary() + { + _arrayStorage = Array.Empty>(); + } + + /// + /// Creates a initialized with the specified . + /// + /// An object to initialize the dictionary. The value can be of type + /// or + /// or an object with public properties as key-value pairs. + /// + /// + /// If the value is a dictionary or other of , + /// then its entries are copied. Otherwise the object is interpreted as a set of key-value pairs where the + /// property names are keys, and property values are the values, and copied into the dictionary. + /// Only public instance non-index properties are considered. + /// + public SmallCapacityDictionary(object? values) + { + if (values is SmallCapacityDictionary dictionary) + { + var count = dictionary._count; + if (count > 0) + { + var other = dictionary._arrayStorage; + var storage = new KeyValuePair[count]; + Array.Copy(other, 0, storage, 0, count); + _arrayStorage = storage; + _count = count; + } + else + { + _arrayStorage = Array.Empty>(); + } + + return; + } + + if (values is IEnumerable> keyValueEnumerable) + { + _arrayStorage = Array.Empty>(); + + foreach (var kvp in keyValueEnumerable) + { + Add(kvp.Key, kvp.Value); + } + + return; + } + + if (values is IEnumerable> stringValueEnumerable) + { + _arrayStorage = Array.Empty>(); + + foreach (var kvp in stringValueEnumerable) + { + Add(kvp.Key, kvp.Value); + } + + return; + } + + _arrayStorage = Array.Empty>(); + } + + /// + public object? this[string key] + { + get + { + if (key == null) + { + ThrowArgumentNullExceptionForKey(); + } + + TryGetValue(key, out var value); + return value; + } + + set + { + if (key == null) + { + ThrowArgumentNullExceptionForKey(); + } + + if (_backup != null) + { + _backup[key] = value; + return; + } + + var index = FindIndex(key); + if (index < 0) + { + EnsureCapacity(_count + 1); + if (_backup != null) + { + _backup[key] = value; + return; + } + _arrayStorage[_count++] = new KeyValuePair(key, value); + } + else + { + _arrayStorage[index] = new KeyValuePair(key, value); + } + } + } + + /// + /// Gets the comparer for this dictionary. + /// + /// + /// This will always be a reference to + /// + public IEqualityComparer Comparer => StringComparer.OrdinalIgnoreCase; + + /// + public int Count + { + get + { + if (_backup != null) + { + return _backup.Count; + } + return _count; + } + } + + /// + bool ICollection>.IsReadOnly => false; + + /// + public ICollection Keys + { + get + { + if (_backup != null) + { + return _backup.Keys; + } + + var array = _arrayStorage; + var keys = new string[_count]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = array[i].Key; + } + + return keys; + } + } + + IEnumerable IReadOnlyDictionary.Keys => Keys; + + /// + public ICollection Values + { + get + { + if (_backup != null) + { + return _backup.Values; + } + + var array = _arrayStorage; + var values = new object?[_count]; + for (var i = 0; i < values.Length; i++) + { + values[i] = array[i].Value; + } + + return values; + } + } + + IEnumerable IReadOnlyDictionary.Values => Values; + + /// + void ICollection>.Add(KeyValuePair item) + { + if (_backup != null) + { + ((ICollection>)_backup).Add(item); + return; + } + + Add(item.Key, item.Value); + } + + /// + public void Add(string key, object? value) + { + if (key == null) + { + ThrowArgumentNullExceptionForKey(); + } + + if (_backup != null) + { + _backup.Add(key, value); + return; + } + + EnsureCapacity(_count + 1); + + if (_backup != null) + { + _backup.Add(key, value); + return; + } + + if (ContainsKeyArray(key)) + { + throw new ArgumentException($"An element with the key '{nameof(key)}' already exists in the {nameof(SmallCapacityDictionary)}."); + } + + _arrayStorage[_count] = new KeyValuePair(key, value); + _count++; + } + + /// + public void Clear() + { + if (_backup != null) + { + _backup.Clear(); + } + + if (_count == 0) + { + return; + } + + Array.Clear(_arrayStorage, 0, _count); + _count = 0; + } + + /// + bool ICollection>.Contains(KeyValuePair item) + { + if (_backup != null) + { + return ((ICollection>)_backup).Contains(item); + } + + return TryGetValue(item.Key, out var value) && EqualityComparer.Default.Equals(value, item.Value); + } + + /// + public bool ContainsKey(string key) + { + if (key == null) + { + ThrowArgumentNullExceptionForKey(); + } + + if (_backup != null) + { + return _backup.ContainsKey(key); + } + + return ContainsKeyCore(key); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool ContainsKeyCore(string key) + { + return ContainsKeyArray(key); + } + + /// + void ICollection>.CopyTo( + KeyValuePair[] array, + int arrayIndex) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (arrayIndex < 0 || arrayIndex > array.Length || array.Length - arrayIndex < this.Count) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + if (_backup != null) + { + var i = 0; + foreach (var kvp in _backup) + { + array[i] = kvp; + i++; + } + + return; + } + + if (Count == 0) + { + return; + } + + var storage = _arrayStorage; + Array.Copy(storage, 0, array, arrayIndex, _count); + } + + /// + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + /// + IEnumerator> IEnumerable>.GetEnumerator() + { + if (_backup != null) + { + return _backup.GetEnumerator(); + } + + return GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + if (_backup != null) + { + return _backup.GetEnumerator(); + } + + return GetEnumerator(); + } + + /// + bool ICollection>.Remove(KeyValuePair item) + { + if (_backup != null) + { + return ((ICollection>)_backup).Remove(item); + } + + if (Count == 0) + { + return false; + } + + var index = FindIndex(item.Key); + var array = _arrayStorage; + if (index >= 0 && EqualityComparer.Default.Equals(array[index].Value, item.Value)) + { + Array.Copy(array, index + 1, array, index, _count - index); + _count--; + array[_count] = default; + return true; + } + + return false; + } + + /// + public bool Remove(string key) + { + if (key == null) + { + ThrowArgumentNullExceptionForKey(); + } + + if (_backup != null) + { + return _backup.Remove(key); + } + + if (Count == 0) + { + return false; + } + + var index = FindIndex(key); + if (index >= 0) + { + _count--; + var array = _arrayStorage; + Array.Copy(array, index + 1, array, index, _count - index); + array[_count] = default; + + return true; + } + + return false; + } + + /// + /// Attempts to remove and return the value that has the specified key from the . + /// + /// The key of the element to remove and return. + /// When this method returns, contains the object removed from the , or null if key does not exist. + /// + /// true if the object was removed successfully; otherwise, false. + /// + public bool Remove(string key, out object? value) + { + if (key == null) + { + ThrowArgumentNullExceptionForKey(); + } + + + if (_backup != null) + { + return _backup.Remove(key, out value); + } + + if (_count == 0) + { + value = default; + return false; + } + + var index = FindIndex(key); + if (index >= 0) + { + _count--; + var array = _arrayStorage; + value = array[index].Value; + Array.Copy(array, index + 1, array, index, _count - index); + array[_count] = default; + + return true; + } + + value = default; + return false; + } + + /// + /// Attempts to the add the provided and to the dictionary. + /// + /// The key. + /// The value. + /// Returns true if the value was added. Returns false if the key was already present. + public bool TryAdd(string key, object? value) + { + if (key == null) + { + ThrowArgumentNullExceptionForKey(); + } + + if (_backup != null) + { + return _backup.TryAdd(key, value); + } + + if (ContainsKeyCore(key)) + { + return false; + } + + EnsureCapacity(Count + 1); + _arrayStorage[Count] = new KeyValuePair(key, value); + _count++; + return true; + } + + /// + public bool TryGetValue(string key, out object? value) + { + if (key == null) + { + ThrowArgumentNullExceptionForKey(); + } + + if (_backup != null) + { + return _backup.TryGetValue(key, out value); + } + + return TryFindItem(key, out value); + } + + + [DoesNotReturn] + private static void ThrowArgumentNullExceptionForKey() + { + throw new ArgumentNullException("key"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureCapacity(int capacity) + { + EnsureCapacitySlow(capacity); + } + + private void EnsureCapacitySlow(int capacity) + { + if (_arrayStorage.Length < capacity) + { + if (capacity < DefaultArrayThreshold) + { + _backup = new Dictionary(capacity); + foreach (var item in _arrayStorage) + { + _backup[item.Key] = item.Value; + } + // Don't use _count or _arrayStorage anymore + // TODO clear arrays here? + } + else + { + capacity = _arrayStorage.Length == 0 ? DefaultArrayThreshold : _arrayStorage.Length * 2; + var array = new KeyValuePair[capacity]; + if (_count > 0) + { + Array.Copy(_arrayStorage, 0, array, 0, _count); + } + + _arrayStorage = array; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int FindIndex(string key) + { + // Generally the bounds checking here will be elided by the JIT because this will be called + // on the same code path as EnsureCapacity. + var array = _arrayStorage; + var count = _count; + + for (var i = 0; i < count; i++) + { + if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase)) + { + return i; + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool TryFindItem(string key, out object? value) + { + var array = _arrayStorage; + var count = _count; + + // Elide bounds check for indexing. + if ((uint)count <= (uint)array.Length) + { + for (var i = 0; i < count; i++) + { + if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase)) + { + value = array[i].Value; + return true; + } + } + } + + value = null; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool ContainsKeyArray(string key) + { + var array = _arrayStorage; + var count = _count; + + // Elide bounds check for indexing. + if ((uint)count <= (uint)array.Length) + { + for (var i = 0; i < count; i++) + { + if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + + return false; + } + + /// + public struct Enumerator : IEnumerator> + { + private readonly SmallCapacityDictionary _dictionary; + private int _index; + + /// + /// Instantiates a new enumerator with the values provided in . + /// + /// A . + public Enumerator(SmallCapacityDictionary dictionary) + { + if (dictionary == null) + { + throw new ArgumentNullException(); + } + + _dictionary = dictionary; + + Current = default; + _index = 0; + } + + /// + public KeyValuePair Current { get; private set; } + + object IEnumerator.Current => Current; + + /// + /// Releases resources used by the . + /// + public void Dispose() + { + } + + // Similar to the design of List.Enumerator - Split into fast path and slow path for inlining friendliness + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + var dictionary = _dictionary; + + // The uncommon case is that the propertyStorage is in use + Current = dictionary._arrayStorage[_index]; + _index++; + return true; + } + + + /// + public void Reset() + { + Current = default; + _index = 0; + } + } + } +} From 51dccd28f3a84b57907c07a36688f02fdf360f90 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Sat, 27 Mar 2021 14:00:20 -0700 Subject: [PATCH 03/28] Minor cleanup --- .../src/Internal/RequestCookieCollection.cs | 17 ++++++++--------- .../Dictionary/SmallCapacityDictionary.cs | 13 ++++++++++++- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs index 6b30d4390f9f..066dca10e6d4 100644 --- a/src/Http/Http/src/Internal/RequestCookieCollection.cs +++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Primitives; ======= using System.Linq; +using Microsoft.AspNetCore.Internal.Dictionary; using Microsoft.AspNetCore.Routing; >>>>>>> 41cfee2cfe (Trying routevaluedict) using Microsoft.Net.Http.Headers; @@ -24,24 +25,22 @@ internal class RequestCookieCollection : IRequestCookieCollection private static readonly IEnumerator> EmptyIEnumeratorType = EmptyEnumerator; private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator; - private RouteValueDictionary Store { get; set; } + private SmallCapacityDictionary Store { get; set; } public RequestCookieCollection(Dictionary store) { - Store = new RouteValueDictionary(); - //Store = RouteValueDictionary.FromArray(store.ToArray()); - //Store = new RouteValueDictionary(; + Store = new SmallCapacityDictionary(store); } public RequestCookieCollection() { - Store = new RouteValueDictionary(); + Store = new SmallCapacityDictionary(); } - //public RequestCookieCollection(int capacity) - //{ - // Store = new RouteValueDictionary(capacity); - //} + public RequestCookieCollection(int capacity) + { + Store = new SmallCapacityDictionary(capacity); + } public string? this[string key] { diff --git a/src/Shared/Dictionary/SmallCapacityDictionary.cs b/src/Shared/Dictionary/SmallCapacityDictionary.cs index 0b951df7f241..16dd7cedfdae 100644 --- a/src/Shared/Dictionary/SmallCapacityDictionary.cs +++ b/src/Shared/Dictionary/SmallCapacityDictionary.cs @@ -8,7 +8,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -namespace Microsoft.AspNetCore.Internal.Dictionarys +namespace Microsoft.AspNetCore.Internal.Dictionary { /// /// An type for route values. @@ -97,6 +97,17 @@ public SmallCapacityDictionary() _arrayStorage = Array.Empty>(); } + public SmallCapacityDictionary(Dictionary dict) + { + _backup = dict; + _arrayStorage = Array.Empty>(); + } + + public SmallCapacityDictionary(int capacity) + { + _arrayStorage = new KeyValuePair[capacity]; + } + /// /// Creates a initialized with the specified . /// From 5e0cdc94ab89b1fb811d39e304eb46109b7250d8 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 29 Mar 2021 10:51:35 -0700 Subject: [PATCH 04/28] Comparers --- .../src/Internal/RequestCookieCollection.cs | 15 +- .../Dictionary/SmallCapacityDictionary.cs | 207 +++++++++--------- 2 files changed, 107 insertions(+), 115 deletions(-) diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs index 066dca10e6d4..fd9f28eb591a 100644 --- a/src/Http/Http/src/Internal/RequestCookieCollection.cs +++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs @@ -25,21 +25,16 @@ internal class RequestCookieCollection : IRequestCookieCollection private static readonly IEnumerator> EmptyIEnumeratorType = EmptyEnumerator; private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator; - private SmallCapacityDictionary Store { get; set; } + private SmallCapacityDictionary Store { get; set; } public RequestCookieCollection(Dictionary store) { - Store = new SmallCapacityDictionary(store); + Store = new SmallCapacityDictionary(store); } public RequestCookieCollection() { - Store = new SmallCapacityDictionary(); - } - - public RequestCookieCollection(int capacity) - { - Store = new SmallCapacityDictionary(capacity); + Store = new SmallCapacityDictionary(); } public string? this[string key] @@ -181,10 +176,10 @@ IEnumerator IEnumerable.GetEnumerator() public struct Enumerator : IEnumerator> { // Do NOT make this readonly, or MoveNext will not work - private RouteValueDictionary.Enumerator _dictionaryEnumerator; + private SmallCapacityDictionary.Enumerator _dictionaryEnumerator; private bool _notEmpty; - internal Enumerator(RouteValueDictionary.Enumerator dictionaryEnumerator) + internal Enumerator(SmallCapacityDictionary.Enumerator dictionaryEnumerator) { _dictionaryEnumerator = dictionaryEnumerator; _notEmpty = true; diff --git a/src/Shared/Dictionary/SmallCapacityDictionary.cs b/src/Shared/Dictionary/SmallCapacityDictionary.cs index 16dd7cedfdae..e9a2a290ee9d 100644 --- a/src/Shared/Dictionary/SmallCapacityDictionary.cs +++ b/src/Shared/Dictionary/SmallCapacityDictionary.cs @@ -4,7 +4,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -13,23 +12,24 @@ namespace Microsoft.AspNetCore.Internal.Dictionary /// /// An type for route values. /// - internal class SmallCapacityDictionary : IDictionary, IReadOnlyDictionary + internal class SmallCapacityDictionary : IDictionary, IReadOnlyDictionary where TKey : notnull { // Threshold for size of array to use. private static readonly int DefaultArrayThreshold = 4; - internal KeyValuePair[] _arrayStorage; + internal KeyValuePair[] _arrayStorage; private int _count; - private Dictionary? _backup; + private Dictionary? _backup; private int _threshold = DefaultArrayThreshold; + private IEqualityComparer _comparer; /// - /// Creates a new from the provided array. + /// Creates a new from the provided array. /// The new instance will take ownership of the array, and may mutate it. /// /// The items array. - /// A new . - public static SmallCapacityDictionary FromArray(KeyValuePair[] items) + /// A new . + public static SmallCapacityDictionary FromArray(KeyValuePair[] items) { if (items == null) { @@ -39,13 +39,13 @@ public static SmallCapacityDictionary FromArray(KeyValuePair[] if (items.Length > DefaultArrayThreshold) { // Don't use dictionary for large arrays. - var dict = new Dictionary(); + var dict = new Dictionary(); foreach (var item in items) { dict[item.Key] = item.Value; } - return new SmallCapacityDictionary() + return new SmallCapacityDictionary() { _backup = dict }; @@ -82,7 +82,7 @@ public static SmallCapacityDictionary FromArray(KeyValuePair[] } } - return new SmallCapacityDictionary() + return new SmallCapacityDictionary() { _arrayStorage = items!, _count = start, @@ -90,26 +90,38 @@ public static SmallCapacityDictionary FromArray(KeyValuePair[] } /// - /// Creates an empty . + /// Creates an empty . /// public SmallCapacityDictionary() { - _arrayStorage = Array.Empty>(); + _comparer = EqualityComparer.Default; + _arrayStorage = Array.Empty>(); } - public SmallCapacityDictionary(Dictionary dict) + public SmallCapacityDictionary(Dictionary dict) { _backup = dict; - _arrayStorage = Array.Empty>(); + _comparer = EqualityComparer.Default; + + _arrayStorage = Array.Empty>(); } - public SmallCapacityDictionary(int capacity) + public SmallCapacityDictionary(IEqualityComparer comparer) { - _arrayStorage = new KeyValuePair[capacity]; + if (comparer is not null && comparer != EqualityComparer.Default) // first check for null to avoid forcing default comparer instantiation unnecessarily + { + _comparer = comparer; + } + else + { + _comparer = EqualityComparer.Default; + } + + _arrayStorage = Array.Empty>(); } /// - /// Creates a initialized with the specified . + /// Creates a initialized with the specified . /// /// An object to initialize the dictionary. The value can be of type /// or @@ -121,30 +133,13 @@ public SmallCapacityDictionary(int capacity) /// property names are keys, and property values are the values, and copied into the dictionary. /// Only public instance non-index properties are considered. /// - public SmallCapacityDictionary(object? values) + public SmallCapacityDictionary(IEnumerable> values) { - if (values is SmallCapacityDictionary dictionary) - { - var count = dictionary._count; - if (count > 0) - { - var other = dictionary._arrayStorage; - var storage = new KeyValuePair[count]; - Array.Copy(other, 0, storage, 0, count); - _arrayStorage = storage; - _count = count; - } - else - { - _arrayStorage = Array.Empty>(); - } + _comparer = EqualityComparer.Default; - return; - } - - if (values is IEnumerable> keyValueEnumerable) + if (values is IEnumerable> keyValueEnumerable) { - _arrayStorage = Array.Empty>(); + _arrayStorage = Array.Empty>(); foreach (var kvp in keyValueEnumerable) { @@ -154,23 +149,11 @@ public SmallCapacityDictionary(object? values) return; } - if (values is IEnumerable> stringValueEnumerable) - { - _arrayStorage = Array.Empty>(); - - foreach (var kvp in stringValueEnumerable) - { - Add(kvp.Key, kvp.Value); - } - - return; - } - - _arrayStorage = Array.Empty>(); + _arrayStorage = Array.Empty>(); } /// - public object? this[string key] + public TValue this[TKey key] { get { @@ -180,6 +163,12 @@ public object? this[string key] } TryGetValue(key, out var value); + + if (value == null) + { + ThrowKeyNotFoundException(nameof(key)); + } + return value; } @@ -205,23 +194,15 @@ public object? this[string key] _backup[key] = value; return; } - _arrayStorage[_count++] = new KeyValuePair(key, value); + _arrayStorage[_count++] = new KeyValuePair(key, value); } else { - _arrayStorage[index] = new KeyValuePair(key, value); + _arrayStorage[index] = new KeyValuePair(key, value); } } } - /// - /// Gets the comparer for this dictionary. - /// - /// - /// This will always be a reference to - /// - public IEqualityComparer Comparer => StringComparer.OrdinalIgnoreCase; - /// public int Count { @@ -236,10 +217,19 @@ public int Count } /// - bool ICollection>.IsReadOnly => false; + public IEqualityComparer Comparer + { + get + { + return _comparer ?? EqualityComparer.Default; + } + } /// - public ICollection Keys + bool ICollection>.IsReadOnly => false; + + /// + public ICollection Keys { get { @@ -249,7 +239,7 @@ public ICollection Keys } var array = _arrayStorage; - var keys = new string[_count]; + var keys = new TKey[_count]; for (var i = 0; i < keys.Length; i++) { keys[i] = array[i].Key; @@ -259,10 +249,10 @@ public ICollection Keys } } - IEnumerable IReadOnlyDictionary.Keys => Keys; + IEnumerable IReadOnlyDictionary.Keys => Keys; /// - public ICollection Values + public ICollection Values { get { @@ -272,7 +262,7 @@ public ICollection Values } var array = _arrayStorage; - var values = new object?[_count]; + var values = new TValue[_count]; for (var i = 0; i < values.Length; i++) { values[i] = array[i].Value; @@ -282,14 +272,14 @@ public ICollection Values } } - IEnumerable IReadOnlyDictionary.Values => Values; + IEnumerable IReadOnlyDictionary.Values => Values; /// - void ICollection>.Add(KeyValuePair item) + void ICollection>.Add(KeyValuePair item) { if (_backup != null) { - ((ICollection>)_backup).Add(item); + ((ICollection>)_backup).Add(item); return; } @@ -297,7 +287,7 @@ public ICollection Values } /// - public void Add(string key, object? value) + public void Add(TKey key, TValue value) { if (key == null) { @@ -320,10 +310,10 @@ public void Add(string key, object? value) if (ContainsKeyArray(key)) { - throw new ArgumentException($"An element with the key '{nameof(key)}' already exists in the {nameof(SmallCapacityDictionary)}."); + throw new ArgumentException($"An element with the key '{nameof(key)}' already exists in the {nameof(SmallCapacityDictionary)}."); } - _arrayStorage[_count] = new KeyValuePair(key, value); + _arrayStorage[_count] = new KeyValuePair(key, value); _count++; } @@ -345,18 +335,18 @@ public void Clear() } /// - bool ICollection>.Contains(KeyValuePair item) + bool ICollection>.Contains(KeyValuePair item) { if (_backup != null) { - return ((ICollection>)_backup).Contains(item); + return ((ICollection>)_backup).Contains(item); } return TryGetValue(item.Key, out var value) && EqualityComparer.Default.Equals(value, item.Value); } /// - public bool ContainsKey(string key) + public bool ContainsKey(TKey key) { if (key == null) { @@ -372,14 +362,14 @@ public bool ContainsKey(string key) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool ContainsKeyCore(string key) + private bool ContainsKeyCore(TKey key) { return ContainsKeyArray(key); } /// - void ICollection>.CopyTo( - KeyValuePair[] array, + void ICollection>.CopyTo( + KeyValuePair[] array, int arrayIndex) { if (array == null) @@ -420,7 +410,7 @@ public Enumerator GetEnumerator() } /// - IEnumerator> IEnumerable>.GetEnumerator() + IEnumerator> IEnumerable>.GetEnumerator() { if (_backup != null) { @@ -442,11 +432,11 @@ IEnumerator IEnumerable.GetEnumerator() } /// - bool ICollection>.Remove(KeyValuePair item) + bool ICollection>.Remove(KeyValuePair item) { if (_backup != null) { - return ((ICollection>)_backup).Remove(item); + return ((ICollection>)_backup).Remove(item); } if (Count == 0) @@ -456,7 +446,7 @@ IEnumerator IEnumerable.GetEnumerator() var index = FindIndex(item.Key); var array = _arrayStorage; - if (index >= 0 && EqualityComparer.Default.Equals(array[index].Value, item.Value)) + if (index >= 0 && EqualityComparer.Default.Equals(array[index].Value, item.Value)) { Array.Copy(array, index + 1, array, index, _count - index); _count--; @@ -468,7 +458,7 @@ IEnumerator IEnumerable.GetEnumerator() } /// - public bool Remove(string key) + public bool Remove(TKey key) { if (key == null) { @@ -500,14 +490,14 @@ public bool Remove(string key) } /// - /// Attempts to remove and return the value that has the specified key from the . + /// Attempts to remove and return the value that has the specified key from the . /// /// The key of the element to remove and return. - /// When this method returns, contains the object removed from the , or null if key does not exist. + /// When this method returns, contains the object removed from the , or null if key does not exist. /// /// true if the object was removed successfully; otherwise, false. /// - public bool Remove(string key, out object? value) + public bool Remove(TKey key, out TValue? value) { if (key == null) { @@ -548,7 +538,7 @@ public bool Remove(string key, out object? value) /// The key. /// The value. /// Returns true if the value was added. Returns false if the key was already present. - public bool TryAdd(string key, object? value) + public bool TryAdd(TKey key, TValue value) { if (key == null) { @@ -566,13 +556,13 @@ public bool TryAdd(string key, object? value) } EnsureCapacity(Count + 1); - _arrayStorage[Count] = new KeyValuePair(key, value); + _arrayStorage[Count] = new KeyValuePair(key, value); _count++; return true; } /// - public bool TryGetValue(string key, out object? value) + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) { if (key == null) { @@ -594,6 +584,12 @@ private static void ThrowArgumentNullExceptionForKey() throw new ArgumentNullException("key"); } + [DoesNotReturn] + private static void ThrowKeyNotFoundException(string keyName) + { + throw new KeyNotFoundException($"The given key '{keyName}' was not present in the dictionary."); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnsureCapacity(int capacity) { @@ -606,18 +602,19 @@ private void EnsureCapacitySlow(int capacity) { if (capacity < DefaultArrayThreshold) { - _backup = new Dictionary(capacity); + _backup = new Dictionary(capacity); foreach (var item in _arrayStorage) { _backup[item.Key] = item.Value; } + // Don't use _count or _arrayStorage anymore // TODO clear arrays here? } else { capacity = _arrayStorage.Length == 0 ? DefaultArrayThreshold : _arrayStorage.Length * 2; - var array = new KeyValuePair[capacity]; + var array = new KeyValuePair[capacity]; if (_count > 0) { Array.Copy(_arrayStorage, 0, array, 0, _count); @@ -629,7 +626,7 @@ private void EnsureCapacitySlow(int capacity) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int FindIndex(string key) + private int FindIndex(TKey key) { // Generally the bounds checking here will be elided by the JIT because this will be called // on the same code path as EnsureCapacity. @@ -638,7 +635,7 @@ private int FindIndex(string key) for (var i = 0; i < count; i++) { - if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase)) + if (_comparer.Equals(array[i].Key, key)) { return i; } @@ -648,7 +645,7 @@ private int FindIndex(string key) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool TryFindItem(string key, out object? value) + private bool TryFindItem(TKey key, out TValue? value) { var array = _arrayStorage; var count = _count; @@ -658,7 +655,7 @@ private bool TryFindItem(string key, out object? value) { for (var i = 0; i < count; i++) { - if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase)) + if (_comparer.Equals(array[i].Key, key)) { value = array[i].Value; return true; @@ -666,12 +663,12 @@ private bool TryFindItem(string key, out object? value) } } - value = null; + value = default; return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool ContainsKeyArray(string key) + private bool ContainsKeyArray(TKey key) { var array = _arrayStorage; var count = _count; @@ -681,7 +678,7 @@ private bool ContainsKeyArray(string key) { for (var i = 0; i < count; i++) { - if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase)) + if (_comparer.Equals(array[i].Key, key)) { return true; } @@ -692,16 +689,16 @@ private bool ContainsKeyArray(string key) } /// - public struct Enumerator : IEnumerator> + public struct Enumerator : IEnumerator> { - private readonly SmallCapacityDictionary _dictionary; + private readonly SmallCapacityDictionary _dictionary; private int _index; /// /// Instantiates a new enumerator with the values provided in . /// - /// A . - public Enumerator(SmallCapacityDictionary dictionary) + /// A . + public Enumerator(SmallCapacityDictionary dictionary) { if (dictionary == null) { @@ -715,7 +712,7 @@ public Enumerator(SmallCapacityDictionary dictionary) } /// - public KeyValuePair Current { get; private set; } + public KeyValuePair Current { get; private set; } object IEnumerator.Current => Current; From 65d1055b25787281839c39bf19a3f5983db367dd Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 29 Mar 2021 12:08:38 -0700 Subject: [PATCH 05/28] Some benchmarks --- ...oft.AspNetCore.Http.Microbenchmarks.csproj | 1 - .../SmallCapacityDictionaryBenchmark.cs | 86 +++++++++++++++++++ .../src/Internal/RequestCookieCollection.cs | 18 ++-- src/Http/Shared/CookieHeaderParserShared.cs | 2 +- .../Dictionary/SmallCapacityDictionary.cs | 27 ++++-- 5 files changed, 116 insertions(+), 18 deletions(-) create mode 100644 src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs diff --git a/src/Http/Http/perf/Microbenchmarks/Microsoft.AspNetCore.Http.Microbenchmarks.csproj b/src/Http/Http/perf/Microbenchmarks/Microsoft.AspNetCore.Http.Microbenchmarks.csproj index 01539b81e19c..f5d277301b12 100644 --- a/src/Http/Http/perf/Microbenchmarks/Microsoft.AspNetCore.Http.Microbenchmarks.csproj +++ b/src/Http/Http/perf/Microbenchmarks/Microsoft.AspNetCore.Http.Microbenchmarks.csproj @@ -11,7 +11,6 @@ - diff --git a/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs b/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs new file mode 100644 index 000000000000..6bf9a300bc88 --- /dev/null +++ b/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs @@ -0,0 +1,86 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Internal.Dictionary; +using Microsoft.Extensions.Primitives; +namespace Microsoft.AspNetCore.Http +{ + public class SmallCapacityDictionaryBenchmark + { + private SmallCapacityDictionary _smallCapDict; + private SmallCapacityDictionary _smallCapDictFour; + private Dictionary _dict; + private Dictionary _dictFour; + private KeyValuePair _oneValue; + private List> _fourValues; + + [IterationSetup] + public void Setup() + { + _oneValue = new KeyValuePair("a", "b"); + + _fourValues = new List>() + { + new KeyValuePair("a", "b"), + new KeyValuePair("c", "d"), + new KeyValuePair("e", "f"), + new KeyValuePair("g", "h"), + }; + + _smallCapDict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase, capacity: 1); + _smallCapDictFour = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase, capacity: 4); + _dict = new Dictionary(1, StringComparer.OrdinalIgnoreCase); + _dictFour = new Dictionary(4, StringComparer.OrdinalIgnoreCase); + } + + [Benchmark] + public void OneValue_SmallDict() + { + _smallCapDict[_oneValue.Key] = _oneValue.Value; + _ = _smallCapDict[_oneValue.Key]; + } + + [Benchmark] + public void OneValue_Dict() + { + _dict[_oneValue.Key] = _oneValue.Value; + _ = _dict[_oneValue.Key]; + } + + [Benchmark] + public void FourValues_SmallDict() + { + foreach (var val in _fourValues) + { + _smallCapDictFour[val.Key] = val.Value; + _ = _smallCapDictFour[val.Key]; + } + } + + [Benchmark] + public void FourValues_Dict() + { + foreach (var val in _fourValues) + { + _dictFour[val.Key] = val.Value; + _ = _dictFour[val.Key]; + } + } + + [Benchmark] + public void SmallDict() + { + _ = new SmallCapacityDictionary(capacity: 1); + } + + [Benchmark] + public void Dict() + { + _ = new Dictionary(capacity: 1); + } + + } +} diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs index fd9f28eb591a..5748aafee4bc 100644 --- a/src/Http/Http/src/Internal/RequestCookieCollection.cs +++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs @@ -5,13 +5,8 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -<<<<<<< HEAD using Microsoft.Extensions.Primitives; -======= -using System.Linq; using Microsoft.AspNetCore.Internal.Dictionary; -using Microsoft.AspNetCore.Routing; ->>>>>>> 41cfee2cfe (Trying routevaluedict) using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Http @@ -27,14 +22,19 @@ internal class RequestCookieCollection : IRequestCookieCollection private SmallCapacityDictionary Store { get; set; } - public RequestCookieCollection(Dictionary store) + public RequestCookieCollection() { - Store = new SmallCapacityDictionary(store); + Store = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase); } - public RequestCookieCollection() + public RequestCookieCollection(int capacity) { - Store = new SmallCapacityDictionary(); + Store = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase, capacity); + } + + public RequestCookieCollection(Dictionary store) + { + Store = new SmallCapacityDictionary(store); } public string? this[string key] diff --git a/src/Http/Shared/CookieHeaderParserShared.cs b/src/Http/Shared/CookieHeaderParserShared.cs index 061fe0b52874..e72b2e45c1d5 100644 --- a/src/Http/Shared/CookieHeaderParserShared.cs +++ b/src/Http/Shared/CookieHeaderParserShared.cs @@ -11,7 +11,7 @@ namespace Microsoft.Net.Http.Headers { internal static class CookieHeaderParserShared { - public static bool TryParseValues(StringValues values, Dictionary store, bool enableCookieNameEncoding, bool supportsMultipleValues) + public static bool TryParseValues(StringValues values, IDictionary store, bool enableCookieNameEncoding, bool supportsMultipleValues) { // If a parser returns an empty list, it means there was no value, but that's valid (e.g. "Accept: "). The caller // can ignore the value. diff --git a/src/Shared/Dictionary/SmallCapacityDictionary.cs b/src/Shared/Dictionary/SmallCapacityDictionary.cs index e9a2a290ee9d..14091fcc9db6 100644 --- a/src/Shared/Dictionary/SmallCapacityDictionary.cs +++ b/src/Shared/Dictionary/SmallCapacityDictionary.cs @@ -20,7 +20,6 @@ internal class SmallCapacityDictionary : IDictionary internal KeyValuePair[] _arrayStorage; private int _count; private Dictionary? _backup; - private int _threshold = DefaultArrayThreshold; private IEqualityComparer _comparer; /// @@ -93,20 +92,27 @@ public static SmallCapacityDictionary FromArray(KeyValuePair. /// public SmallCapacityDictionary() + : this(EqualityComparer.Default, 0) { - _comparer = EqualityComparer.Default; - _arrayStorage = Array.Empty>(); } public SmallCapacityDictionary(Dictionary dict) + : this(EqualityComparer.Default, 0) { _backup = dict; - _comparer = EqualityComparer.Default; - - _arrayStorage = Array.Empty>(); } public SmallCapacityDictionary(IEqualityComparer comparer) + : this(comparer, 0) + { + } + + public SmallCapacityDictionary(int capacity) + : this(EqualityComparer.Default, capacity) + { + } + + public SmallCapacityDictionary(IEqualityComparer comparer, int capacity) { if (comparer is not null && comparer != EqualityComparer.Default) // first check for null to avoid forcing default comparer instantiation unnecessarily { @@ -117,7 +123,14 @@ public SmallCapacityDictionary(IEqualityComparer comparer) _comparer = EqualityComparer.Default; } - _arrayStorage = Array.Empty>(); + if (capacity == 0) + { + _arrayStorage = Array.Empty>(); + } + else + { + _arrayStorage = new KeyValuePair[capacity]; + } } /// From 183287f242c6cd30a2403c37b437d5158ba48be0 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 29 Mar 2021 12:49:01 -0700 Subject: [PATCH 06/28] Fixing for large capacity --- .../SmallCapacityDictionaryBenchmark.cs | 66 ++++++++++++++++++- .../Dictionary/SmallCapacityDictionary.cs | 7 +- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs b/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs index 6bf9a300bc88..6c84fd104a5b 100644 --- a/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs +++ b/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs @@ -5,17 +5,21 @@ using System.Collections.Generic; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Internal.Dictionary; -using Microsoft.Extensions.Primitives; + namespace Microsoft.AspNetCore.Http { public class SmallCapacityDictionaryBenchmark { private SmallCapacityDictionary _smallCapDict; private SmallCapacityDictionary _smallCapDictFour; + private SmallCapacityDictionary _smallCapDictTen; private Dictionary _dict; private Dictionary _dictFour; + private Dictionary _dictTen; + private KeyValuePair _oneValue; private List> _fourValues; + private List> _tenValues; [IterationSetup] public void Setup() @@ -30,10 +34,26 @@ public void Setup() new KeyValuePair("g", "h"), }; + _tenValues = new List>() + { + new KeyValuePair("a", "b"), + new KeyValuePair("c", "d"), + new KeyValuePair("e", "f"), + new KeyValuePair("g", "h"), + new KeyValuePair("i", "j"), + new KeyValuePair("k", "l"), + new KeyValuePair("m", "n"), + new KeyValuePair("o", "p"), + new KeyValuePair("q", "r"), + new KeyValuePair("s", "t"), + }; + _smallCapDict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase, capacity: 1); _smallCapDictFour = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase, capacity: 4); + _smallCapDictTen = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase, capacity: 10); _dict = new Dictionary(1, StringComparer.OrdinalIgnoreCase); _dictFour = new Dictionary(4, StringComparer.OrdinalIgnoreCase); + _dictTen = new Dictionary(10, StringComparer.OrdinalIgnoreCase); } [Benchmark] @@ -70,6 +90,26 @@ public void FourValues_Dict() } } + [Benchmark] + public void TenValues_SmallDict() + { + foreach (var val in _tenValues) + { + _smallCapDictTen[val.Key] = val.Value; + _ = _smallCapDictTen[val.Key]; + } + } + + [Benchmark] + public void TenValues_Dict() + { + foreach (var val in _tenValues) + { + _dictTen[val.Key] = val.Value; + _ = _dictTen[val.Key]; + } + } + [Benchmark] public void SmallDict() { @@ -82,5 +122,29 @@ public void Dict() _ = new Dictionary(capacity: 1); } + + [Benchmark] + public void SmallDictFour() + { + _ = new SmallCapacityDictionary(capacity: 4); + } + + [Benchmark] + public void DictFour() + { + _ = new Dictionary(capacity: 4); + } + + [Benchmark] + public void SmallDictTen() + { + _ = new SmallCapacityDictionary(capacity: 10); + } + + [Benchmark] + public void DictTen() + { + _ = new Dictionary(capacity: 10); + } } } diff --git a/src/Shared/Dictionary/SmallCapacityDictionary.cs b/src/Shared/Dictionary/SmallCapacityDictionary.cs index 14091fcc9db6..17a646ea03dc 100644 --- a/src/Shared/Dictionary/SmallCapacityDictionary.cs +++ b/src/Shared/Dictionary/SmallCapacityDictionary.cs @@ -127,10 +127,15 @@ public SmallCapacityDictionary(IEqualityComparer comparer, int capacity) { _arrayStorage = Array.Empty>(); } - else + else if (capacity <= DefaultArrayThreshold) { _arrayStorage = new KeyValuePair[capacity]; } + else + { + _backup = new Dictionary(capacity: 10); + _arrayStorage = Array.Empty>(); + } } /// From 8458629c72fcf5de46b1f61e1be7f7526892db24 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 29 Mar 2021 16:31:11 -0700 Subject: [PATCH 07/28] Tests --- .../Dictionary/SmallCapacityDictionary.cs | 20 +- .../Microsoft.AspNetCore.Shared.Tests.csproj | 3 +- .../SmallCapacityDictionaryTests.cs | 1501 +++++++++++++++++ 3 files changed, 1515 insertions(+), 9 deletions(-) create mode 100644 src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs diff --git a/src/Shared/Dictionary/SmallCapacityDictionary.cs b/src/Shared/Dictionary/SmallCapacityDictionary.cs index 17a646ea03dc..074702970c44 100644 --- a/src/Shared/Dictionary/SmallCapacityDictionary.cs +++ b/src/Shared/Dictionary/SmallCapacityDictionary.cs @@ -19,7 +19,7 @@ internal class SmallCapacityDictionary : IDictionary internal KeyValuePair[] _arrayStorage; private int _count; - private Dictionary? _backup; + internal Dictionary? _backup; private IEqualityComparer _comparer; /// @@ -28,23 +28,25 @@ internal class SmallCapacityDictionary : IDictionary /// /// The items array. /// A new . - public static SmallCapacityDictionary FromArray(KeyValuePair[] items) + public static SmallCapacityDictionary FromArray(KeyValuePair[] items, IEqualityComparer? comparer = null) { if (items == null) { throw new ArgumentNullException(nameof(items)); } + comparer = comparer ?? EqualityComparer.Default; + if (items.Length > DefaultArrayThreshold) { // Don't use dictionary for large arrays. - var dict = new Dictionary(); + var dict = new Dictionary(comparer); foreach (var item in items) { dict[item.Key] = item.Value; } - return new SmallCapacityDictionary() + return new SmallCapacityDictionary(comparer) { _backup = dict }; @@ -328,7 +330,7 @@ public void Add(TKey key, TValue value) if (ContainsKeyArray(key)) { - throw new ArgumentException($"An element with the key '{nameof(key)}' already exists in the {nameof(SmallCapacityDictionary)}."); + throw new ArgumentException($"An element with the key '{key}' already exists in the {nameof(SmallCapacityDictionary)}.", nameof(key)); } _arrayStorage[_count] = new KeyValuePair(key, value); @@ -618,7 +620,7 @@ private void EnsureCapacitySlow(int capacity) { if (_arrayStorage.Length < capacity) { - if (capacity < DefaultArrayThreshold) + if (capacity > DefaultArrayThreshold) { _backup = new Dictionary(capacity); foreach (var item in _arrayStorage) @@ -747,14 +749,16 @@ public void Dispose() public bool MoveNext() { var dictionary = _dictionary; + if (dictionary._arrayStorage.Length >= _index) + { + return false; + } - // The uncommon case is that the propertyStorage is in use Current = dictionary._arrayStorage[_index]; _index++; return true; } - /// public void Reset() { diff --git a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj index b2ab2174f95e..c8e8888b6e2d 100644 --- a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj +++ b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) @@ -27,6 +27,7 @@ + diff --git a/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs b/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs new file mode 100644 index 000000000000..13a05336f364 --- /dev/null +++ b/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs @@ -0,0 +1,1501 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Internal.Dictionary; +using Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Routing.Tests +{ + public class SmallCapacityDictionaryTests + { + [Fact] + public void DefaultCtor() + { + // Arrange + // Act + var dict = new SmallCapacityDictionary(); + + // Assert + Assert.Empty(dict); + Assert.Empty(dict._arrayStorage); + Assert.Null(dict._backup); + } + + [Fact] + public void CreateFromNull() + { + // Arrange + // Act + var dict = new SmallCapacityDictionary(dict: null); + + // Assert + Assert.Empty(dict); + Assert.Empty(dict._arrayStorage); + Assert.Null(dict._backup); + } + + public static KeyValuePair[] IEnumerableKeyValuePairData + { + get + { + return new[] + { + new KeyValuePair("Name", "James"), + new KeyValuePair("Age", 30), + new KeyValuePair("Address", new Address() { City = "Redmond", State = "WA" }) + }; + } + } + + public static KeyValuePair[] IEnumerableStringValuePairData + { + get + { + return new[] + { + new KeyValuePair("First Name", "James"), + new KeyValuePair("Last Name", "Henrik"), + new KeyValuePair("Middle Name", "Bob") + }; + } + } + + [Fact] + public void CreateFromIEnumerableKeyValuePair_CopiesValues() + { + // Arrange & Act + var dict = SmallCapacityDictionary.FromArray(IEnumerableKeyValuePairData); + + // Assert + Assert.IsType[]>(dict._arrayStorage); + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("Address", kvp.Key); + var address = Assert.IsType
(kvp.Value); + Assert.Equal("Redmond", address.City); + Assert.Equal("WA", address.State); + }, + kvp => { Assert.Equal("Age", kvp.Key); Assert.Equal(30, kvp.Value); }, + kvp => { Assert.Equal("Name", kvp.Key); Assert.Equal("James", kvp.Value); }); + } + + [Fact] + public void CreateFromIEnumerableStringValuePair_CopiesValues() + { + // Arrange & Act + var dict = new SmallCapacityDictionary(IEnumerableStringValuePairData); + + // Assert + Assert.IsType[]>(dict._arrayStorage); + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("First Name", kvp.Key); Assert.Equal("James", kvp.Value); }, + kvp => { Assert.Equal("Last Name", kvp.Key); Assert.Equal("Henrik", kvp.Value); }, + kvp => { Assert.Equal("Middle Name", kvp.Key); Assert.Equal("Bob", kvp.Value); }); + } + + [Fact] + public void CreateFromIEnumerableKeyValuePair_ThrowsExceptionForDuplicateKey() + { + // Arrange + var values = new List>() + { + new KeyValuePair("name", "Billy"), + new KeyValuePair("Name", "Joey"), + }; + + // Act & Assert + ExceptionAssert.ThrowsArgument( + () => SmallCapacityDictionary.FromArray(values.ToArray()), + "key", + $"An element with the key 'Name' already exists in the {nameof(SmallCapacityDictionary)}."); + } + + [Fact] + public void CreateFromIEnumerableStringValuePair_ThrowsExceptionForDuplicateKey() + { + // Arrange + var values = new List>() + { + new KeyValuePair("name", "Billy"), + new KeyValuePair("Name", "Joey"), + }; + + // Act & Assert + ExceptionAssert.ThrowsArgument( + () => new SmallCapacityDictionary(values), + "key", + $"An element with the key 'Name' already exists in the {nameof(SmallCapacityDictionary)}."); + } + + [Fact] + public void Comparer_IsOrdinalIgnoreCase() + { + // Arrange + // Act + var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase); + + // Assert + Assert.Same(StringComparer.OrdinalIgnoreCase, dict.Comparer); + } + + // Our comparer is hardcoded to be IsReadOnly==false no matter what. + [Fact] + public void IsReadOnly_False() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var result = ((ICollection>)dict).IsReadOnly; + + // Assert + Assert.False(result); + } + + [Fact] + public void IndexGet_EmptyStringIsAllowed() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var value = dict[""]; + + // Assert + Assert.Null(value); + } + + [Fact] + public void IndexGet_EmptyStorage_ReturnsNull() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var value = dict["key"]; + + // Assert + Assert.Null(value); + } + + [Fact] + public void IndexGet_backup_NoMatch_ReturnsNull() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + dict.Add("age", 30); + // Act + var value = dict["key"]; + + // Assert + Assert.Null(value); + Assert.NotNull(dict._backup); + } + + [Fact] + public void IndexGet_backup_Match_ReturnsValue() + { + // Arrange + var dict = new SmallCapacityDictionary(); + dict.Add("key", "value"); + + // Act + var value = dict["key"]; + + // Assert + Assert.Equal("value", value); + Assert.NotNull(dict._backup); + } + + [Fact] + public void IndexGet_backup_MatchIgnoreCase_ReturnsValue() + { + // Arrange + var dict = new SmallCapacityDictionary(); + dict.Add("key", "value"); + + // Act + var value = dict["kEy"]; + + // Assert + Assert.Equal("value", value); + Assert.NotNull(dict._backup); + } + + [Fact] + public void IndexGet_ArrayStorage_NoMatch_ReturnsNull() + { + // Arrange + var dict = new SmallCapacityDictionary(); + dict.Add("age", 30); + + // Act + var value = dict["key"]; + + // Assert + Assert.Null(value); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void IndexGet_ListStorage_Match_ReturnsValue() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + var value = dict["key"]; + + // Assert + Assert.Equal("value", value); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void IndexGet_ListStorage_MatchIgnoreCase_ReturnsValue() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + var value = dict["kEy"]; + + // Assert + Assert.Equal("value", value); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void IndexSet_EmptyStringIsAllowed() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + dict[""] = "foo"; + + // Assert + Assert.Equal("foo", dict[""]); + } + + [Fact] + public void IndexSet_EmptyStorage_UpgradesToList() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + dict["key"] = "value"; + + // Assert + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void IndexSet_backup_NoMatch_AddsValue() + { + // Arrange + var dict = new SmallCapacityDictionary(); + dict.Add("age", 30); + + // Act + dict["key"] = "value"; + + // Assert + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); }, + kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void IndexSet_backup_MatchIgnoreCase_SetsValue() + { + // Arrange + var dict = new SmallCapacityDictionary(); + dict.Add("key", "value"); + + // Act + dict["kEy"] = "value"; + + // Assert + Assert.Collection(dict, kvp => { Assert.Equal("kEy", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void IndexSet_ListStorage_NoMatch_AddsValue() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "age", 30 }, + }; + + // Act + dict["key"] = "value"; + + // Assert + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); }, + kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void IndexSet_ListStorage_Match_SetsValue() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + dict["key"] = "value"; + + // Assert + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void IndexSet_ListStorage_MatchIgnoreCase_SetsValue() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + dict["key"] = "value"; + + // Assert + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Count_EmptyStorage() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var count = dict.Count; + + // Assert + Assert.Equal(0, count); + } + + [Fact] + public void Count_ListStorage() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + var count = dict.Count; + + // Assert + Assert.Equal(1, count); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Keys_EmptyStorage() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var keys = dict.Keys; + + // Assert + Assert.Empty(keys); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Keys_ListStorage() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + var keys = dict.Keys; + + // Assert + Assert.Equal(new[] { "key" }, keys); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Values_EmptyStorage() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var values = dict.Values; + + // Assert + Assert.Empty(values); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Values_ListStorage() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + var values = dict.Values; + + // Assert + Assert.Equal(new object[] { "value" }, values); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Add_EmptyStorage() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + dict.Add("key", "value"); + + // Assert + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Add_EmptyStringIsAllowed() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + dict.Add("", "foo"); + + // Assert + Assert.Equal("foo", dict[""]); + } + + [Fact] + public void Add_ListStorage() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "age", 30 }, + }; + + // Act + dict.Add("key", "value"); + + // Assert + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); }, + kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Add_DuplicateKey() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + var message = $"An element with the key 'key' already exists in the {nameof(SmallCapacityDictionary)}"; + + // Act & Assert + ExceptionAssert.ThrowsArgument(() => dict.Add("key", "value2"), "key", message); + + // Assert + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Add_DuplicateKey_CaseInsensitive() + { + // Arrange + var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) + { + { "key", "value" }, + }; + + var message = $"An element with the key 'kEy' already exists in the {nameof(SmallCapacityDictionary)}"; + + // Act & Assert + ExceptionAssert.ThrowsArgument(() => dict.Add("kEy", "value2"), "key", message); + + // Assert + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Add_KeyValuePair() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "age", 30 }, + }; + + // Act + ((ICollection>)dict).Add(new KeyValuePair("key", "value")); + + // Assert + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); }, + kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Clear_EmptyStorage() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + dict.Clear(); + + // Assert + Assert.Empty(dict); + } + + [Fact] + public void Clear_ListStorage() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + dict.Clear(); + + // Assert + Assert.Empty(dict); + Assert.IsType[]>(dict._arrayStorage); + Assert.Null(dict._backup); + } + + [Fact] + public void Contains_ListStorage_KeyValuePair_True() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + var input = new KeyValuePair("key", "value"); + + // Act + var result = ((ICollection>)dict).Contains(input); + + // Assert + Assert.True(result); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Contains_ListStory_KeyValuePair_True_CaseInsensitive() + { + // Arrange + var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) + { + { "key", "value" }, + }; + + var input = new KeyValuePair("KEY", "value"); + + // Act + var result = ((ICollection>)dict).Contains(input); + + // Assert + Assert.True(result); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Contains_ListStorage_KeyValuePair_False() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + var input = new KeyValuePair("other", "value"); + + // Act + var result = ((ICollection>)dict).Contains(input); + + // Assert + Assert.False(result); + Assert.IsType[]>(dict._arrayStorage); + } + + // Value comparisons use the default equality comparer. + [Fact] + public void Contains_ListStorage_KeyValuePair_False_ValueComparisonIsDefault() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + var input = new KeyValuePair("key", "valUE"); + + // Act + var result = ((ICollection>)dict).Contains(input); + + // Assert + Assert.False(result); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void ContainsKey_EmptyStorage() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var result = dict.ContainsKey("key"); + + // Assert + Assert.False(result); + } + + [Fact] + public void ContainsKey_EmptyStringIsAllowed() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var result = dict.ContainsKey(""); + + // Assert + Assert.False(result); + } + + [Fact] + public void ContainsKey_ListStorage_False() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.ContainsKey("other"); + + // Assert + Assert.False(result); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void ContainsKey_ListStorage_True() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.ContainsKey("key"); + + // Assert + Assert.True(result); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void ContainsKey_ListStorage_True_CaseInsensitive() + { + // Arrange + var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) + { + { "key", "value" }, + }; + + // Act + var result = dict.ContainsKey("kEy"); + + // Assert + Assert.True(result); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void CopyTo() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + var array = new KeyValuePair[2]; + + // Act + ((ICollection>)dict).CopyTo(array, 1); + + // Assert + Assert.Equal( + new KeyValuePair[] + { + default(KeyValuePair), + new KeyValuePair("key", "value") + }, + array); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyValuePair_True() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + var input = new KeyValuePair("key", "value"); + + // Act + var result = ((ICollection>)dict).Remove(input); + + // Assert + Assert.True(result); + Assert.Empty(dict); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyValuePair_True_CaseInsensitive() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + var input = new KeyValuePair("KEY", "value"); + + // Act + var result = ((ICollection>)dict).Remove(input); + + // Assert + Assert.True(result); + Assert.Empty(dict); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyValuePair_False() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + var input = new KeyValuePair("other", "value"); + + // Act + var result = ((ICollection>)dict).Remove(input); + + // Assert + Assert.False(result); + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + // Value comparisons use the default equality comparer. + [Fact] + public void Remove_KeyValuePair_False_ValueComparisonIsDefault() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + var input = new KeyValuePair("key", "valUE"); + + // Act + var result = ((ICollection>)dict).Remove(input); + + // Assert + Assert.False(result); + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_EmptyStorage() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var result = dict.Remove("key"); + + // Assert + Assert.False(result); + } + + [Fact] + public void Remove_EmptyStringIsAllowed() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var result = dict.Remove(""); + + // Assert + Assert.False(result); + } + + [Fact] + public void Remove_ListStorage_False() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.Remove("other"); + + // Assert + Assert.False(result); + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_ListStorage_True() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.Remove("key"); + + // Assert + Assert.True(result); + Assert.Empty(dict); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_ListStorage_True_CaseInsensitive() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.Remove("kEy"); + + // Assert + Assert.True(result); + Assert.Empty(dict); + Assert.IsType[]>(dict._arrayStorage); + } + + + [Fact] + public void Remove_KeyAndOutValue_EmptyStorage() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var result = dict.Remove("key", out var removedValue); + + // Assert + Assert.False(result); + Assert.Null(removedValue); + } + + [Fact] + public void Remove_KeyAndOutValue_EmptyStringIsAllowed() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var result = dict.Remove("", out var removedValue); + + // Assert + Assert.False(result); + Assert.Null(removedValue); + } + + [Fact] + public void Remove_KeyAndOutValue_ListStorage_False() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.Remove("other", out var removedValue); + + // Assert + Assert.False(result); + Assert.Null(removedValue); + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyAndOutValue_ListStorage_True() + { + // Arrange + object value = "value"; + var dict = new SmallCapacityDictionary() + { + { "key", value } + }; + + // Act + var result = dict.Remove("key", out var removedValue); + + // Assert + Assert.True(result); + Assert.Same(value, removedValue); + Assert.Empty(dict); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyAndOutValue_ListStorage_True_CaseInsensitive() + { + // Arrange + object value = "value"; + var dict = new SmallCapacityDictionary() + { + { "key", value } + }; + + // Act + var result = dict.Remove("kEy", out var removedValue); + + // Assert + Assert.True(result); + Assert.Same(value, removedValue); + Assert.Empty(dict); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyAndOutValue_ListStorage_KeyExists_First() + { + // Arrange + object value = "value"; + var dict = new SmallCapacityDictionary() + { + { "key", value }, + { "other", 5 }, + { "dotnet", "rocks" } + }; + + // Act + var result = dict.Remove("key", out var removedValue); + + // Assert + Assert.True(result); + Assert.Same(value, removedValue); + Assert.Equal(2, dict.Count); + Assert.False(dict.ContainsKey("key")); + Assert.True(dict.ContainsKey("other")); + Assert.True(dict.ContainsKey("dotnet")); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyAndOutValue_ListStorage_KeyExists_Middle() + { + // Arrange + object value = "value"; + var dict = new SmallCapacityDictionary() + { + { "other", 5 }, + { "key", value }, + { "dotnet", "rocks" } + }; + + // Act + var result = dict.Remove("key", out var removedValue); + + // Assert + Assert.True(result); + Assert.Same(value, removedValue); + Assert.Equal(2, dict.Count); + Assert.False(dict.ContainsKey("key")); + Assert.True(dict.ContainsKey("other")); + Assert.True(dict.ContainsKey("dotnet")); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyAndOutValue_ListStorage_KeyExists_Last() + { + // Arrange + object value = "value"; + var dict = new SmallCapacityDictionary() + { + { "other", 5 }, + { "dotnet", "rocks" }, + { "key", value } + }; + + // Act + var result = dict.Remove("key", out var removedValue); + + // Assert + Assert.True(result); + Assert.Same(value, removedValue); + Assert.Equal(2, dict.Count); + Assert.False(dict.ContainsKey("key")); + Assert.True(dict.ContainsKey("other")); + Assert.True(dict.ContainsKey("dotnet")); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void TryAdd_EmptyStringIsAllowed() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var result = dict.TryAdd("", "foo"); + + // Assert + Assert.True(result); + } + + [Fact] + public void TryAdd_EmptyStorage_CanAdd() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var result = dict.TryAdd("key", "value"); + + // Assert + Assert.True(result); + Assert.Collection( + dict._arrayStorage, + kvp => Assert.Equal(new KeyValuePair("key", "value"), kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp)); + } + + [Fact] + public void TryAdd_ArrayStorage_CanAdd() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key0", "value0" }, + }; + + // Act + var result = dict.TryAdd("key1", "value1"); + + // Assert + Assert.True(result); + Assert.Collection( + dict._arrayStorage, + kvp => Assert.Equal(new KeyValuePair("key0", "value0"), kvp), + kvp => Assert.Equal(new KeyValuePair("key1", "value1"), kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp)); + } + + [Fact] + public void TryAdd_ArrayStorage_CanAddWithResize() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key0", "value0" }, + { "key1", "value1" }, + { "key2", "value2" }, + { "key3", "value3" }, + }; + + // Act + var result = dict.TryAdd("key4", "value4"); + + // Assert + Assert.True(result); + Assert.Collection( + dict._arrayStorage, + kvp => Assert.Equal(new KeyValuePair("key0", "value0"), kvp), + kvp => Assert.Equal(new KeyValuePair("key1", "value1"), kvp), + kvp => Assert.Equal(new KeyValuePair("key2", "value2"), kvp), + kvp => Assert.Equal(new KeyValuePair("key3", "value3"), kvp), + kvp => Assert.Equal(new KeyValuePair("key4", "value4"), kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp)); + } + + [Fact] + public void TryAdd_ArrayStorage_DoesNotAddWhenKeyIsPresent() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key0", "value0" }, + }; + + // Act + var result = dict.TryAdd("key0", "value1"); + + // Assert + Assert.False(result); + Assert.Collection( + dict._arrayStorage, + kvp => Assert.Equal(new KeyValuePair("key0", "value0"), kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp)); + } + + [Fact] + public void TryGetValue_EmptyStorage() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var result = dict.TryGetValue("key", out var value); + + // Assert + Assert.False(result); + Assert.Null(value); + } + + [Fact] + public void TryGetValue_EmptyStringIsAllowed() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act + var result = dict.TryGetValue("", out var value); + + // Assert + Assert.False(result); + Assert.Null(value); + } + + [Fact] + public void TryGetValue_ListStorage_False() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.TryGetValue("other", out var value); + + // Assert + Assert.False(result); + Assert.Null(value); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void TryGetValue_ListStorage_True() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.TryGetValue("key", out var value); + + // Assert + Assert.True(result); + Assert.Equal("value", value); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void TryGetValue_ListStorage_True_CaseInsensitive() + { + // Arrange + var dict = new SmallCapacityDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.TryGetValue("kEy", out var value); + + // Assert + Assert.True(result); + Assert.Equal("value", value); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void ListStorage_DynamicallyAdjustsCapacity() + { + // Arrange + var dict = new SmallCapacityDictionary(); + + // Act 1 + dict.Add("key", "value"); + + // Assert 1 + var storage = Assert.IsType[]>(dict._arrayStorage); + Assert.Equal(4, storage.Length); + + // Act 2 + dict.Add("key2", "value2"); + dict.Add("key3", "value3"); + dict.Add("key4", "value4"); + dict.Add("key5", "value5"); + + // Assert 2 + storage = Assert.IsType[]>(dict._arrayStorage); + Assert.Equal(8, storage.Length); + } + + [Fact] + public void ListStorage_RemoveAt_RearrangesInnerArray() + { + // Arrange + var dict = new SmallCapacityDictionary(); + dict.Add("key", "value"); + dict.Add("key2", "value2"); + dict.Add("key3", "value3"); + + // Assert 1 + var storage = Assert.IsType[]>(dict._arrayStorage); + Assert.Equal(3, dict.Count); + + // Act + dict.Remove("key2"); + + // Assert 2 + storage = Assert.IsType[]>(dict._arrayStorage); + Assert.Equal(2, dict.Count); + Assert.Equal("key", storage[0].Key); + Assert.Equal("value", storage[0].Value); + Assert.Equal("key3", storage[1].Key); + Assert.Equal("value3", storage[1].Value); + } + + [Fact] + public void FromArray_TakesOwnershipOfArray() + { + // Arrange + var array = new KeyValuePair[] + { + new KeyValuePair("a", 0), + new KeyValuePair("b", 1), + new KeyValuePair("c", 2), + }; + + var dictionary = SmallCapacityDictionary.FromArray(array); + + // Act - modifying the array should modify the dictionary + array[0] = new KeyValuePair("aa", 10); + + // Assert + Assert.Equal(3, dictionary.Count); + Assert.Equal(10, dictionary["aa"]); + } + + [Fact] + public void FromArray_EmptyArray() + { + // Arrange + var array = Array.Empty>(); + + // Act + var dictionary = SmallCapacityDictionary.FromArray(array); + + // Assert + Assert.Empty(dictionary); + } + + [Fact] + public void FromArray_RemovesGapsInArray() + { + // Arrange + var array = new KeyValuePair[] + { + new KeyValuePair(null!, null), + new KeyValuePair("a", 0), + new KeyValuePair(null!, null), + new KeyValuePair(null!, null), + new KeyValuePair("b", 1), + new KeyValuePair("c", 2), + new KeyValuePair("d", 3), + new KeyValuePair(null!, null), + }; + + // Act - calling From should modify the array + var dictionary = SmallCapacityDictionary.FromArray(array); + + // Assert + Assert.Equal(4, dictionary.Count); + Assert.Equal( + new KeyValuePair[] + { + new KeyValuePair("d", 3), + new KeyValuePair("a", 0), + new KeyValuePair("c", 2), + new KeyValuePair("b", 1), + new KeyValuePair(null!, null), + new KeyValuePair(null!, null), + new KeyValuePair(null!, null), + new KeyValuePair(null!, null), + }, + array); + } + + private void AssertEmptyArrayStorage(SmallCapacityDictionary value) + { + Assert.Same(Array.Empty>(), value._arrayStorage); + } + + private class RegularType + { + public bool IsAwesome { get; set; } + + public int CoolnessFactor { get; set; } + } + + private class Visibility + { + private string? PrivateYo { get; set; } + + internal int ItsInternalDealWithIt { get; set; } + + public bool IsPublic { get; set; } + } + + private class StaticProperty + { + public static bool IsStatic { get; set; } + } + + private class SetterOnly + { + private bool _coolSetOnly; + + public bool CoolSetOnly { set { _coolSetOnly = value; } } + } + + private class Base + { + public bool DerivedProperty { get; set; } + } + + private class Derived : Base + { + public bool TotallySweetProperty { get; set; } + } + + private class DerivedHiddenProperty : Base + { + public new int DerivedProperty { get; set; } + } + + private class IndexerProperty + { + public bool this[string key] + { + get { return false; } + set { } + } + } + + private class Address + { + public string? City { get; set; } + + public string? State { get; set; } + } + } +} From a4b53f5c1e334d769eda428971e7cdface9b4e6b Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 29 Mar 2021 16:36:48 -0700 Subject: [PATCH 08/28] Feedback --- .../Dictionary/SmallCapacityDictionary.cs | 42 ++-- .../SmallCapacityDictionaryTests.cs | 190 +++--------------- 2 files changed, 46 insertions(+), 186 deletions(-) diff --git a/src/Shared/Dictionary/SmallCapacityDictionary.cs b/src/Shared/Dictionary/SmallCapacityDictionary.cs index 074702970c44..5e16acddf58c 100644 --- a/src/Shared/Dictionary/SmallCapacityDictionary.cs +++ b/src/Shared/Dictionary/SmallCapacityDictionary.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Internal.Dictionary { /// - /// An type for route values. + /// An type to hold a small amount of items (4 or less in the common case). /// internal class SmallCapacityDictionary : IDictionary, IReadOnlyDictionary where TKey : notnull { @@ -27,6 +27,7 @@ internal class SmallCapacityDictionary : IDictionary /// The new instance will take ownership of the array, and may mutate it. ///
/// The items array. + /// /// A new . public static SmallCapacityDictionary FromArray(KeyValuePair[] items, IEqualityComparer? comparer = null) { @@ -39,11 +40,14 @@ public static SmallCapacityDictionary FromArray(KeyValuePair DefaultArrayThreshold) { - // Don't use dictionary for large arrays. - var dict = new Dictionary(comparer); + // Use dictionary for large arrays. + var dict = new Dictionary(items.Length, comparer); foreach (var item in items) { - dict[item.Key] = item.Value; + if (item.Key != null) + { + dict[item.Key] = item.Value; + } } return new SmallCapacityDictionary(comparer) @@ -153,23 +157,16 @@ public SmallCapacityDictionary(IEqualityComparer comparer, int capacity) /// property names are keys, and property values are the values, and copied into the dictionary. /// Only public instance non-index properties are considered. /// - public SmallCapacityDictionary(IEnumerable> values) + public SmallCapacityDictionary(IEnumerable> values, IEqualityComparer comparer = null, int capacity = 0) { - _comparer = EqualityComparer.Default; + _comparer = comparer ?? EqualityComparer.Default; - if (values is IEnumerable> keyValueEnumerable) - { - _arrayStorage = Array.Empty>(); + _arrayStorage = new KeyValuePair[capacity]; - foreach (var kvp in keyValueEnumerable) - { - Add(kvp.Key, kvp.Value); - } - - return; + foreach (var kvp in values) + { + Add(kvp.Key, kvp.Value); } - - _arrayStorage = Array.Empty>(); } /// @@ -184,11 +181,6 @@ public TValue this[TKey key] TryGetValue(key, out var value); - if (value == null) - { - ThrowKeyNotFoundException(nameof(key)); - } - return value; } @@ -604,12 +596,6 @@ private static void ThrowArgumentNullExceptionForKey() throw new ArgumentNullException("key"); } - [DoesNotReturn] - private static void ThrowKeyNotFoundException(string keyName) - { - throw new KeyNotFoundException($"The given key '{keyName}' was not present in the dictionary."); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnsureCapacity(int capacity) { diff --git a/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs b/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs index 13a05336f364..dadb78df0356 100644 --- a/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs +++ b/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs @@ -92,7 +92,7 @@ public void CreateFromIEnumerableStringValuePair_CopiesValues() var dict = new SmallCapacityDictionary(IEnumerableStringValuePairData); // Assert - Assert.IsType[]>(dict._arrayStorage); + Assert.IsType[]>(dict._arrayStorage); Assert.Collection( dict.OrderBy(kvp => kvp.Key), kvp => { Assert.Equal("First Name", kvp.Key); Assert.Equal("James", kvp.Value); }, @@ -103,16 +103,13 @@ public void CreateFromIEnumerableStringValuePair_CopiesValues() [Fact] public void CreateFromIEnumerableKeyValuePair_ThrowsExceptionForDuplicateKey() { - // Arrange - var values = new List>() - { - new KeyValuePair("name", "Billy"), - new KeyValuePair("Name", "Joey"), - }; - - // Act & Assert + // Arrange, Act & Assert ExceptionAssert.ThrowsArgument( - () => SmallCapacityDictionary.FromArray(values.ToArray()), + () => new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) + { + { "name", "Billy" }, + { "Name", "Joey" } + }, "key", $"An element with the key 'Name' already exists in the {nameof(SmallCapacityDictionary)}."); } @@ -129,7 +126,7 @@ public void CreateFromIEnumerableStringValuePair_ThrowsExceptionForDuplicateKey( // Act & Assert ExceptionAssert.ThrowsArgument( - () => new SmallCapacityDictionary(values), + () => new SmallCapacityDictionary(values, StringComparer.OrdinalIgnoreCase), "key", $"An element with the key 'Name' already exists in the {nameof(SmallCapacityDictionary)}."); } @@ -150,7 +147,7 @@ public void Comparer_IsOrdinalIgnoreCase() public void IsReadOnly_False() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new SmallCapacityDictionary(); // Act var result = ((ICollection>)dict).IsReadOnly; @@ -185,51 +182,6 @@ public void IndexGet_EmptyStorage_ReturnsNull() Assert.Null(value); } - [Fact] - public void IndexGet_backup_NoMatch_ReturnsNull() - { - // Arrange - var dict = new SmallCapacityDictionary(); - - dict.Add("age", 30); - // Act - var value = dict["key"]; - - // Assert - Assert.Null(value); - Assert.NotNull(dict._backup); - } - - [Fact] - public void IndexGet_backup_Match_ReturnsValue() - { - // Arrange - var dict = new SmallCapacityDictionary(); - dict.Add("key", "value"); - - // Act - var value = dict["key"]; - - // Assert - Assert.Equal("value", value); - Assert.NotNull(dict._backup); - } - - [Fact] - public void IndexGet_backup_MatchIgnoreCase_ReturnsValue() - { - // Arrange - var dict = new SmallCapacityDictionary(); - dict.Add("key", "value"); - - // Act - var value = dict["kEy"]; - - // Assert - Assert.Equal("value", value); - Assert.NotNull(dict._backup); - } - [Fact] public void IndexGet_ArrayStorage_NoMatch_ReturnsNull() { @@ -249,7 +201,7 @@ public void IndexGet_ArrayStorage_NoMatch_ReturnsNull() public void IndexGet_ListStorage_Match_ReturnsValue() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary() { { "key", "value" }, }; @@ -266,7 +218,7 @@ public void IndexGet_ListStorage_Match_ReturnsValue() public void IndexGet_ListStorage_MatchIgnoreCase_ReturnsValue() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) { { "key", "value" }, }; @@ -296,7 +248,7 @@ public void IndexSet_EmptyStringIsAllowed() public void IndexSet_EmptyStorage_UpgradesToList() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new SmallCapacityDictionary(); // Act dict["key"] = "value"; @@ -324,21 +276,6 @@ public void IndexSet_backup_NoMatch_AddsValue() Assert.IsType[]>(dict._arrayStorage); } - [Fact] - public void IndexSet_backup_MatchIgnoreCase_SetsValue() - { - // Arrange - var dict = new SmallCapacityDictionary(); - dict.Add("key", "value"); - - // Act - dict["kEy"] = "value"; - - // Assert - Assert.Collection(dict, kvp => { Assert.Equal("kEy", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - [Fact] public void IndexSet_ListStorage_NoMatch_AddsValue() { @@ -363,7 +300,7 @@ public void IndexSet_ListStorage_NoMatch_AddsValue() public void IndexSet_ListStorage_Match_SetsValue() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary() { { "key", "value" }, }; @@ -380,7 +317,7 @@ public void IndexSet_ListStorage_Match_SetsValue() public void IndexSet_ListStorage_MatchIgnoreCase_SetsValue() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary() { { "key", "value" }, }; @@ -808,7 +745,7 @@ public void CopyTo() public void Remove_KeyValuePair_True() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary() { { "key", "value" }, }; @@ -828,7 +765,7 @@ public void Remove_KeyValuePair_True() public void Remove_KeyValuePair_True_CaseInsensitive() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) { { "key", "value" }, }; @@ -848,7 +785,7 @@ public void Remove_KeyValuePair_True_CaseInsensitive() public void Remove_KeyValuePair_False() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary() { { "key", "value" }, }; @@ -869,7 +806,7 @@ public void Remove_KeyValuePair_False() public void Remove_KeyValuePair_False_ValueComparisonIsDefault() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary() { { "key", "value" }, }; @@ -915,7 +852,7 @@ public void Remove_EmptyStringIsAllowed() public void Remove_ListStorage_False() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary() { { "key", "value" }, }; @@ -933,7 +870,7 @@ public void Remove_ListStorage_False() public void Remove_ListStorage_True() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary() { { "key", "value" }, }; @@ -951,7 +888,7 @@ public void Remove_ListStorage_True() public void Remove_ListStorage_True_CaseInsensitive() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) { { "key", "value" }, }; @@ -998,7 +935,7 @@ public void Remove_KeyAndOutValue_EmptyStringIsAllowed() public void Remove_KeyAndOutValue_ListStorage_False() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary() { { "key", "value" }, }; @@ -1038,7 +975,7 @@ public void Remove_KeyAndOutValue_ListStorage_True_CaseInsensitive() { // Arrange object value = "value"; - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) { { "key", value } }; @@ -1182,35 +1119,6 @@ public void TryAdd_ArrayStorage_CanAdd() kvp => Assert.Equal(default, kvp)); } - [Fact] - public void TryAdd_ArrayStorage_CanAddWithResize() - { - // Arrange - var dict = new SmallCapacityDictionary() - { - { "key0", "value0" }, - { "key1", "value1" }, - { "key2", "value2" }, - { "key3", "value3" }, - }; - - // Act - var result = dict.TryAdd("key4", "value4"); - - // Assert - Assert.True(result); - Assert.Collection( - dict._arrayStorage, - kvp => Assert.Equal(new KeyValuePair("key0", "value0"), kvp), - kvp => Assert.Equal(new KeyValuePair("key1", "value1"), kvp), - kvp => Assert.Equal(new KeyValuePair("key2", "value2"), kvp), - kvp => Assert.Equal(new KeyValuePair("key3", "value3"), kvp), - kvp => Assert.Equal(new KeyValuePair("key4", "value4"), kvp), - kvp => Assert.Equal(default, kvp), - kvp => Assert.Equal(default, kvp), - kvp => Assert.Equal(default, kvp)); - } - [Fact] public void TryAdd_ArrayStorage_DoesNotAddWhenKeyIsPresent() { @@ -1265,7 +1173,7 @@ public void TryGetValue_EmptyStringIsAllowed() public void TryGetValue_ListStorage_False() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary() { { "key", "value" }, }; @@ -1283,7 +1191,7 @@ public void TryGetValue_ListStorage_False() public void TryGetValue_ListStorage_True() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary() { { "key", "value" }, }; @@ -1301,7 +1209,7 @@ public void TryGetValue_ListStorage_True() public void TryGetValue_ListStorage_True_CaseInsensitive() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) { { "key", "value" }, }; @@ -1316,10 +1224,10 @@ public void TryGetValue_ListStorage_True_CaseInsensitive() } [Fact] - public void ListStorage_DynamicallyAdjustsCapacity() + public void ListStorage_SwitchesToDictionaryAfter4() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new SmallCapacityDictionary(); // Act 1 dict.Add("key", "value"); @@ -1336,14 +1244,16 @@ public void ListStorage_DynamicallyAdjustsCapacity() // Assert 2 storage = Assert.IsType[]>(dict._arrayStorage); - Assert.Equal(8, storage.Length); + Assert.Equal(4, storage.Length); + + Assert.Equal(8, dict.Count); } [Fact] public void ListStorage_RemoveAt_RearrangesInnerArray() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new SmallCapacityDictionary(); dict.Add("key", "value"); dict.Add("key2", "value2"); dict.Add("key3", "value3"); @@ -1398,42 +1308,6 @@ public void FromArray_EmptyArray() Assert.Empty(dictionary); } - [Fact] - public void FromArray_RemovesGapsInArray() - { - // Arrange - var array = new KeyValuePair[] - { - new KeyValuePair(null!, null), - new KeyValuePair("a", 0), - new KeyValuePair(null!, null), - new KeyValuePair(null!, null), - new KeyValuePair("b", 1), - new KeyValuePair("c", 2), - new KeyValuePair("d", 3), - new KeyValuePair(null!, null), - }; - - // Act - calling From should modify the array - var dictionary = SmallCapacityDictionary.FromArray(array); - - // Assert - Assert.Equal(4, dictionary.Count); - Assert.Equal( - new KeyValuePair[] - { - new KeyValuePair("d", 3), - new KeyValuePair("a", 0), - new KeyValuePair("c", 2), - new KeyValuePair("b", 1), - new KeyValuePair(null!, null), - new KeyValuePair(null!, null), - new KeyValuePair(null!, null), - new KeyValuePair(null!, null), - }, - array); - } - private void AssertEmptyArrayStorage(SmallCapacityDictionary value) { Assert.Same(Array.Empty>(), value._arrayStorage); From 58558d729e290d0aa1899be30ede1111f21d947a Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 29 Mar 2021 16:44:44 -0700 Subject: [PATCH 09/28] nit --- src/Shared/Dictionary/SmallCapacityDictionary.cs | 14 +++++++------- .../Shared.Tests/SmallCapacityDictionaryTests.cs | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Shared/Dictionary/SmallCapacityDictionary.cs b/src/Shared/Dictionary/SmallCapacityDictionary.cs index 5e16acddf58c..71b13be6027c 100644 --- a/src/Shared/Dictionary/SmallCapacityDictionary.cs +++ b/src/Shared/Dictionary/SmallCapacityDictionary.cs @@ -98,27 +98,27 @@ public static SmallCapacityDictionary FromArray(KeyValuePair. ///
public SmallCapacityDictionary() - : this(EqualityComparer.Default, 0) + : this(0, EqualityComparer.Default) { } public SmallCapacityDictionary(Dictionary dict) - : this(EqualityComparer.Default, 0) + : this(0, EqualityComparer.Default) { _backup = dict; } public SmallCapacityDictionary(IEqualityComparer comparer) - : this(comparer, 0) + : this(0, comparer) { } public SmallCapacityDictionary(int capacity) - : this(EqualityComparer.Default, capacity) + : this(capacity, EqualityComparer.Default) { } - public SmallCapacityDictionary(IEqualityComparer comparer, int capacity) + public SmallCapacityDictionary(int capacity, IEqualityComparer comparer) { if (comparer is not null && comparer != EqualityComparer.Default) // first check for null to avoid forcing default comparer instantiation unnecessarily { @@ -157,7 +157,7 @@ public SmallCapacityDictionary(IEqualityComparer comparer, int capacity) /// property names are keys, and property values are the values, and copied into the dictionary. /// Only public instance non-index properties are considered. /// - public SmallCapacityDictionary(IEnumerable> values, IEqualityComparer comparer = null, int capacity = 0) + public SmallCapacityDictionary(IEnumerable> values, IEqualityComparer comparer, int capacity) { _comparer = comparer ?? EqualityComparer.Default; @@ -181,7 +181,7 @@ public TValue this[TKey key] TryGetValue(key, out var value); - return value; + return value!; } set diff --git a/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs b/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs index dadb78df0356..12c371e62183 100644 --- a/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs +++ b/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs @@ -89,7 +89,7 @@ public void CreateFromIEnumerableKeyValuePair_CopiesValues() public void CreateFromIEnumerableStringValuePair_CopiesValues() { // Arrange & Act - var dict = new SmallCapacityDictionary(IEnumerableStringValuePairData); + var dict = new SmallCapacityDictionary(IEnumerableStringValuePairData, StringComparer.OrdinalIgnoreCase, capacity: 3); // Assert Assert.IsType[]>(dict._arrayStorage); @@ -126,7 +126,7 @@ public void CreateFromIEnumerableStringValuePair_ThrowsExceptionForDuplicateKey( // Act & Assert ExceptionAssert.ThrowsArgument( - () => new SmallCapacityDictionary(values, StringComparer.OrdinalIgnoreCase), + () => new SmallCapacityDictionary(values, StringComparer.OrdinalIgnoreCase, capacity: 3), "key", $"An element with the key 'Name' already exists in the {nameof(SmallCapacityDictionary)}."); } From c82a2050b6cedbab5347875b73a7fac4cee704a9 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 29 Mar 2021 16:57:12 -0700 Subject: [PATCH 10/28] nit --- src/Http/Http/src/Internal/RequestCookieCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs index 5748aafee4bc..61b4486dcd5e 100644 --- a/src/Http/Http/src/Internal/RequestCookieCollection.cs +++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs @@ -29,7 +29,7 @@ public RequestCookieCollection() public RequestCookieCollection(int capacity) { - Store = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase, capacity); + Store = new SmallCapacityDictionary(capacity, StringComparer.OrdinalIgnoreCase); } public RequestCookieCollection(Dictionary store) From 2733b313496edf470e38d742ad426ba68db80e0e Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 29 Mar 2021 17:10:57 -0700 Subject: [PATCH 11/28] More build issues --- .../Dictionary/SmallCapacityDictionary.cs | 31 ++++++++++--------- .../SmallCapacityDictionaryTests.cs | 6 ++-- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/Shared/Dictionary/SmallCapacityDictionary.cs b/src/Shared/Dictionary/SmallCapacityDictionary.cs index 71b13be6027c..f36518348f1b 100644 --- a/src/Shared/Dictionary/SmallCapacityDictionary.cs +++ b/src/Shared/Dictionary/SmallCapacityDictionary.cs @@ -27,7 +27,7 @@ internal class SmallCapacityDictionary : IDictionary /// The new instance will take ownership of the array, and may mutate it. /// /// The items array. - /// + /// Equality comparison. /// A new . public static SmallCapacityDictionary FromArray(KeyValuePair[] items, IEqualityComparer? comparer = null) { @@ -102,22 +102,29 @@ public SmallCapacityDictionary() { } - public SmallCapacityDictionary(Dictionary dict) - : this(0, EqualityComparer.Default) - { - _backup = dict; - } - + /// + /// Creates a . + /// + /// Equality comparison. public SmallCapacityDictionary(IEqualityComparer comparer) : this(0, comparer) { } + /// + /// Creates a . + /// + /// Initial capacity. public SmallCapacityDictionary(int capacity) : this(capacity, EqualityComparer.Default) { } + /// + /// Creates a . + /// + /// Initial capacity. + /// Equality comparison. public SmallCapacityDictionary(int capacity, IEqualityComparer comparer) { if (comparer is not null && comparer != EqualityComparer.Default) // first check for null to avoid forcing default comparer instantiation unnecessarily @@ -151,13 +158,9 @@ public SmallCapacityDictionary(int capacity, IEqualityComparer comparer) /// or /// or an object with public properties as key-value pairs. /// - /// - /// If the value is a dictionary or other of , - /// then its entries are copied. Otherwise the object is interpreted as a set of key-value pairs where the - /// property names are keys, and property values are the values, and copied into the dictionary. - /// Only public instance non-index properties are considered. - /// - public SmallCapacityDictionary(IEnumerable> values, IEqualityComparer comparer, int capacity) + /// Equality comparison. + /// Initial capacity. + public SmallCapacityDictionary(IEnumerable> values, int capacity, IEqualityComparer comparer) { _comparer = comparer ?? EqualityComparer.Default; diff --git a/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs b/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs index 12c371e62183..ea6040147d84 100644 --- a/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs +++ b/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs @@ -30,7 +30,7 @@ public void CreateFromNull() { // Arrange // Act - var dict = new SmallCapacityDictionary(dict: null); + var dict = new SmallCapacityDictionary(); // Assert Assert.Empty(dict); @@ -89,7 +89,7 @@ public void CreateFromIEnumerableKeyValuePair_CopiesValues() public void CreateFromIEnumerableStringValuePair_CopiesValues() { // Arrange & Act - var dict = new SmallCapacityDictionary(IEnumerableStringValuePairData, StringComparer.OrdinalIgnoreCase, capacity: 3); + var dict = new SmallCapacityDictionary(IEnumerableStringValuePairData, capacity: 3, StringComparer.OrdinalIgnoreCase); // Assert Assert.IsType[]>(dict._arrayStorage); @@ -126,7 +126,7 @@ public void CreateFromIEnumerableStringValuePair_ThrowsExceptionForDuplicateKey( // Act & Assert ExceptionAssert.ThrowsArgument( - () => new SmallCapacityDictionary(values, StringComparer.OrdinalIgnoreCase, capacity: 3), + () => new SmallCapacityDictionary(values, capacity: 3, StringComparer.OrdinalIgnoreCase), "key", $"An element with the key 'Name' already exists in the {nameof(SmallCapacityDictionary)}."); } From b7d79440b4be86bc9a2f476cbdc386a1c59be821 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 30 Mar 2021 11:20:50 -0700 Subject: [PATCH 12/28] No struct for testing --- src/Http/Http/src/Internal/RequestCookieCollection.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs index 61b4486dcd5e..5ec69f2132c2 100644 --- a/src/Http/Http/src/Internal/RequestCookieCollection.cs +++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Primitives; using Microsoft.AspNetCore.Internal.Dictionary; using Microsoft.Net.Http.Headers; +using System.Linq; namespace Microsoft.AspNetCore.Http { @@ -34,7 +35,7 @@ public RequestCookieCollection(int capacity) public RequestCookieCollection(Dictionary store) { - Store = new SmallCapacityDictionary(store); + Store = new SmallCapacityDictionary(store.ToList(), capacity: store.Count, StringComparer.OrdinalIgnoreCase); } public string? this[string key] From b29bbde02d6c39d91bd99734d8ae4e2aff99831d Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 30 Mar 2021 12:12:55 -0700 Subject: [PATCH 13/28] ordre --- .../Microbenchmarks/SmallCapacityDictionaryBenchmark.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs b/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs index 6c84fd104a5b..92400c9bdff6 100644 --- a/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs +++ b/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs @@ -48,9 +48,9 @@ public void Setup() new KeyValuePair("s", "t"), }; - _smallCapDict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase, capacity: 1); - _smallCapDictFour = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase, capacity: 4); - _smallCapDictTen = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase, capacity: 10); + _smallCapDict = new SmallCapacityDictionary(capacity: 1, StringComparer.OrdinalIgnoreCase); + _smallCapDictFour = new SmallCapacityDictionary(capacity: 4, StringComparer.OrdinalIgnoreCase); + _smallCapDictTen = new SmallCapacityDictionary(capacity: 10, StringComparer.OrdinalIgnoreCase); _dict = new Dictionary(1, StringComparer.OrdinalIgnoreCase); _dictFour = new Dictionary(4, StringComparer.OrdinalIgnoreCase); _dictTen = new Dictionary(10, StringComparer.OrdinalIgnoreCase); From e7e72e7315fcda00b7af924c91530a65e7240ddf Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 30 Mar 2021 17:08:11 -0700 Subject: [PATCH 14/28] Fixing two tests --- src/Shared/Dictionary/SmallCapacityDictionary.cs | 2 +- src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Shared/Dictionary/SmallCapacityDictionary.cs b/src/Shared/Dictionary/SmallCapacityDictionary.cs index f36518348f1b..3855b4b832a2 100644 --- a/src/Shared/Dictionary/SmallCapacityDictionary.cs +++ b/src/Shared/Dictionary/SmallCapacityDictionary.cs @@ -738,7 +738,7 @@ public void Dispose() public bool MoveNext() { var dictionary = _dictionary; - if (dictionary._arrayStorage.Length >= _index) + if (dictionary._arrayStorage.Length <= _index) { return false; } diff --git a/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs b/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs index ea6040147d84..a503fba0dad9 100644 --- a/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs +++ b/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs @@ -1246,7 +1246,7 @@ public void ListStorage_SwitchesToDictionaryAfter4() storage = Assert.IsType[]>(dict._arrayStorage); Assert.Equal(4, storage.Length); - Assert.Equal(8, dict.Count); + Assert.Equal(5, dict.Count); } [Fact] From 96900073705981398e72a64f6fc5875b1f1d5a3b Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 30 Mar 2021 20:16:32 -0700 Subject: [PATCH 15/28] Fix check --- src/Shared/Dictionary/SmallCapacityDictionary.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Shared/Dictionary/SmallCapacityDictionary.cs b/src/Shared/Dictionary/SmallCapacityDictionary.cs index 3855b4b832a2..764e8311e960 100644 --- a/src/Shared/Dictionary/SmallCapacityDictionary.cs +++ b/src/Shared/Dictionary/SmallCapacityDictionary.cs @@ -347,6 +347,7 @@ public void Clear() Array.Clear(_arrayStorage, 0, _count); _count = 0; + _arrayStorage = Array.Empty>(); } /// @@ -738,7 +739,7 @@ public void Dispose() public bool MoveNext() { var dictionary = _dictionary; - if (dictionary._arrayStorage.Length <= _index) + if (dictionary._count <= _index) { return false; } From 6eb437550643c402ec09cffd3d1cd90138b1e5d1 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 31 Mar 2021 09:38:46 -0700 Subject: [PATCH 16/28] Feedback --- .../SmallCapacityDictionaryBenchmark.cs | 20 +-- .../src/Internal/RequestCookieCollection.cs | 12 +- .../Http/src/Microsoft.AspNetCore.Http.csproj | 2 +- ...onary.cs => AdaptiveCapacityDictionary.cs} | 154 ++++++++--------- ....cs => AdaptiveCapacityDictionaryTests.cs} | 158 +++++++++--------- 5 files changed, 170 insertions(+), 176 deletions(-) rename src/Shared/Dictionary/{SmallCapacityDictionary.cs => AdaptiveCapacityDictionary.cs} (79%) rename src/Shared/test/Shared.Tests/{SmallCapacityDictionaryTests.cs => AdaptiveCapacityDictionaryTests.cs} (85%) diff --git a/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs b/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs index 92400c9bdff6..73e0fcf4ae8d 100644 --- a/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs +++ b/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs @@ -8,11 +8,11 @@ namespace Microsoft.AspNetCore.Http { - public class SmallCapacityDictionaryBenchmark + public class AdaptiveCapacityDictionaryBenchmark { - private SmallCapacityDictionary _smallCapDict; - private SmallCapacityDictionary _smallCapDictFour; - private SmallCapacityDictionary _smallCapDictTen; + private AdaptiveCapacityDictionary _smallCapDict; + private AdaptiveCapacityDictionary _smallCapDictFour; + private AdaptiveCapacityDictionary _smallCapDictTen; private Dictionary _dict; private Dictionary _dictFour; private Dictionary _dictTen; @@ -48,9 +48,9 @@ public void Setup() new KeyValuePair("s", "t"), }; - _smallCapDict = new SmallCapacityDictionary(capacity: 1, StringComparer.OrdinalIgnoreCase); - _smallCapDictFour = new SmallCapacityDictionary(capacity: 4, StringComparer.OrdinalIgnoreCase); - _smallCapDictTen = new SmallCapacityDictionary(capacity: 10, StringComparer.OrdinalIgnoreCase); + _smallCapDict = new AdaptiveCapacityDictionary(capacity: 1, StringComparer.OrdinalIgnoreCase); + _smallCapDictFour = new AdaptiveCapacityDictionary(capacity: 4, StringComparer.OrdinalIgnoreCase); + _smallCapDictTen = new AdaptiveCapacityDictionary(capacity: 10, StringComparer.OrdinalIgnoreCase); _dict = new Dictionary(1, StringComparer.OrdinalIgnoreCase); _dictFour = new Dictionary(4, StringComparer.OrdinalIgnoreCase); _dictTen = new Dictionary(10, StringComparer.OrdinalIgnoreCase); @@ -113,7 +113,7 @@ public void TenValues_Dict() [Benchmark] public void SmallDict() { - _ = new SmallCapacityDictionary(capacity: 1); + _ = new AdaptiveCapacityDictionary(capacity: 1); } [Benchmark] @@ -126,7 +126,7 @@ public void Dict() [Benchmark] public void SmallDictFour() { - _ = new SmallCapacityDictionary(capacity: 4); + _ = new AdaptiveCapacityDictionary(capacity: 4); } [Benchmark] @@ -138,7 +138,7 @@ public void DictFour() [Benchmark] public void SmallDictTen() { - _ = new SmallCapacityDictionary(capacity: 10); + _ = new AdaptiveCapacityDictionary(capacity: 10); } [Benchmark] diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs index 5ec69f2132c2..faa310b5296b 100644 --- a/src/Http/Http/src/Internal/RequestCookieCollection.cs +++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs @@ -21,21 +21,21 @@ internal class RequestCookieCollection : IRequestCookieCollection private static readonly IEnumerator> EmptyIEnumeratorType = EmptyEnumerator; private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator; - private SmallCapacityDictionary Store { get; set; } + private AdaptiveCapacityDictionary Store { get; set; } public RequestCookieCollection() { - Store = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase); + Store = new AdaptiveCapacityDictionary(StringComparer.OrdinalIgnoreCase); } public RequestCookieCollection(int capacity) { - Store = new SmallCapacityDictionary(capacity, StringComparer.OrdinalIgnoreCase); + Store = new AdaptiveCapacityDictionary(capacity, StringComparer.OrdinalIgnoreCase); } public RequestCookieCollection(Dictionary store) { - Store = new SmallCapacityDictionary(store.ToList(), capacity: store.Count, StringComparer.OrdinalIgnoreCase); + Store = new AdaptiveCapacityDictionary(store.ToList(), capacity: store.Count, StringComparer.OrdinalIgnoreCase); } public string? this[string key] @@ -177,10 +177,10 @@ IEnumerator IEnumerable.GetEnumerator() public struct Enumerator : IEnumerator> { // Do NOT make this readonly, or MoveNext will not work - private SmallCapacityDictionary.Enumerator _dictionaryEnumerator; + private AdaptiveCapacityDictionary.Enumerator _dictionaryEnumerator; private bool _notEmpty; - internal Enumerator(SmallCapacityDictionary.Enumerator dictionaryEnumerator) + internal Enumerator(AdaptiveCapacityDictionary.Enumerator dictionaryEnumerator) { _dictionaryEnumerator = dictionaryEnumerator; _notEmpty = true; diff --git a/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj index d2281f33202c..8c40428a47dd 100644 --- a/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj +++ b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Shared/Dictionary/SmallCapacityDictionary.cs b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs similarity index 79% rename from src/Shared/Dictionary/SmallCapacityDictionary.cs rename to src/Shared/Dictionary/AdaptiveCapacityDictionary.cs index 764e8311e960..010bdc45dd18 100644 --- a/src/Shared/Dictionary/SmallCapacityDictionary.cs +++ b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -12,24 +13,24 @@ namespace Microsoft.AspNetCore.Internal.Dictionary /// /// An type to hold a small amount of items (4 or less in the common case). /// - internal class SmallCapacityDictionary : IDictionary, IReadOnlyDictionary where TKey : notnull + internal class AdaptiveCapacityDictionary : IDictionary, IReadOnlyDictionary where TKey : notnull { // Threshold for size of array to use. private static readonly int DefaultArrayThreshold = 4; internal KeyValuePair[] _arrayStorage; private int _count; - internal Dictionary? _backup; + internal Dictionary? _dictionaryStorage; private IEqualityComparer _comparer; /// - /// Creates a new from the provided array. + /// Creates a new from the provided array. /// The new instance will take ownership of the array, and may mutate it. /// /// The items array. /// Equality comparison. - /// A new . - public static SmallCapacityDictionary FromArray(KeyValuePair[] items, IEqualityComparer? comparer = null) + /// A new . + public static AdaptiveCapacityDictionary FromArray(KeyValuePair[] items, IEqualityComparer? comparer = null) { if (items == null) { @@ -41,19 +42,7 @@ public static SmallCapacityDictionary FromArray(KeyValuePair DefaultArrayThreshold) { // Use dictionary for large arrays. - var dict = new Dictionary(items.Length, comparer); - foreach (var item in items) - { - if (item.Key != null) - { - dict[item.Key] = item.Value; - } - } - - return new SmallCapacityDictionary(comparer) - { - _backup = dict - }; + return new AdaptiveCapacityDictionary(items, capacity: items.Length,comparer); } // We need to compress the array by removing non-contiguous items. We @@ -87,7 +76,7 @@ public static SmallCapacityDictionary FromArray(KeyValuePair() + return new AdaptiveCapacityDictionary() { _arrayStorage = items!, _count = start, @@ -95,37 +84,37 @@ public static SmallCapacityDictionary FromArray(KeyValuePair - /// Creates an empty . + /// Creates an empty . /// - public SmallCapacityDictionary() + public AdaptiveCapacityDictionary() : this(0, EqualityComparer.Default) { } /// - /// Creates a . + /// Creates a . /// /// Equality comparison. - public SmallCapacityDictionary(IEqualityComparer comparer) + public AdaptiveCapacityDictionary(IEqualityComparer comparer) : this(0, comparer) { } /// - /// Creates a . + /// Creates a . /// /// Initial capacity. - public SmallCapacityDictionary(int capacity) + public AdaptiveCapacityDictionary(int capacity) : this(capacity, EqualityComparer.Default) { } /// - /// Creates a . + /// Creates a . /// /// Initial capacity. /// Equality comparison. - public SmallCapacityDictionary(int capacity, IEqualityComparer comparer) + public AdaptiveCapacityDictionary(int capacity, IEqualityComparer comparer) { if (comparer is not null && comparer != EqualityComparer.Default) // first check for null to avoid forcing default comparer instantiation unnecessarily { @@ -146,13 +135,13 @@ public SmallCapacityDictionary(int capacity, IEqualityComparer comparer) } else { - _backup = new Dictionary(capacity: 10); + _dictionaryStorage = new Dictionary(capacity); _arrayStorage = Array.Empty>(); } } /// - /// Creates a initialized with the specified . + /// Creates a initialized with the specified . /// /// An object to initialize the dictionary. The value can be of type /// or @@ -160,7 +149,7 @@ public SmallCapacityDictionary(int capacity, IEqualityComparer comparer) /// /// Equality comparison. /// Initial capacity. - public SmallCapacityDictionary(IEnumerable> values, int capacity, IEqualityComparer comparer) + public AdaptiveCapacityDictionary(IEnumerable> values, int capacity, IEqualityComparer comparer) { _comparer = comparer ?? EqualityComparer.Default; @@ -194,9 +183,9 @@ public TValue this[TKey key] ThrowArgumentNullExceptionForKey(); } - if (_backup != null) + if (_dictionaryStorage != null) { - _backup[key] = value; + _dictionaryStorage[key] = value; return; } @@ -204,9 +193,9 @@ public TValue this[TKey key] if (index < 0) { EnsureCapacity(_count + 1); - if (_backup != null) + if (_dictionaryStorage != null) { - _backup[key] = value; + _dictionaryStorage[key] = value; return; } _arrayStorage[_count++] = new KeyValuePair(key, value); @@ -223,9 +212,9 @@ public int Count { get { - if (_backup != null) + if (_dictionaryStorage != null) { - return _backup.Count; + return _dictionaryStorage.Count; } return _count; } @@ -248,9 +237,9 @@ public ICollection Keys { get { - if (_backup != null) + if (_dictionaryStorage != null) { - return _backup.Keys; + return _dictionaryStorage.Keys; } var array = _arrayStorage; @@ -271,9 +260,9 @@ public ICollection Values { get { - if (_backup != null) + if (_dictionaryStorage != null) { - return _backup.Values; + return _dictionaryStorage.Values; } var array = _arrayStorage; @@ -292,9 +281,9 @@ public ICollection Values /// void ICollection>.Add(KeyValuePair item) { - if (_backup != null) + if (_dictionaryStorage != null) { - ((ICollection>)_backup).Add(item); + ((ICollection>)_dictionaryStorage).Add(item); return; } @@ -309,23 +298,23 @@ public void Add(TKey key, TValue value) ThrowArgumentNullExceptionForKey(); } - if (_backup != null) + if (_dictionaryStorage != null) { - _backup.Add(key, value); + _dictionaryStorage.Add(key, value); return; } EnsureCapacity(_count + 1); - if (_backup != null) + if (_dictionaryStorage != null) { - _backup.Add(key, value); + _dictionaryStorage.Add(key, value); return; } if (ContainsKeyArray(key)) { - throw new ArgumentException($"An element with the key '{key}' already exists in the {nameof(SmallCapacityDictionary)}.", nameof(key)); + throw new ArgumentException($"An element with the key '{key}' already exists in the {nameof(AdaptiveCapacityDictionary)}.", nameof(key)); } _arrayStorage[_count] = new KeyValuePair(key, value); @@ -335,9 +324,9 @@ public void Add(TKey key, TValue value) /// public void Clear() { - if (_backup != null) + if (_dictionaryStorage != null) { - _backup.Clear(); + _dictionaryStorage.Clear(); } if (_count == 0) @@ -353,9 +342,9 @@ public void Clear() /// bool ICollection>.Contains(KeyValuePair item) { - if (_backup != null) + if (_dictionaryStorage != null) { - return ((ICollection>)_backup).Contains(item); + return ((ICollection>)_dictionaryStorage).Contains(item); } return TryGetValue(item.Key, out var value) && EqualityComparer.Default.Equals(value, item.Value); @@ -369,9 +358,9 @@ public bool ContainsKey(TKey key) ThrowArgumentNullExceptionForKey(); } - if (_backup != null) + if (_dictionaryStorage != null) { - return _backup.ContainsKey(key); + return _dictionaryStorage.ContainsKey(key); } return ContainsKeyCore(key); @@ -393,15 +382,15 @@ void ICollection>.CopyTo( throw new ArgumentNullException(nameof(array)); } - if (arrayIndex < 0 || arrayIndex > array.Length || array.Length - arrayIndex < this.Count) + if ((uint)arrayIndex > array.Length || array.Length - arrayIndex < this.Count) { throw new ArgumentOutOfRangeException(nameof(arrayIndex)); } - if (_backup != null) + if (_dictionaryStorage != null) { var i = 0; - foreach (var kvp in _backup) + foreach (var kvp in _dictionaryStorage) { array[i] = kvp; i++; @@ -428,9 +417,9 @@ public Enumerator GetEnumerator() /// IEnumerator> IEnumerable>.GetEnumerator() { - if (_backup != null) + if (_dictionaryStorage != null) { - return _backup.GetEnumerator(); + return _dictionaryStorage.GetEnumerator(); } return GetEnumerator(); @@ -439,9 +428,9 @@ IEnumerator> IEnumerable>. /// IEnumerator IEnumerable.GetEnumerator() { - if (_backup != null) + if (_dictionaryStorage != null) { - return _backup.GetEnumerator(); + return _dictionaryStorage.GetEnumerator(); } return GetEnumerator(); @@ -450,9 +439,9 @@ IEnumerator IEnumerable.GetEnumerator() /// bool ICollection>.Remove(KeyValuePair item) { - if (_backup != null) + if (_dictionaryStorage != null) { - return ((ICollection>)_backup).Remove(item); + return ((ICollection>)_dictionaryStorage).Remove(item); } if (Count == 0) @@ -481,9 +470,9 @@ public bool Remove(TKey key) ThrowArgumentNullExceptionForKey(); } - if (_backup != null) + if (_dictionaryStorage != null) { - return _backup.Remove(key); + return _dictionaryStorage.Remove(key); } if (Count == 0) @@ -506,10 +495,10 @@ public bool Remove(TKey key) } /// - /// Attempts to remove and return the value that has the specified key from the . + /// Attempts to remove and return the value that has the specified key from the . /// /// The key of the element to remove and return. - /// When this method returns, contains the object removed from the , or null if key does not exist. + /// When this method returns, contains the object removed from the , or null if key does not exist. /// /// true if the object was removed successfully; otherwise, false. /// @@ -520,10 +509,9 @@ public bool Remove(TKey key, out TValue? value) ThrowArgumentNullExceptionForKey(); } - - if (_backup != null) + if (_dictionaryStorage != null) { - return _backup.Remove(key, out value); + return _dictionaryStorage.Remove(key, out value); } if (_count == 0) @@ -561,9 +549,9 @@ public bool TryAdd(TKey key, TValue value) ThrowArgumentNullExceptionForKey(); } - if (_backup != null) + if (_dictionaryStorage != null) { - return _backup.TryAdd(key, value); + return _dictionaryStorage.TryAdd(key, value); } if (ContainsKeyCore(key)) @@ -585,9 +573,9 @@ public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) ThrowArgumentNullExceptionForKey(); } - if (_backup != null) + if (_dictionaryStorage != null) { - return _backup.TryGetValue(key, out value); + return _dictionaryStorage.TryGetValue(key, out value); } return TryFindItem(key, out value); @@ -612,14 +600,14 @@ private void EnsureCapacitySlow(int capacity) { if (capacity > DefaultArrayThreshold) { - _backup = new Dictionary(capacity); + _dictionaryStorage = new Dictionary(capacity); foreach (var item in _arrayStorage) { - _backup[item.Key] = item.Value; + _dictionaryStorage[item.Key] = item.Value; } - // Don't use _count or _arrayStorage anymore - // TODO clear arrays here? + // Clear array storage. + _arrayStorage = Array.Empty>(); } else { @@ -640,6 +628,8 @@ private int FindIndex(TKey key) { // Generally the bounds checking here will be elided by the JIT because this will be called // on the same code path as EnsureCapacity. + Debug.Assert(_dictionaryStorage == null); + var array = _arrayStorage; var count = _count; @@ -657,6 +647,8 @@ private int FindIndex(TKey key) [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool TryFindItem(TKey key, out TValue? value) { + Debug.Assert(_dictionaryStorage == null); + var array = _arrayStorage; var count = _count; @@ -680,6 +672,8 @@ private bool TryFindItem(TKey key, out TValue? value) [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool ContainsKeyArray(TKey key) { + Debug.Assert(_dictionaryStorage == null); + var array = _arrayStorage; var count = _count; @@ -701,14 +695,14 @@ private bool ContainsKeyArray(TKey key) /// public struct Enumerator : IEnumerator> { - private readonly SmallCapacityDictionary _dictionary; + private readonly AdaptiveCapacityDictionary _dictionary; private int _index; /// /// Instantiates a new enumerator with the values provided in . /// - /// A . - public Enumerator(SmallCapacityDictionary dictionary) + /// A . + public Enumerator(AdaptiveCapacityDictionary dictionary) { if (dictionary == null) { diff --git a/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs b/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs similarity index 85% rename from src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs rename to src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs index a503fba0dad9..6cd31dd12716 100644 --- a/src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs +++ b/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs @@ -10,14 +10,14 @@ namespace Microsoft.AspNetCore.Routing.Tests { - public class SmallCapacityDictionaryTests + public class AdaptiveCapacityDictionaryTests { [Fact] public void DefaultCtor() { // Arrange // Act - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Assert Assert.Empty(dict); @@ -30,7 +30,7 @@ public void CreateFromNull() { // Arrange // Act - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Assert Assert.Empty(dict); @@ -68,7 +68,7 @@ public static KeyValuePair[] IEnumerableStringValuePairData public void CreateFromIEnumerableKeyValuePair_CopiesValues() { // Arrange & Act - var dict = SmallCapacityDictionary.FromArray(IEnumerableKeyValuePairData); + var dict = AdaptiveCapacityDictionary.FromArray(IEnumerableKeyValuePairData); // Assert Assert.IsType[]>(dict._arrayStorage); @@ -89,7 +89,7 @@ public void CreateFromIEnumerableKeyValuePair_CopiesValues() public void CreateFromIEnumerableStringValuePair_CopiesValues() { // Arrange & Act - var dict = new SmallCapacityDictionary(IEnumerableStringValuePairData, capacity: 3, StringComparer.OrdinalIgnoreCase); + var dict = new AdaptiveCapacityDictionary(IEnumerableStringValuePairData, capacity: 3, StringComparer.OrdinalIgnoreCase); // Assert Assert.IsType[]>(dict._arrayStorage); @@ -105,13 +105,13 @@ public void CreateFromIEnumerableKeyValuePair_ThrowsExceptionForDuplicateKey() { // Arrange, Act & Assert ExceptionAssert.ThrowsArgument( - () => new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) + () => new AdaptiveCapacityDictionary(StringComparer.OrdinalIgnoreCase) { { "name", "Billy" }, { "Name", "Joey" } }, "key", - $"An element with the key 'Name' already exists in the {nameof(SmallCapacityDictionary)}."); + $"An element with the key 'Name' already exists in the {nameof(AdaptiveCapacityDictionary)}."); } [Fact] @@ -126,9 +126,9 @@ public void CreateFromIEnumerableStringValuePair_ThrowsExceptionForDuplicateKey( // Act & Assert ExceptionAssert.ThrowsArgument( - () => new SmallCapacityDictionary(values, capacity: 3, StringComparer.OrdinalIgnoreCase), + () => new AdaptiveCapacityDictionary(values, capacity: 3, StringComparer.OrdinalIgnoreCase), "key", - $"An element with the key 'Name' already exists in the {nameof(SmallCapacityDictionary)}."); + $"An element with the key 'Name' already exists in the {nameof(AdaptiveCapacityDictionary)}."); } [Fact] @@ -136,7 +136,7 @@ public void Comparer_IsOrdinalIgnoreCase() { // Arrange // Act - var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase); + var dict = new AdaptiveCapacityDictionary(StringComparer.OrdinalIgnoreCase); // Assert Assert.Same(StringComparer.OrdinalIgnoreCase, dict.Comparer); @@ -147,7 +147,7 @@ public void Comparer_IsOrdinalIgnoreCase() public void IsReadOnly_False() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var result = ((ICollection>)dict).IsReadOnly; @@ -160,7 +160,7 @@ public void IsReadOnly_False() public void IndexGet_EmptyStringIsAllowed() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var value = dict[""]; @@ -173,7 +173,7 @@ public void IndexGet_EmptyStringIsAllowed() public void IndexGet_EmptyStorage_ReturnsNull() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var value = dict["key"]; @@ -186,7 +186,7 @@ public void IndexGet_EmptyStorage_ReturnsNull() public void IndexGet_ArrayStorage_NoMatch_ReturnsNull() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); dict.Add("age", 30); // Act @@ -201,7 +201,7 @@ public void IndexGet_ArrayStorage_NoMatch_ReturnsNull() public void IndexGet_ListStorage_Match_ReturnsValue() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -218,7 +218,7 @@ public void IndexGet_ListStorage_Match_ReturnsValue() public void IndexGet_ListStorage_MatchIgnoreCase_ReturnsValue() { // Arrange - var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) + var dict = new AdaptiveCapacityDictionary(StringComparer.OrdinalIgnoreCase) { { "key", "value" }, }; @@ -235,7 +235,7 @@ public void IndexGet_ListStorage_MatchIgnoreCase_ReturnsValue() public void IndexSet_EmptyStringIsAllowed() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act dict[""] = "foo"; @@ -248,7 +248,7 @@ public void IndexSet_EmptyStringIsAllowed() public void IndexSet_EmptyStorage_UpgradesToList() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act dict["key"] = "value"; @@ -262,7 +262,7 @@ public void IndexSet_EmptyStorage_UpgradesToList() public void IndexSet_backup_NoMatch_AddsValue() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); dict.Add("age", 30); // Act @@ -280,7 +280,7 @@ public void IndexSet_backup_NoMatch_AddsValue() public void IndexSet_ListStorage_NoMatch_AddsValue() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "age", 30 }, }; @@ -300,7 +300,7 @@ public void IndexSet_ListStorage_NoMatch_AddsValue() public void IndexSet_ListStorage_Match_SetsValue() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -317,7 +317,7 @@ public void IndexSet_ListStorage_Match_SetsValue() public void IndexSet_ListStorage_MatchIgnoreCase_SetsValue() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -334,7 +334,7 @@ public void IndexSet_ListStorage_MatchIgnoreCase_SetsValue() public void Count_EmptyStorage() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var count = dict.Count; @@ -347,7 +347,7 @@ public void Count_EmptyStorage() public void Count_ListStorage() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -364,7 +364,7 @@ public void Count_ListStorage() public void Keys_EmptyStorage() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var keys = dict.Keys; @@ -378,7 +378,7 @@ public void Keys_EmptyStorage() public void Keys_ListStorage() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -395,7 +395,7 @@ public void Keys_ListStorage() public void Values_EmptyStorage() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var values = dict.Values; @@ -409,7 +409,7 @@ public void Values_EmptyStorage() public void Values_ListStorage() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -426,7 +426,7 @@ public void Values_ListStorage() public void Add_EmptyStorage() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act dict.Add("key", "value"); @@ -440,7 +440,7 @@ public void Add_EmptyStorage() public void Add_EmptyStringIsAllowed() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act dict.Add("", "foo"); @@ -453,7 +453,7 @@ public void Add_EmptyStringIsAllowed() public void Add_ListStorage() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "age", 30 }, }; @@ -473,12 +473,12 @@ public void Add_ListStorage() public void Add_DuplicateKey() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; - var message = $"An element with the key 'key' already exists in the {nameof(SmallCapacityDictionary)}"; + var message = $"An element with the key 'key' already exists in the {nameof(AdaptiveCapacityDictionary)}"; // Act & Assert ExceptionAssert.ThrowsArgument(() => dict.Add("key", "value2"), "key", message); @@ -494,12 +494,12 @@ public void Add_DuplicateKey() public void Add_DuplicateKey_CaseInsensitive() { // Arrange - var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) + var dict = new AdaptiveCapacityDictionary(StringComparer.OrdinalIgnoreCase) { { "key", "value" }, }; - var message = $"An element with the key 'kEy' already exists in the {nameof(SmallCapacityDictionary)}"; + var message = $"An element with the key 'kEy' already exists in the {nameof(AdaptiveCapacityDictionary)}"; // Act & Assert ExceptionAssert.ThrowsArgument(() => dict.Add("kEy", "value2"), "key", message); @@ -515,7 +515,7 @@ public void Add_DuplicateKey_CaseInsensitive() public void Add_KeyValuePair() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "age", 30 }, }; @@ -535,7 +535,7 @@ public void Add_KeyValuePair() public void Clear_EmptyStorage() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act dict.Clear(); @@ -548,7 +548,7 @@ public void Clear_EmptyStorage() public void Clear_ListStorage() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -566,7 +566,7 @@ public void Clear_ListStorage() public void Contains_ListStorage_KeyValuePair_True() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -585,7 +585,7 @@ public void Contains_ListStorage_KeyValuePair_True() public void Contains_ListStory_KeyValuePair_True_CaseInsensitive() { // Arrange - var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) + var dict = new AdaptiveCapacityDictionary(StringComparer.OrdinalIgnoreCase) { { "key", "value" }, }; @@ -604,7 +604,7 @@ public void Contains_ListStory_KeyValuePair_True_CaseInsensitive() public void Contains_ListStorage_KeyValuePair_False() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -624,7 +624,7 @@ public void Contains_ListStorage_KeyValuePair_False() public void Contains_ListStorage_KeyValuePair_False_ValueComparisonIsDefault() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -643,7 +643,7 @@ public void Contains_ListStorage_KeyValuePair_False_ValueComparisonIsDefault() public void ContainsKey_EmptyStorage() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var result = dict.ContainsKey("key"); @@ -656,7 +656,7 @@ public void ContainsKey_EmptyStorage() public void ContainsKey_EmptyStringIsAllowed() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var result = dict.ContainsKey(""); @@ -669,7 +669,7 @@ public void ContainsKey_EmptyStringIsAllowed() public void ContainsKey_ListStorage_False() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -686,7 +686,7 @@ public void ContainsKey_ListStorage_False() public void ContainsKey_ListStorage_True() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -703,7 +703,7 @@ public void ContainsKey_ListStorage_True() public void ContainsKey_ListStorage_True_CaseInsensitive() { // Arrange - var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) + var dict = new AdaptiveCapacityDictionary(StringComparer.OrdinalIgnoreCase) { { "key", "value" }, }; @@ -720,7 +720,7 @@ public void ContainsKey_ListStorage_True_CaseInsensitive() public void CopyTo() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -745,7 +745,7 @@ public void CopyTo() public void Remove_KeyValuePair_True() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -765,7 +765,7 @@ public void Remove_KeyValuePair_True() public void Remove_KeyValuePair_True_CaseInsensitive() { // Arrange - var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) + var dict = new AdaptiveCapacityDictionary(StringComparer.OrdinalIgnoreCase) { { "key", "value" }, }; @@ -785,7 +785,7 @@ public void Remove_KeyValuePair_True_CaseInsensitive() public void Remove_KeyValuePair_False() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -806,7 +806,7 @@ public void Remove_KeyValuePair_False() public void Remove_KeyValuePair_False_ValueComparisonIsDefault() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -826,7 +826,7 @@ public void Remove_KeyValuePair_False_ValueComparisonIsDefault() public void Remove_EmptyStorage() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var result = dict.Remove("key"); @@ -839,7 +839,7 @@ public void Remove_EmptyStorage() public void Remove_EmptyStringIsAllowed() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var result = dict.Remove(""); @@ -852,7 +852,7 @@ public void Remove_EmptyStringIsAllowed() public void Remove_ListStorage_False() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -870,7 +870,7 @@ public void Remove_ListStorage_False() public void Remove_ListStorage_True() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -888,7 +888,7 @@ public void Remove_ListStorage_True() public void Remove_ListStorage_True_CaseInsensitive() { // Arrange - var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) + var dict = new AdaptiveCapacityDictionary(StringComparer.OrdinalIgnoreCase) { { "key", "value" }, }; @@ -907,7 +907,7 @@ public void Remove_ListStorage_True_CaseInsensitive() public void Remove_KeyAndOutValue_EmptyStorage() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var result = dict.Remove("key", out var removedValue); @@ -921,7 +921,7 @@ public void Remove_KeyAndOutValue_EmptyStorage() public void Remove_KeyAndOutValue_EmptyStringIsAllowed() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var result = dict.Remove("", out var removedValue); @@ -935,7 +935,7 @@ public void Remove_KeyAndOutValue_EmptyStringIsAllowed() public void Remove_KeyAndOutValue_ListStorage_False() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -955,7 +955,7 @@ public void Remove_KeyAndOutValue_ListStorage_True() { // Arrange object value = "value"; - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", value } }; @@ -975,7 +975,7 @@ public void Remove_KeyAndOutValue_ListStorage_True_CaseInsensitive() { // Arrange object value = "value"; - var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) + var dict = new AdaptiveCapacityDictionary(StringComparer.OrdinalIgnoreCase) { { "key", value } }; @@ -995,7 +995,7 @@ public void Remove_KeyAndOutValue_ListStorage_KeyExists_First() { // Arrange object value = "value"; - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", value }, { "other", 5 }, @@ -1020,7 +1020,7 @@ public void Remove_KeyAndOutValue_ListStorage_KeyExists_Middle() { // Arrange object value = "value"; - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "other", 5 }, { "key", value }, @@ -1045,7 +1045,7 @@ public void Remove_KeyAndOutValue_ListStorage_KeyExists_Last() { // Arrange object value = "value"; - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "other", 5 }, { "dotnet", "rocks" }, @@ -1069,7 +1069,7 @@ public void Remove_KeyAndOutValue_ListStorage_KeyExists_Last() public void TryAdd_EmptyStringIsAllowed() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var result = dict.TryAdd("", "foo"); @@ -1082,7 +1082,7 @@ public void TryAdd_EmptyStringIsAllowed() public void TryAdd_EmptyStorage_CanAdd() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var result = dict.TryAdd("key", "value"); @@ -1101,7 +1101,7 @@ public void TryAdd_EmptyStorage_CanAdd() public void TryAdd_ArrayStorage_CanAdd() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key0", "value0" }, }; @@ -1123,7 +1123,7 @@ public void TryAdd_ArrayStorage_CanAdd() public void TryAdd_ArrayStorage_DoesNotAddWhenKeyIsPresent() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key0", "value0" }, }; @@ -1145,7 +1145,7 @@ public void TryAdd_ArrayStorage_DoesNotAddWhenKeyIsPresent() public void TryGetValue_EmptyStorage() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var result = dict.TryGetValue("key", out var value); @@ -1159,7 +1159,7 @@ public void TryGetValue_EmptyStorage() public void TryGetValue_EmptyStringIsAllowed() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act var result = dict.TryGetValue("", out var value); @@ -1173,7 +1173,7 @@ public void TryGetValue_EmptyStringIsAllowed() public void TryGetValue_ListStorage_False() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -1191,7 +1191,7 @@ public void TryGetValue_ListStorage_False() public void TryGetValue_ListStorage_True() { // Arrange - var dict = new SmallCapacityDictionary() + var dict = new AdaptiveCapacityDictionary() { { "key", "value" }, }; @@ -1209,7 +1209,7 @@ public void TryGetValue_ListStorage_True() public void TryGetValue_ListStorage_True_CaseInsensitive() { // Arrange - var dict = new SmallCapacityDictionary(StringComparer.OrdinalIgnoreCase) + var dict = new AdaptiveCapacityDictionary(StringComparer.OrdinalIgnoreCase) { { "key", "value" }, }; @@ -1227,7 +1227,7 @@ public void TryGetValue_ListStorage_True_CaseInsensitive() public void ListStorage_SwitchesToDictionaryAfter4() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); // Act 1 dict.Add("key", "value"); @@ -1253,7 +1253,7 @@ public void ListStorage_SwitchesToDictionaryAfter4() public void ListStorage_RemoveAt_RearrangesInnerArray() { // Arrange - var dict = new SmallCapacityDictionary(); + var dict = new AdaptiveCapacityDictionary(); dict.Add("key", "value"); dict.Add("key2", "value2"); dict.Add("key3", "value3"); @@ -1285,7 +1285,7 @@ public void FromArray_TakesOwnershipOfArray() new KeyValuePair("c", 2), }; - var dictionary = SmallCapacityDictionary.FromArray(array); + var dictionary = AdaptiveCapacityDictionary.FromArray(array); // Act - modifying the array should modify the dictionary array[0] = new KeyValuePair("aa", 10); @@ -1302,13 +1302,13 @@ public void FromArray_EmptyArray() var array = Array.Empty>(); // Act - var dictionary = SmallCapacityDictionary.FromArray(array); + var dictionary = AdaptiveCapacityDictionary.FromArray(array); // Assert Assert.Empty(dictionary); } - private void AssertEmptyArrayStorage(SmallCapacityDictionary value) + private void AssertEmptyArrayStorage(AdaptiveCapacityDictionary value) { Assert.Same(Array.Empty>(), value._arrayStorage); } From 6cfeb8c9e0f0d9d18d47a7dd4a3eb565e51d32ac Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 31 Mar 2021 14:45:30 -0700 Subject: [PATCH 17/28] fixing test --- .../AdaptiveCapacityDictionaryTests.cs | 28 ++++--------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs b/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs index 6cd31dd12716..cc4c1f28811a 100644 --- a/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs +++ b/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs @@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Testing; using Xunit; -namespace Microsoft.AspNetCore.Routing.Tests +namespace Microsoft.AspNetCore.Internal.Dictionary.Tests { public class AdaptiveCapacityDictionaryTests { @@ -22,7 +22,7 @@ public void DefaultCtor() // Assert Assert.Empty(dict); Assert.Empty(dict._arrayStorage); - Assert.Null(dict._backup); + Assert.Null(dict._dictionaryStorage); } [Fact] @@ -35,7 +35,7 @@ public void CreateFromNull() // Assert Assert.Empty(dict); Assert.Empty(dict._arrayStorage); - Assert.Null(dict._backup); + Assert.Null(dict._dictionaryStorage); } public static KeyValuePair[] IEnumerableKeyValuePairData @@ -258,24 +258,6 @@ public void IndexSet_EmptyStorage_UpgradesToList() Assert.IsType[]>(dict._arrayStorage); } - [Fact] - public void IndexSet_backup_NoMatch_AddsValue() - { - // Arrange - var dict = new AdaptiveCapacityDictionary(); - dict.Add("age", 30); - - // Act - dict["key"] = "value"; - - // Assert - Assert.Collection( - dict.OrderBy(kvp => kvp.Key), - kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); }, - kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - [Fact] public void IndexSet_ListStorage_NoMatch_AddsValue() { @@ -559,7 +541,7 @@ public void Clear_ListStorage() // Assert Assert.Empty(dict); Assert.IsType[]>(dict._arrayStorage); - Assert.Null(dict._backup); + Assert.Null(dict._dictionaryStorage); } [Fact] @@ -1244,7 +1226,7 @@ public void ListStorage_SwitchesToDictionaryAfter4() // Assert 2 storage = Assert.IsType[]>(dict._arrayStorage); - Assert.Equal(4, storage.Length); + Assert.Empty(storage); Assert.Equal(5, dict.Count); } From 11f8c4380624903c93af2b42182b3a1d31092558 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 31 Mar 2021 14:47:10 -0700 Subject: [PATCH 18/28] remove comment --- src/Shared/Dictionary/AdaptiveCapacityDictionary.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs index 010bdc45dd18..22fbd0b8d483 100644 --- a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs +++ b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs @@ -652,7 +652,6 @@ private bool TryFindItem(TKey key, out TValue? value) var array = _arrayStorage; var count = _count; - // Elide bounds check for indexing. if ((uint)count <= (uint)array.Length) { for (var i = 0; i < count; i++) @@ -677,7 +676,6 @@ private bool ContainsKeyArray(TKey key) var array = _arrayStorage; var count = _count; - // Elide bounds check for indexing. if ((uint)count <= (uint)array.Length) { for (var i = 0; i < count; i++) From 3da0a96b5bde79bbda2160278ed5a97f503df0d4 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 1 Apr 2021 09:46:14 -0700 Subject: [PATCH 19/28] Feedback --- .../SmallCapacityDictionaryBenchmark.cs | 2 +- .../src/Internal/RequestCookieCollection.cs | 2 +- .../Http/src/Microsoft.AspNetCore.Http.csproj | 2 +- .../Cookies/samples/CookieSample/Startup.cs | 35 +- .../Dictionary/AdaptiveCapacityDictionary.cs | 377 ++++++++---------- .../AdaptiveCapacityDictionaryTests.cs | 84 ++-- 6 files changed, 235 insertions(+), 267 deletions(-) diff --git a/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs b/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs index 73e0fcf4ae8d..ff463e42e914 100644 --- a/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs +++ b/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using BenchmarkDotNet.Attributes; -using Microsoft.AspNetCore.Internal.Dictionary; +using Microsoft.AspNetCore.Internal; namespace Microsoft.AspNetCore.Http { diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs index faa310b5296b..c4aee0c48797 100644 --- a/src/Http/Http/src/Internal/RequestCookieCollection.cs +++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Primitives; -using Microsoft.AspNetCore.Internal.Dictionary; +using Microsoft.AspNetCore.Internal; using Microsoft.Net.Http.Headers; using System.Linq; diff --git a/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj index 8c40428a47dd..669a90b01f12 100644 --- a/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj +++ b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core default HTTP feature implementations. diff --git a/src/Security/Authentication/Cookies/samples/CookieSample/Startup.cs b/src/Security/Authentication/Cookies/samples/CookieSample/Startup.cs index 1d1f1fc73427..a91791070a06 100644 --- a/src/Security/Authentication/Cookies/samples/CookieSample/Startup.cs +++ b/src/Security/Authentication/Cookies/samples/CookieSample/Startup.cs @@ -14,34 +14,31 @@ public class Startup public void ConfigureServices(IServiceCollection services) { // This can be removed after https://github.com/aspnet/IISIntegration/issues/371 - //services.AddAuthentication(options => - //{ - // options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; - // options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; - //}).AddCookie(); + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; + }).AddCookie(); } public void Configure(IApplicationBuilder app) { - //app.UseAuthentication(); + app.UseAuthentication(); app.Run(async context => { - var cookie = context.Request.Cookies[".AspNetCore.Cookies"]; - - await context.Response.WriteAsync($"Hello World {cookie}"); - //if (!context.User.Identities.Any(identity => identity.IsAuthenticated)) - //{ - // var user = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "bob") }, CookieAuthenticationDefaults.AuthenticationScheme)); - // await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, user); + if (!context.User.Identities.Any(identity => identity.IsAuthenticated)) + { + var user = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "bob") }, CookieAuthenticationDefaults.AuthenticationScheme)); + await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, user); - // context.Response.ContentType = "text/plain"; - // await context.Response.WriteAsync("Hello First timer"); - // return; - //} + context.Response.ContentType = "text/plain"; + await context.Response.WriteAsync("Hello First timer"); + return; + } - //context.Response.ContentType = "text/plain"; - //await context.Response.WriteAsync("Hello old timer"); + context.Response.ContentType = "text/plain"; + await context.Response.WriteAsync("Hello old timer"); }); } } diff --git a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs index 22fbd0b8d483..a199b8dd0c8a 100644 --- a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs +++ b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs @@ -8,7 +8,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -namespace Microsoft.AspNetCore.Internal.Dictionary +namespace Microsoft.AspNetCore.Internal { /// /// An type to hold a small amount of items (4 or less in the common case). @@ -18,71 +18,11 @@ internal class AdaptiveCapacityDictionary : IDictionary[] _arrayStorage; + internal KeyValuePair[]? _arrayStorage; private int _count; internal Dictionary? _dictionaryStorage; private IEqualityComparer _comparer; - /// - /// Creates a new from the provided array. - /// The new instance will take ownership of the array, and may mutate it. - /// - /// The items array. - /// Equality comparison. - /// A new . - public static AdaptiveCapacityDictionary FromArray(KeyValuePair[] items, IEqualityComparer? comparer = null) - { - if (items == null) - { - throw new ArgumentNullException(nameof(items)); - } - - comparer = comparer ?? EqualityComparer.Default; - - if (items.Length > DefaultArrayThreshold) - { - // Use dictionary for large arrays. - return new AdaptiveCapacityDictionary(items, capacity: items.Length,comparer); - } - - // We need to compress the array by removing non-contiguous items. We - // typically have a very small number of items to process. We don't need - // to preserve order. - var start = 0; - var end = items.Length - 1; - - // We walk forwards from the beginning of the array and fill in 'null' slots. - // We walk backwards from the end of the array end move items in non-null' slots - // into whatever start is pointing to. O(n) - while (start <= end) - { - if (items[start].Key != null) - { - start++; - } - else if (items[end].Key != null) - { - // Swap this item into start and advance - items[start] = items[end]; - items[end] = default; - start++; - end--; - } - else - { - // Both null, we need to hold on 'start' since we - // still need to fill it with something. - end--; - } - } - - return new AdaptiveCapacityDictionary() - { - _arrayStorage = items!, - _count = start, - }; - } - /// /// Creates an empty . /// @@ -147,9 +87,10 @@ public AdaptiveCapacityDictionary(int capacity, IEqualityComparer comparer /// or /// or an object with public properties as key-value pairs. /// + /// This constructor is unoptimized and primarily used for tests. /// Equality comparison. /// Initial capacity. - public AdaptiveCapacityDictionary(IEnumerable> values, int capacity, IEqualityComparer comparer) + internal AdaptiveCapacityDictionary(IEnumerable> values, int capacity, IEqualityComparer comparer) { _comparer = comparer ?? EqualityComparer.Default; @@ -182,28 +123,27 @@ public TValue this[TKey key] { ThrowArgumentNullExceptionForKey(); } - - if (_dictionaryStorage != null) + if (_arrayStorage != null) { - _dictionaryStorage[key] = value; - return; - } - - var index = FindIndex(key); - if (index < 0) - { - EnsureCapacity(_count + 1); - if (_dictionaryStorage != null) + var index = FindIndex(key); + if (index < 0) { - _dictionaryStorage[key] = value; - return; + EnsureCapacity(_count + 1); + if (_dictionaryStorage != null) + { + _dictionaryStorage[key] = value; + return; + } + _arrayStorage[_count++] = new KeyValuePair(key, value); + } + else + { + _arrayStorage[index] = new KeyValuePair(key, value); } - _arrayStorage[_count++] = new KeyValuePair(key, value); - } - else - { - _arrayStorage[index] = new KeyValuePair(key, value); } + + _dictionaryStorage![key] = value; + return; } } @@ -237,19 +177,21 @@ public ICollection Keys { get { - if (_dictionaryStorage != null) + if (_arrayStorage != null) { - return _dictionaryStorage.Keys; - } + // TODO if common operation, make keys and values + // in separate arrays to avoid copying. + var array = _arrayStorage; + var keys = new TKey[_count]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = array[i].Key; + } - var array = _arrayStorage; - var keys = new TKey[_count]; - for (var i = 0; i < keys.Length; i++) - { - keys[i] = array[i].Key; + return keys; } - return keys; + return _dictionaryStorage!.Keys; } } @@ -260,19 +202,21 @@ public ICollection Values { get { - if (_dictionaryStorage != null) + if (_arrayStorage != null) { - return _dictionaryStorage.Values; - } + // TODO if common operation, make keys and values + // in separate arrays to avoid copying. + var array = _arrayStorage; + var values = new TValue[_count]; + for (var i = 0; i < values.Length; i++) + { + values[i] = array[i].Value; + } - var array = _arrayStorage; - var values = new TValue[_count]; - for (var i = 0; i < values.Length; i++) - { - values[i] = array[i].Value; + return values; } - return values; + return _dictionaryStorage!.Values; } } @@ -281,13 +225,13 @@ public ICollection Values /// void ICollection>.Add(KeyValuePair item) { - if (_dictionaryStorage != null) + if (_arrayStorage != null) { - ((ICollection>)_dictionaryStorage).Add(item); - return; + Add(item.Key, item.Value); } - Add(item.Key, item.Value); + ((ICollection>)_dictionaryStorage!).Add(item); + return; } /// @@ -298,13 +242,25 @@ public void Add(TKey key, TValue value) ThrowArgumentNullExceptionForKey(); } - if (_dictionaryStorage != null) + if (_arrayStorage != null) { - _dictionaryStorage.Add(key, value); - return; - } + EnsureCapacity(_count + 1); - EnsureCapacity(_count + 1); + if (_dictionaryStorage != null) + { + Debug.Assert(_arrayStorage == null); + _dictionaryStorage.Add(key, value); + return; + } + + if (ContainsKeyArray(key)) + { + throw new ArgumentException($"An element with the key '{key}' already exists in the {nameof(AdaptiveCapacityDictionary)}.", nameof(key)); + } + + _arrayStorage[_count] = new KeyValuePair(key, value); + _count++; + } if (_dictionaryStorage != null) { @@ -312,13 +268,7 @@ public void Add(TKey key, TValue value) return; } - if (ContainsKeyArray(key)) - { - throw new ArgumentException($"An element with the key '{key}' already exists in the {nameof(AdaptiveCapacityDictionary)}.", nameof(key)); - } - - _arrayStorage[_count] = new KeyValuePair(key, value); - _count++; + } /// @@ -333,10 +283,11 @@ public void Clear() { return; } - - Array.Clear(_arrayStorage, 0, _count); - _count = 0; - _arrayStorage = Array.Empty>(); + if (_arrayStorage != null) + { + Array.Clear(_arrayStorage, 0, _count); + _count = 0; + } } /// @@ -387,25 +338,19 @@ void ICollection>.CopyTo( throw new ArgumentOutOfRangeException(nameof(arrayIndex)); } - if (_dictionaryStorage != null) + if (_arrayStorage != null) { - var i = 0; - foreach (var kvp in _dictionaryStorage) + if (Count == 0) { - array[i] = kvp; - i++; + return; } + var storage = _arrayStorage; + Array.Copy(storage, 0, array, arrayIndex, _count); return; } - if (Count == 0) - { - return; - } - - var storage = _arrayStorage; - Array.Copy(storage, 0, array, arrayIndex, _count); + ((ICollection>)_dictionaryStorage!).CopyTo(array, arrayIndex); } /// @@ -439,27 +384,27 @@ IEnumerator IEnumerable.GetEnumerator() /// bool ICollection>.Remove(KeyValuePair item) { - if (_dictionaryStorage != null) + if (_arrayStorage != null) { - return ((ICollection>)_dictionaryStorage).Remove(item); - } + if (Count == 0) + { + return false; + } - if (Count == 0) - { - return false; - } + var index = FindIndex(item.Key); + var array = _arrayStorage; + if (index >= 0 && EqualityComparer.Default.Equals(array[index].Value, item.Value)) + { + Array.Copy(array, index + 1, array, index, _count - index); + _count--; + array[_count] = default; + return true; + } - var index = FindIndex(item.Key); - var array = _arrayStorage; - if (index >= 0 && EqualityComparer.Default.Equals(array[index].Value, item.Value)) - { - Array.Copy(array, index + 1, array, index, _count - index); - _count--; - array[_count] = default; - return true; + return false; } - return false; + return ((ICollection>)_dictionaryStorage!).Remove(item); } /// @@ -470,28 +415,28 @@ public bool Remove(TKey key) ThrowArgumentNullExceptionForKey(); } - if (_dictionaryStorage != null) + if (_arrayStorage != null) { - return _dictionaryStorage.Remove(key); - } + if (Count == 0) + { + return false; + } - if (Count == 0) - { - return false; - } + var index = FindIndex(key); + if (index >= 0) + { + _count--; + var array = _arrayStorage; + Array.Copy(array, index + 1, array, index, _count - index); + array[_count] = default; - var index = FindIndex(key); - if (index >= 0) - { - _count--; - var array = _arrayStorage; - Array.Copy(array, index + 1, array, index, _count - index); - array[_count] = default; + return true; + } - return true; + return false; } - return false; + return _dictionaryStorage!.Remove(key); } /// @@ -509,31 +454,31 @@ public bool Remove(TKey key, out TValue? value) ThrowArgumentNullExceptionForKey(); } - if (_dictionaryStorage != null) + if (_arrayStorage != null) { - return _dictionaryStorage.Remove(key, out value); - } + if (_count == 0) + { + value = default; + return false; + } - if (_count == 0) - { - value = default; - return false; - } + var index = FindIndex(key); + if (index >= 0) + { + _count--; + var array = _arrayStorage; + value = array[index].Value; + Array.Copy(array, index + 1, array, index, _count - index); + array[_count] = default; - var index = FindIndex(key); - if (index >= 0) - { - _count--; - var array = _arrayStorage; - value = array[index].Value; - Array.Copy(array, index + 1, array, index, _count - index); - array[_count] = default; + return true; + } - return true; + value = default; + return false; } - value = default; - return false; + return _dictionaryStorage!.Remove(key, out value); } /// @@ -549,20 +494,26 @@ public bool TryAdd(TKey key, TValue value) ThrowArgumentNullExceptionForKey(); } - if (_dictionaryStorage != null) + if (_arrayStorage != null) { - return _dictionaryStorage.TryAdd(key, value); - } + if (ContainsKeyCore(key)) + { + return false; + } - if (ContainsKeyCore(key)) - { - return false; + EnsureCapacity(Count + 1); + + if (_dictionaryStorage != null) + { + return _dictionaryStorage.TryAdd(key, value); + } + + _arrayStorage[Count] = new KeyValuePair(key, value); + _count++; + return true; } - EnsureCapacity(Count + 1); - _arrayStorage[Count] = new KeyValuePair(key, value); - _count++; - return true; + return _dictionaryStorage!.TryAdd(key, value); } /// @@ -596,6 +547,8 @@ private void EnsureCapacity(int capacity) private void EnsureCapacitySlow(int capacity) { + Debug.Assert(_arrayStorage != null); + if (_arrayStorage.Length < capacity) { if (capacity > DefaultArrayThreshold) @@ -607,7 +560,7 @@ private void EnsureCapacitySlow(int capacity) } // Clear array storage. - _arrayStorage = Array.Empty>(); + _arrayStorage = null; } else { @@ -629,13 +582,14 @@ private int FindIndex(TKey key) // Generally the bounds checking here will be elided by the JIT because this will be called // on the same code path as EnsureCapacity. Debug.Assert(_dictionaryStorage == null); + Debug.Assert(_arrayStorage != null); var array = _arrayStorage; var count = _count; for (var i = 0; i < count; i++) { - if (_comparer.Equals(array[i].Key, key)) + if (_comparer.Equals(array![i].Key, key)) { return i; } @@ -648,19 +602,16 @@ private int FindIndex(TKey key) private bool TryFindItem(TKey key, out TValue? value) { Debug.Assert(_dictionaryStorage == null); + Debug.Assert(_arrayStorage != null); - var array = _arrayStorage; - var count = _count; + var storage = _arrayStorage.AsSpan(0, _count); - if ((uint)count <= (uint)array.Length) + foreach (ref var item in storage) { - for (var i = 0; i < count; i++) + if (_comparer.Equals(item.Key, key)) { - if (_comparer.Equals(array[i].Key, key)) - { - value = array[i].Value; - return true; - } + value = item.Value; + return true; } } @@ -672,18 +623,15 @@ private bool TryFindItem(TKey key, out TValue? value) private bool ContainsKeyArray(TKey key) { Debug.Assert(_dictionaryStorage == null); + Debug.Assert(_arrayStorage != null); - var array = _arrayStorage; - var count = _count; + var storage = _arrayStorage.AsSpan(0, _count); - if ((uint)count <= (uint)array.Length) + foreach (ref var item in storage) { - for (var i = 0; i < count; i++) + if (_comparer.Equals(item.Key, key)) { - if (_comparer.Equals(array[i].Key, key)) - { - return true; - } + return true; } } @@ -731,12 +679,17 @@ public void Dispose() public bool MoveNext() { var dictionary = _dictionary; + if (_dictionary._arrayStorage == null) + { + return false; + } + if (dictionary._count <= _index) { return false; } - Current = dictionary._arrayStorage[_index]; + Current = dictionary._arrayStorage![_index]; _index++; return true; } diff --git a/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs b/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs index cc4c1f28811a..3a263168690a 100644 --- a/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs +++ b/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs @@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Testing; using Xunit; -namespace Microsoft.AspNetCore.Internal.Dictionary.Tests +namespace Microsoft.AspNetCore.Internal.Tests { public class AdaptiveCapacityDictionaryTests { @@ -68,7 +68,7 @@ public static KeyValuePair[] IEnumerableStringValuePairData public void CreateFromIEnumerableKeyValuePair_CopiesValues() { // Arrange & Act - var dict = AdaptiveCapacityDictionary.FromArray(IEnumerableKeyValuePairData); + var dict = new AdaptiveCapacityDictionary(IEnumerableKeyValuePairData, capacity: IEnumerableKeyValuePairData.Length, EqualityComparer.Default); // Assert Assert.IsType[]>(dict._arrayStorage); @@ -1206,7 +1206,7 @@ public void TryGetValue_ListStorage_True_CaseInsensitive() } [Fact] - public void ListStorage_SwitchesToDictionaryAfter4() + public void ListStorage_SwitchesToDictionaryAfter4_Add() { // Arrange var dict = new AdaptiveCapacityDictionary(); @@ -1232,62 +1232,80 @@ public void ListStorage_SwitchesToDictionaryAfter4() } [Fact] - public void ListStorage_RemoveAt_RearrangesInnerArray() + public void ListStorage_SwitchesToDictionaryAfter4_TryAdd() { // Arrange var dict = new AdaptiveCapacityDictionary(); - dict.Add("key", "value"); - dict.Add("key2", "value2"); - dict.Add("key3", "value3"); + + // Act 1 + dict.TryAdd("key", "value"); // Assert 1 var storage = Assert.IsType[]>(dict._arrayStorage); - Assert.Equal(3, dict.Count); + Assert.Equal(4, storage.Length); - // Act - dict.Remove("key2"); + // Act 2 + dict.TryAdd("key2", "value2"); + dict.TryAdd("key3", "value3"); + dict.TryAdd("key4", "value4"); + dict.TryAdd("key5", "value5"); // Assert 2 storage = Assert.IsType[]>(dict._arrayStorage); - Assert.Equal(2, dict.Count); - Assert.Equal("key", storage[0].Key); - Assert.Equal("value", storage[0].Value); - Assert.Equal("key3", storage[1].Key); - Assert.Equal("value3", storage[1].Value); + Assert.Empty(storage); + + Assert.Equal(5, dict.Count); } [Fact] - public void FromArray_TakesOwnershipOfArray() + public void ListStorage_SwitchesToDictionaryAfter4_Index() { // Arrange - var array = new KeyValuePair[] - { - new KeyValuePair("a", 0), - new KeyValuePair("b", 1), - new KeyValuePair("c", 2), - }; + var dict = new AdaptiveCapacityDictionary(); - var dictionary = AdaptiveCapacityDictionary.FromArray(array); + // Act 1 + dict["key"] = "value"; - // Act - modifying the array should modify the dictionary - array[0] = new KeyValuePair("aa", 10); + // Assert 1 + var storage = Assert.IsType[]>(dict._arrayStorage); + Assert.Equal(4, storage.Length); - // Assert - Assert.Equal(3, dictionary.Count); - Assert.Equal(10, dictionary["aa"]); + // Act 2 + dict["key1"] = "value"; + dict["key2"] = "value"; + dict["key3"] = "value"; + dict["key4"] = "value"; + + // Assert 2 + storage = Assert.IsType[]>(dict._arrayStorage); + Assert.Empty(storage); + + Assert.Equal(5, dict.Count); } [Fact] - public void FromArray_EmptyArray() + public void ListStorage_RemoveAt_RearrangesInnerArray() { // Arrange - var array = Array.Empty>(); + var dict = new AdaptiveCapacityDictionary(); + dict.Add("key", "value"); + dict.Add("key2", "value2"); + dict.Add("key3", "value3"); + + // Assert 1 + var storage = Assert.IsType[]>(dict._arrayStorage); + Assert.Equal(3, dict.Count); // Act - var dictionary = AdaptiveCapacityDictionary.FromArray(array); + dict.Remove("key2"); - // Assert - Assert.Empty(dictionary); + // Assert 2 + storage = Assert.IsType[]>(dict._arrayStorage); + Assert.Equal(2, dict.Count); + Assert.Equal("key", storage[0].Key); + Assert.Equal("value", storage[0].Value); + Assert.Equal("key3", storage[1].Key); + Assert.Equal("value3", storage[1].Value); } private void AssertEmptyArrayStorage(AdaptiveCapacityDictionary value) From 1c7c005d7f4db436110980ef91795e5bc0fd3585 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 1 Apr 2021 12:11:04 -0700 Subject: [PATCH 20/28] Perf tests and new limit --- .../AdaptiveCapacityDictionaryBenchmark.cs | 331 ++++++++++++++++++ .../SmallCapacityDictionaryBenchmark.cs | 150 -------- .../Dictionary/AdaptiveCapacityDictionary.cs | 49 +-- .../AdaptiveCapacityDictionaryTests.cs | 45 ++- 4 files changed, 388 insertions(+), 187 deletions(-) create mode 100644 src/Http/Http/perf/Microbenchmarks/AdaptiveCapacityDictionaryBenchmark.cs delete mode 100644 src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs diff --git a/src/Http/Http/perf/Microbenchmarks/AdaptiveCapacityDictionaryBenchmark.cs b/src/Http/Http/perf/Microbenchmarks/AdaptiveCapacityDictionaryBenchmark.cs new file mode 100644 index 000000000000..15368c45218b --- /dev/null +++ b/src/Http/Http/perf/Microbenchmarks/AdaptiveCapacityDictionaryBenchmark.cs @@ -0,0 +1,331 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Internal; + +namespace Microsoft.AspNetCore.Http +{ + public class AdaptiveCapacityDictionaryBenchmark + { + private AdaptiveCapacityDictionary _smallCapDict; + private AdaptiveCapacityDictionary _smallCapDictTen; + private AdaptiveCapacityDictionary _filledSmallDictionary; + private Dictionary _dict; + private Dictionary _dictTen; + private Dictionary _filledDictTen; + private KeyValuePair _oneValue; + private List> _tenValues; + + [IterationSetup] + public void Setup() + { + _oneValue = new KeyValuePair("a", "b"); + + _tenValues = new List>() + { + new KeyValuePair("a", "b"), + new KeyValuePair("c", "d"), + new KeyValuePair("e", "f"), + new KeyValuePair("g", "h"), + new KeyValuePair("i", "j"), + new KeyValuePair("k", "l"), + new KeyValuePair("m", "n"), + new KeyValuePair("o", "p"), + new KeyValuePair("q", "r"), + new KeyValuePair("s", "t"), + }; + + _smallCapDict = new AdaptiveCapacityDictionary(capacity: 1, StringComparer.OrdinalIgnoreCase); + _smallCapDictTen = new AdaptiveCapacityDictionary(capacity: 10, StringComparer.OrdinalIgnoreCase); + _filledSmallDictionary = new AdaptiveCapacityDictionary(_tenValues, capacity: 10, StringComparer.OrdinalIgnoreCase); + + _dict = new Dictionary(1, StringComparer.OrdinalIgnoreCase); + _dictTen = new Dictionary(10, StringComparer.OrdinalIgnoreCase); + _filledDictTen = new Dictionary(10, StringComparer.OrdinalIgnoreCase); + + foreach (var a in _tenValues) + { + _filledDictTen[a.Key] = a.Value; + } + } + + [Benchmark] + public void OneValue_SmallDict() + { + _smallCapDict[_oneValue.Key] = _oneValue.Value; + _ = _smallCapDict[_oneValue.Key]; + } + + [Benchmark] + public void OneValue_Dict() + { + _dict[_oneValue.Key] = _oneValue.Value; + _ = _dict[_oneValue.Key]; + } + + [Benchmark] + public void OneValue_SmallDict_Set() + { + _smallCapDict[_oneValue.Key] = _oneValue.Value; + } + + [Benchmark] + public void OneValue_Dict_Set() + { + _dict[_oneValue.Key] = _oneValue.Value; + } + + + [Benchmark] + public void OneValue_SmallDict_Get() + { + _smallCapDict.TryGetValue("test", out _); + } + + [Benchmark] + public void OneValue_Dict_Get() + { + _dict.TryGetValue("test", out _); + } + + [Benchmark] + public void FourValues_SmallDict() + { + for (var i = 0; i < 4; i++) + { + var val = _tenValues[i]; + _smallCapDictTen[val.Key] = val.Value; + _ = _smallCapDictTen[val.Key]; + } + } + + [Benchmark] + public void FiveValues_SmallDict() + { + for (var i = 0; i < 5; i++) + { + var val = _tenValues[i]; + _smallCapDictTen[val.Key] = val.Value; + _ = _smallCapDictTen[val.Key]; + } + } + + [Benchmark] + public void SixValues_SmallDict() + { + for (var i = 0; i < 6; i++) + { + var val = _tenValues[i]; + _smallCapDictTen[val.Key] = val.Value; + _ = _smallCapDictTen[val.Key]; + } + } + + [Benchmark] + public void SevenValues_SmallDict() + { + for (var i = 0; i < 7; i++) + { + var val = _tenValues[i]; + _smallCapDictTen[val.Key] = val.Value; + _ = _smallCapDictTen[val.Key]; + } + } + + [Benchmark] + public void EightValues_SmallDict() + { + for (var i = 0; i < 8; i++) + { + var val = _tenValues[i]; + _smallCapDictTen[val.Key] = val.Value; + _ = _smallCapDictTen[val.Key]; + } + } + + [Benchmark] + public void NineValues_SmallDict() + { + for (var i = 0; i < 9; i++) + { + var val = _tenValues[i]; + _smallCapDictTen[val.Key] = val.Value; + _ = _smallCapDictTen[val.Key]; + } + } + + [Benchmark] + public void TenValues_SmallDict() + { + for (var i = 0; i < 10; i++) + { + var val = _tenValues[i]; + _smallCapDictTen[val.Key] = val.Value; + _ = _smallCapDictTen[val.Key]; + } + } + + + [Benchmark] + public void FourValues_Dict() + { + for (var i = 0; i < 4; i++) + { + var val = _tenValues[i]; + _dictTen[val.Key] = val.Value; + _ = _dictTen[val.Key]; + } + } + + [Benchmark] + public void FiveValues_Dict() + { + for (var i = 0; i < 5; i++) + { + var val = _tenValues[i]; + _dictTen[val.Key] = val.Value; + _ = _dictTen[val.Key]; + } + } + [Benchmark] + public void SixValues_Dict() + { + for (var i = 0; i < 6; i++) + { + var val = _tenValues[i]; + _dictTen[val.Key] = val.Value; + _ = _dictTen[val.Key]; + } + } + [Benchmark] + public void SevenValues_Dict() + { + for (var i = 0; i < 7; i++) + { + var val = _tenValues[i]; + _dictTen[val.Key] = val.Value; + _ = _dictTen[val.Key]; + } + } + [Benchmark] + public void EightValues_Dict() + { + for (var i = 0; i < 8; i++) + { + var val = _tenValues[i]; + _dictTen[val.Key] = val.Value; + _ = _dictTen[val.Key]; + } + } + [Benchmark] + public void NineValues_Dict() + { + for (var i = 0; i < 9; i++) + { + var val = _tenValues[i]; + _dictTen[val.Key] = val.Value; + _ = _dictTen[val.Key]; + } + } + + [Benchmark] + public void TenValues_Dict() + { + for (var i = 0; i < 10; i++) + { + var val = _tenValues[i]; + _dictTen[val.Key] = val.Value; + _ = _dictTen[val.Key]; + } + } + + [Benchmark] + public void FourValues_SmallDictGet() + { + _ = _filledSmallDictionary["g"]; + } + + [Benchmark] + public void FiveValues_SmallDictGet() + { + _ = _filledSmallDictionary["i"]; + } + + [Benchmark] + public void SixValues_SmallDictGetGet() + { + _ = _filledSmallDictionary["k"]; + + } + + [Benchmark] + public void SevenValues_SmallDictGetGet() + { + _ = _filledSmallDictionary["m"]; + } + + [Benchmark] + public void EightValues_SmallDictGet() + { + _ = _filledSmallDictionary["o"]; + } + + [Benchmark] + public void NineValues_SmallDictGet() + { + _ = _filledSmallDictionary["q"]; + } + + [Benchmark] + public void TenValues_SmallDictGet() + { + _ = _filledSmallDictionary["s"]; + } + + [Benchmark] + public void TenValues_DictGet() + { + _ = _filledDictTen["s"]; + } + + [Benchmark] + public void SmallDict() + { + _ = new AdaptiveCapacityDictionary(capacity: 1); + } + + [Benchmark] + public void Dict() + { + _ = new Dictionary(capacity: 1); + } + + + [Benchmark] + public void SmallDictFour() + { + _ = new AdaptiveCapacityDictionary(capacity: 4); + } + + [Benchmark] + public void DictFour() + { + _ = new Dictionary(capacity: 4); + } + + [Benchmark] + public void SmallDictTen() + { + _ = new AdaptiveCapacityDictionary(capacity: 10); + } + + [Benchmark] + public void DictTen() + { + _ = new Dictionary(capacity: 10); + } + } +} diff --git a/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs b/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs deleted file mode 100644 index ff463e42e914..000000000000 --- a/src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using BenchmarkDotNet.Attributes; -using Microsoft.AspNetCore.Internal; - -namespace Microsoft.AspNetCore.Http -{ - public class AdaptiveCapacityDictionaryBenchmark - { - private AdaptiveCapacityDictionary _smallCapDict; - private AdaptiveCapacityDictionary _smallCapDictFour; - private AdaptiveCapacityDictionary _smallCapDictTen; - private Dictionary _dict; - private Dictionary _dictFour; - private Dictionary _dictTen; - - private KeyValuePair _oneValue; - private List> _fourValues; - private List> _tenValues; - - [IterationSetup] - public void Setup() - { - _oneValue = new KeyValuePair("a", "b"); - - _fourValues = new List>() - { - new KeyValuePair("a", "b"), - new KeyValuePair("c", "d"), - new KeyValuePair("e", "f"), - new KeyValuePair("g", "h"), - }; - - _tenValues = new List>() - { - new KeyValuePair("a", "b"), - new KeyValuePair("c", "d"), - new KeyValuePair("e", "f"), - new KeyValuePair("g", "h"), - new KeyValuePair("i", "j"), - new KeyValuePair("k", "l"), - new KeyValuePair("m", "n"), - new KeyValuePair("o", "p"), - new KeyValuePair("q", "r"), - new KeyValuePair("s", "t"), - }; - - _smallCapDict = new AdaptiveCapacityDictionary(capacity: 1, StringComparer.OrdinalIgnoreCase); - _smallCapDictFour = new AdaptiveCapacityDictionary(capacity: 4, StringComparer.OrdinalIgnoreCase); - _smallCapDictTen = new AdaptiveCapacityDictionary(capacity: 10, StringComparer.OrdinalIgnoreCase); - _dict = new Dictionary(1, StringComparer.OrdinalIgnoreCase); - _dictFour = new Dictionary(4, StringComparer.OrdinalIgnoreCase); - _dictTen = new Dictionary(10, StringComparer.OrdinalIgnoreCase); - } - - [Benchmark] - public void OneValue_SmallDict() - { - _smallCapDict[_oneValue.Key] = _oneValue.Value; - _ = _smallCapDict[_oneValue.Key]; - } - - [Benchmark] - public void OneValue_Dict() - { - _dict[_oneValue.Key] = _oneValue.Value; - _ = _dict[_oneValue.Key]; - } - - [Benchmark] - public void FourValues_SmallDict() - { - foreach (var val in _fourValues) - { - _smallCapDictFour[val.Key] = val.Value; - _ = _smallCapDictFour[val.Key]; - } - } - - [Benchmark] - public void FourValues_Dict() - { - foreach (var val in _fourValues) - { - _dictFour[val.Key] = val.Value; - _ = _dictFour[val.Key]; - } - } - - [Benchmark] - public void TenValues_SmallDict() - { - foreach (var val in _tenValues) - { - _smallCapDictTen[val.Key] = val.Value; - _ = _smallCapDictTen[val.Key]; - } - } - - [Benchmark] - public void TenValues_Dict() - { - foreach (var val in _tenValues) - { - _dictTen[val.Key] = val.Value; - _ = _dictTen[val.Key]; - } - } - - [Benchmark] - public void SmallDict() - { - _ = new AdaptiveCapacityDictionary(capacity: 1); - } - - [Benchmark] - public void Dict() - { - _ = new Dictionary(capacity: 1); - } - - - [Benchmark] - public void SmallDictFour() - { - _ = new AdaptiveCapacityDictionary(capacity: 4); - } - - [Benchmark] - public void DictFour() - { - _ = new Dictionary(capacity: 4); - } - - [Benchmark] - public void SmallDictTen() - { - _ = new AdaptiveCapacityDictionary(capacity: 10); - } - - [Benchmark] - public void DictTen() - { - _ = new Dictionary(capacity: 10); - } - } -} diff --git a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs index a199b8dd0c8a..fd632608a641 100644 --- a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs +++ b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Internal internal class AdaptiveCapacityDictionary : IDictionary, IReadOnlyDictionary where TKey : notnull { // Threshold for size of array to use. - private static readonly int DefaultArrayThreshold = 4; + private static readonly int DefaultArrayThreshold = 10; internal KeyValuePair[]? _arrayStorage; private int _count; @@ -123,6 +123,7 @@ public TValue this[TKey key] { ThrowArgumentNullExceptionForKey(); } + if (_arrayStorage != null) { var index = FindIndex(key); @@ -140,6 +141,7 @@ public TValue this[TKey key] { _arrayStorage[index] = new KeyValuePair(key, value); } + return; } _dictionaryStorage![key] = value; @@ -228,6 +230,7 @@ void ICollection>.Add(KeyValuePair item if (_arrayStorage != null) { Add(item.Key, item.Value); + return; } ((ICollection>)_dictionaryStorage!).Add(item); @@ -260,15 +263,11 @@ public void Add(TKey key, TValue value) _arrayStorage[_count] = new KeyValuePair(key, value); _count++; - } - - if (_dictionaryStorage != null) - { - _dictionaryStorage.Add(key, value); return; } - + _dictionaryStorage!.Add(key, value); + return; } /// @@ -524,15 +523,14 @@ public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) ThrowArgumentNullExceptionForKey(); } - if (_dictionaryStorage != null) + if (_arrayStorage != null) { - return _dictionaryStorage.TryGetValue(key, out value); + return TryFindItem(key, out value); } - return TryFindItem(key, out value); + return _dictionaryStorage!.TryGetValue(key, out value); } - [DoesNotReturn] private static void ThrowArgumentNullExceptionForKey() { @@ -576,6 +574,7 @@ private void EnsureCapacitySlow(int capacity) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int FindIndex(TKey key) { @@ -589,7 +588,7 @@ private int FindIndex(TKey key) for (var i = 0; i < count; i++) { - if (_comparer.Equals(array![i].Key, key)) + if (_comparer.Equals(array[i].Key, key)) { return i; } @@ -604,14 +603,18 @@ private bool TryFindItem(TKey key, out TValue? value) Debug.Assert(_dictionaryStorage == null); Debug.Assert(_arrayStorage != null); - var storage = _arrayStorage.AsSpan(0, _count); + var array = _arrayStorage; + var count = _count; - foreach (ref var item in storage) + if ((uint)count <= (uint)array.Length) { - if (_comparer.Equals(item.Key, key)) + for (var i = 0; i < count; i++) { - value = item.Value; - return true; + if (_comparer.Equals(array[i].Key, key)) + { + value = array[i].Value; + return true; + } } } @@ -625,13 +628,17 @@ private bool ContainsKeyArray(TKey key) Debug.Assert(_dictionaryStorage == null); Debug.Assert(_arrayStorage != null); - var storage = _arrayStorage.AsSpan(0, _count); + var array = _arrayStorage; + var count = _count; - foreach (ref var item in storage) + if ((uint)count <= (uint)array.Length) { - if (_comparer.Equals(item.Key, key)) + for (var i = 0; i < count; i++) { - return true; + if (_comparer.Equals(array[i].Key, key)) + { + return true; + } } } diff --git a/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs b/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs index 3a263168690a..f5d39b610999 100644 --- a/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs +++ b/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs @@ -1206,7 +1206,7 @@ public void TryGetValue_ListStorage_True_CaseInsensitive() } [Fact] - public void ListStorage_SwitchesToDictionaryAfter4_Add() + public void ListStorage_SwitchesToDictionaryAfter10_Add() { // Arrange var dict = new AdaptiveCapacityDictionary(); @@ -1216,23 +1216,27 @@ public void ListStorage_SwitchesToDictionaryAfter4_Add() // Assert 1 var storage = Assert.IsType[]>(dict._arrayStorage); - Assert.Equal(4, storage.Length); + Assert.Equal(10, storage.Length); // Act 2 dict.Add("key2", "value2"); dict.Add("key3", "value3"); dict.Add("key4", "value4"); dict.Add("key5", "value5"); + dict.Add("key6", "value2"); + dict.Add("key7", "value3"); + dict.Add("key8", "value4"); + dict.Add("key9", "value5"); + dict.Add("key10", "value2"); + dict.Add("key11", "value3"); // Assert 2 - storage = Assert.IsType[]>(dict._arrayStorage); - Assert.Empty(storage); - + Assert.Null(dict._arrayStorage); Assert.Equal(5, dict.Count); } [Fact] - public void ListStorage_SwitchesToDictionaryAfter4_TryAdd() + public void ListStorage_SwitchesToDictionaryAfter10_TryAdd() { // Arrange var dict = new AdaptiveCapacityDictionary(); @@ -1242,23 +1246,27 @@ public void ListStorage_SwitchesToDictionaryAfter4_TryAdd() // Assert 1 var storage = Assert.IsType[]>(dict._arrayStorage); - Assert.Equal(4, storage.Length); + Assert.Equal(10, storage.Length); // Act 2 dict.TryAdd("key2", "value2"); dict.TryAdd("key3", "value3"); dict.TryAdd("key4", "value4"); dict.TryAdd("key5", "value5"); + dict.TryAdd("key6", "value2"); + dict.TryAdd("key7", "value3"); + dict.TryAdd("key8", "value4"); + dict.TryAdd("key9", "value5"); + dict.TryAdd("key10", "value2"); + dict.TryAdd("key11", "value3"); // Assert 2 - storage = Assert.IsType[]>(dict._arrayStorage); - Assert.Empty(storage); - - Assert.Equal(5, dict.Count); + Assert.Null(dict._arrayStorage); + Assert.Equal(11, dict.Count); } [Fact] - public void ListStorage_SwitchesToDictionaryAfter4_Index() + public void ListStorage_SwitchesToDictionaryAfter10_Index() { // Arrange var dict = new AdaptiveCapacityDictionary(); @@ -1268,19 +1276,24 @@ public void ListStorage_SwitchesToDictionaryAfter4_Index() // Assert 1 var storage = Assert.IsType[]>(dict._arrayStorage); - Assert.Equal(4, storage.Length); + Assert.Equal(10, storage.Length); // Act 2 dict["key1"] = "value"; dict["key2"] = "value"; dict["key3"] = "value"; dict["key4"] = "value"; + dict["key5"] = "value"; + dict["key6"] = "value"; + dict["key7"] = "value"; + dict["key8"] = "value"; + dict["key9"] = "value"; + dict["key10"] = "value"; // Assert 2 storage = Assert.IsType[]>(dict._arrayStorage); - Assert.Empty(storage); - - Assert.Equal(5, dict.Count); + Assert.Null(dict._arrayStorage); + Assert.Equal(11, dict.Count); } [Fact] From a139d9aea5bec5c0b188a6882a49e0ef0ddd994c Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 1 Apr 2021 16:03:27 -0700 Subject: [PATCH 21/28] Update AdaptiveCapacityDictionaryTests.cs --- src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs b/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs index f5d39b610999..d34043ef299c 100644 --- a/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs +++ b/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.AspNetCore.Internal.Dictionary; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Testing; using Xunit; From 4a95eea50cb85b073f523123be7804374b7ddc87 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 1 Apr 2021 16:33:00 -0700 Subject: [PATCH 22/28] Update AdaptiveCapacityDictionary.cs --- src/Shared/Dictionary/AdaptiveCapacityDictionary.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs index fd632608a641..ec8edf8fc447 100644 --- a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs +++ b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Internal { /// - /// An type to hold a small amount of items (4 or less in the common case). + /// An type to hold a small amount of items (10 or less in the common case). /// internal class AdaptiveCapacityDictionary : IDictionary, IReadOnlyDictionary where TKey : notnull { From 069c51943c46c4f88a0af90134e4c543fa4ce71c Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 5 Apr 2021 12:00:22 -0700 Subject: [PATCH 23/28] Feedback --- .../AdaptiveCapacityDictionaryBenchmark.cs | 4 +- .../src/Internal/RequestCookieCollection.cs | 5 +- .../Dictionary/AdaptiveCapacityDictionary.cs | 142 +++++++----------- 3 files changed, 58 insertions(+), 93 deletions(-) diff --git a/src/Http/Http/perf/Microbenchmarks/AdaptiveCapacityDictionaryBenchmark.cs b/src/Http/Http/perf/Microbenchmarks/AdaptiveCapacityDictionaryBenchmark.cs index 15368c45218b..40684acda48f 100644 --- a/src/Http/Http/perf/Microbenchmarks/AdaptiveCapacityDictionaryBenchmark.cs +++ b/src/Http/Http/perf/Microbenchmarks/AdaptiveCapacityDictionaryBenchmark.cs @@ -82,13 +82,13 @@ public void OneValue_Dict_Set() [Benchmark] public void OneValue_SmallDict_Get() { - _smallCapDict.TryGetValue("test", out _); + _smallCapDict.TryGetValue("test", out var val); } [Benchmark] public void OneValue_Dict_Get() { - _dict.TryGetValue("test", out _); + _dict.TryGetValue("test", out var val); } [Benchmark] diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs index c4aee0c48797..62080e024498 100644 --- a/src/Http/Http/src/Internal/RequestCookieCollection.cs +++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs @@ -124,9 +124,8 @@ public bool TryGetValue(string key, [MaybeNullWhen(false)] out string? value) value = null; return false; } - var res = Store.TryGetValue(key, out var objValue); - value = objValue as string; - return res; + + return Store.TryGetValue(key, out value); } /// diff --git a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs index ec8edf8fc447..344c7e302d49 100644 --- a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs +++ b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace Microsoft.AspNetCore.Internal { @@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.Internal internal class AdaptiveCapacityDictionary : IDictionary, IReadOnlyDictionary where TKey : notnull { // Threshold for size of array to use. - private static readonly int DefaultArrayThreshold = 10; + private const int DefaultArrayThreshold = 10; internal KeyValuePair[]? _arrayStorage; private int _count; @@ -56,7 +57,7 @@ public AdaptiveCapacityDictionary(int capacity) /// Equality comparison. public AdaptiveCapacityDictionary(int capacity, IEqualityComparer comparer) { - if (comparer is not null && comparer != EqualityComparer.Default) // first check for null to avoid forcing default comparer instantiation unnecessarily + if (comparer is not null) { _comparer = comparer; } @@ -145,31 +146,14 @@ public TValue this[TKey key] } _dictionaryStorage![key] = value; - return; } } /// - public int Count - { - get - { - if (_dictionaryStorage != null) - { - return _dictionaryStorage.Count; - } - return _count; - } - } + public int Count => _dictionaryStorage != null ? _dictionaryStorage.Count : _count; /// - public IEqualityComparer Comparer - { - get - { - return _comparer ?? EqualityComparer.Default; - } - } + public IEqualityComparer Comparer => _comparer; /// bool ICollection>.IsReadOnly => false; @@ -267,7 +251,6 @@ public void Add(TKey key, TValue value) } _dictionaryStorage!.Add(key, value); - return; } /// @@ -308,18 +291,12 @@ public bool ContainsKey(TKey key) ThrowArgumentNullExceptionForKey(); } - if (_dictionaryStorage != null) + if (_dictionaryStorage is null) { - return _dictionaryStorage.ContainsKey(key); + return ContainsKeyArray(key); } - return ContainsKeyCore(key); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool ContainsKeyCore(TKey key) - { - return ContainsKeyArray(key); + return _dictionaryStorage.ContainsKey(key); } /// @@ -495,7 +472,7 @@ public bool TryAdd(TKey key, TValue value) if (_arrayStorage != null) { - if (ContainsKeyCore(key)) + if (ContainsKey(key)) { return false; } @@ -540,6 +517,11 @@ private static void ThrowArgumentNullExceptionForKey() [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnsureCapacity(int capacity) { + if (_arrayStorage!.Length < capacity) + { + return; + } + EnsureCapacitySlow(capacity); } @@ -547,50 +529,56 @@ private void EnsureCapacitySlow(int capacity) { Debug.Assert(_arrayStorage != null); - if (_arrayStorage.Length < capacity) + if (capacity > DefaultArrayThreshold) { - if (capacity > DefaultArrayThreshold) + _dictionaryStorage = new Dictionary(capacity); + foreach (var item in _arrayStorage) { - _dictionaryStorage = new Dictionary(capacity); - foreach (var item in _arrayStorage) - { - _dictionaryStorage[item.Key] = item.Value; - } - - // Clear array storage. - _arrayStorage = null; + _dictionaryStorage[item.Key] = item.Value; } - else - { - capacity = _arrayStorage.Length == 0 ? DefaultArrayThreshold : _arrayStorage.Length * 2; - var array = new KeyValuePair[capacity]; - if (_count > 0) - { - Array.Copy(_arrayStorage, 0, array, 0, _count); - } - _arrayStorage = array; + // Clear array storage. + _arrayStorage = null; + } + else + { + capacity = _arrayStorage.Length == 0 ? DefaultArrayThreshold : _arrayStorage.Length * 2; + var array = new KeyValuePair[capacity]; + if (_count > 0) + { + Array.Copy(_arrayStorage, 0, array, 0, _count); } + + _arrayStorage = array; } } + private Span> ArrayStorageSpan + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Debug.Assert(_arrayStorage is not null); + Debug.Assert(_count <= _arrayStorage.Length); + ref var r = ref MemoryMarshal.GetArrayDataReference(_arrayStorage); + return MemoryMarshal.CreateSpan(ref r, _count); + } + } [MethodImpl(MethodImplOptions.AggressiveInlining)] private int FindIndex(TKey key) { - // Generally the bounds checking here will be elided by the JIT because this will be called - // on the same code path as EnsureCapacity. Debug.Assert(_dictionaryStorage == null); Debug.Assert(_arrayStorage != null); - var array = _arrayStorage; - var count = _count; - - for (var i = 0; i < count; i++) + if (_count > 0) { - if (_comparer.Equals(array[i].Key, key)) + for (var i = 0; i < ArrayStorageSpan.Length; ++i) { - return i; + if (_comparer.Equals(ArrayStorageSpan[i].Key, key)) + { + return i; + } } } @@ -603,16 +591,13 @@ private bool TryFindItem(TKey key, out TValue? value) Debug.Assert(_dictionaryStorage == null); Debug.Assert(_arrayStorage != null); - var array = _arrayStorage; - var count = _count; - - if ((uint)count <= (uint)array.Length) + if (_count > 0) { - for (var i = 0; i < count; i++) + foreach (ref var item in ArrayStorageSpan) { - if (_comparer.Equals(array[i].Key, key)) + if (_comparer.Equals(item.Key, key)) { - value = array[i].Value; + value = item.Value; return true; } } @@ -623,27 +608,8 @@ private bool TryFindItem(TKey key, out TValue? value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool ContainsKeyArray(TKey key) - { - Debug.Assert(_dictionaryStorage == null); - Debug.Assert(_arrayStorage != null); + private bool ContainsKeyArray(TKey key) => TryFindItem(key, out _); - var array = _arrayStorage; - var count = _count; - - if ((uint)count <= (uint)array.Length) - { - for (var i = 0; i < count; i++) - { - if (_comparer.Equals(array[i].Key, key)) - { - return true; - } - } - } - - return false; - } /// public struct Enumerator : IEnumerator> @@ -686,7 +652,7 @@ public void Dispose() public bool MoveNext() { var dictionary = _dictionary; - if (_dictionary._arrayStorage == null) + if (dictionary._arrayStorage == null) { return false; } @@ -696,7 +662,7 @@ public bool MoveNext() return false; } - Current = dictionary._arrayStorage![_index]; + Current = dictionary._arrayStorage[_index]; _index++; return true; } From 5342d4d00255fef8e63f586a0e95011c2f89044d Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 5 Apr 2021 12:00:49 -0700 Subject: [PATCH 24/28] Add comment --- src/Http/Http/src/Internal/RequestCookieCollection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs index 62080e024498..231c747e8e9d 100644 --- a/src/Http/Http/src/Internal/RequestCookieCollection.cs +++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs @@ -33,6 +33,7 @@ public RequestCookieCollection(int capacity) Store = new AdaptiveCapacityDictionary(capacity, StringComparer.OrdinalIgnoreCase); } + // For tests public RequestCookieCollection(Dictionary store) { Store = new AdaptiveCapacityDictionary(store.ToList(), capacity: store.Count, StringComparer.OrdinalIgnoreCase); From 7348343da67664d89a3b74c1401f0412368f8d92 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 5 Apr 2021 12:32:31 -0700 Subject: [PATCH 25/28] Removing adaptive restriction --- src/Http/Http/src/Internal/RequestCookieCollection.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs index 231c747e8e9d..79db8288568b 100644 --- a/src/Http/Http/src/Internal/RequestCookieCollection.cs +++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs @@ -21,7 +21,7 @@ internal class RequestCookieCollection : IRequestCookieCollection private static readonly IEnumerator> EmptyIEnumeratorType = EmptyEnumerator; private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator; - private AdaptiveCapacityDictionary Store { get; set; } + private IDictionary Store { get; set; } public RequestCookieCollection() { @@ -36,7 +36,7 @@ public RequestCookieCollection(int capacity) // For tests public RequestCookieCollection(Dictionary store) { - Store = new AdaptiveCapacityDictionary(store.ToList(), capacity: store.Count, StringComparer.OrdinalIgnoreCase); + Store = store; } public string? this[string key] @@ -177,10 +177,10 @@ IEnumerator IEnumerable.GetEnumerator() public struct Enumerator : IEnumerator> { // Do NOT make this readonly, or MoveNext will not work - private AdaptiveCapacityDictionary.Enumerator _dictionaryEnumerator; + private IEnumerator> _dictionaryEnumerator; private bool _notEmpty; - internal Enumerator(AdaptiveCapacityDictionary.Enumerator dictionaryEnumerator) + internal Enumerator(IEnumerator> dictionaryEnumerator) { _dictionaryEnumerator = dictionaryEnumerator; _notEmpty = true; From 4456cc0467f991a6e7907348c09c549dac300e20 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 5 Apr 2021 12:51:41 -0700 Subject: [PATCH 26/28] wrong check for capacity --- src/Shared/Dictionary/AdaptiveCapacityDictionary.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs index 344c7e302d49..cf16c91489eb 100644 --- a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs +++ b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs @@ -517,7 +517,7 @@ private static void ThrowArgumentNullExceptionForKey() [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnsureCapacity(int capacity) { - if (_arrayStorage!.Length < capacity) + if (_arrayStorage!.Length >= capacity) { return; } From 58b5f097b3d9ebda043e886141fac2f5e7271fab Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 5 Apr 2021 14:01:57 -0700 Subject: [PATCH 27/28] Remove cast and add constructor --- src/Http/Http/src/Internal/RequestCookieCollection.cs | 8 ++++---- src/Shared/Dictionary/AdaptiveCapacityDictionary.cs | 11 +++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs index 79db8288568b..7a02b276b342 100644 --- a/src/Http/Http/src/Internal/RequestCookieCollection.cs +++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs @@ -21,7 +21,7 @@ internal class RequestCookieCollection : IRequestCookieCollection private static readonly IEnumerator> EmptyIEnumeratorType = EmptyEnumerator; private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator; - private IDictionary Store { get; set; } + private AdaptiveCapacityDictionary Store { get; set; } public RequestCookieCollection() { @@ -36,7 +36,7 @@ public RequestCookieCollection(int capacity) // For tests public RequestCookieCollection(Dictionary store) { - Store = store; + Store = new AdaptiveCapacityDictionary(store); } public string? this[string key] @@ -177,10 +177,10 @@ IEnumerator IEnumerable.GetEnumerator() public struct Enumerator : IEnumerator> { // Do NOT make this readonly, or MoveNext will not work - private IEnumerator> _dictionaryEnumerator; + private AdaptiveCapacityDictionary.Enumerator _dictionaryEnumerator; private bool _notEmpty; - internal Enumerator(IEnumerator> dictionaryEnumerator) + internal Enumerator(AdaptiveCapacityDictionary.Enumerator dictionaryEnumerator) { _dictionaryEnumerator = dictionaryEnumerator; _notEmpty = true; diff --git a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs index cf16c91489eb..3a5bb6932cf8 100644 --- a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs +++ b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs @@ -103,6 +103,17 @@ internal AdaptiveCapacityDictionary(IEnumerable> valu } } + /// + /// Creates a initialized with the specified . + /// + /// A dictionary to use. + /// + internal AdaptiveCapacityDictionary(Dictionary dict) + { + _comparer = dict.Comparer; + _dictionaryStorage = dict; + } + /// public TValue this[TKey key] { From 8768f0e8364ceda5cbb278d2229321fe44ddc70d Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 5 Apr 2021 16:23:29 -0700 Subject: [PATCH 28/28] Nit --- .../Dictionary/AdaptiveCapacityDictionary.cs | 40 ++++++++++++++----- .../AdaptiveCapacityDictionaryTests.cs | 21 +++++++++- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs index 3a5bb6932cf8..d039c4ffbbbd 100644 --- a/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs +++ b/src/Shared/Dictionary/AdaptiveCapacityDictionary.cs @@ -627,6 +627,8 @@ public struct Enumerator : IEnumerator> { private readonly AdaptiveCapacityDictionary _dictionary; private int _index; + // Don't mark this as readonly + private Dictionary.Enumerator? _dictionaryEnumerator; /// /// Instantiates a new enumerator with the values provided in . @@ -641,6 +643,15 @@ public Enumerator(AdaptiveCapacityDictionary dictionary) _dictionary = dictionary; + if (_dictionary._dictionaryStorage != null) + { + _dictionaryEnumerator = _dictionary._dictionaryStorage.GetEnumerator(); + } + else + { + _dictionaryEnumerator = null; + } + Current = default; _index = 0; } @@ -663,19 +674,30 @@ public void Dispose() public bool MoveNext() { var dictionary = _dictionary; - if (dictionary._arrayStorage == null) + if (dictionary._arrayStorage != null) { - return false; - } + if (dictionary._count <= _index) + { + return false; + } - if (dictionary._count <= _index) - { - return false; + Current = dictionary._arrayStorage[_index]; + _index++; + return true; } + else + { + var enumerator = _dictionaryEnumerator!.Value; + var hasNext = enumerator.MoveNext(); + if (hasNext) + { + Current = enumerator.Current; + } - Current = dictionary._arrayStorage[_index]; - _index++; - return true; + _dictionaryEnumerator = enumerator; + + return hasNext; + } } /// diff --git a/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs b/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs index d34043ef299c..b31ae8e14928 100644 --- a/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs +++ b/src/Shared/test/Shared.Tests/AdaptiveCapacityDictionaryTests.cs @@ -1076,6 +1076,12 @@ public void TryAdd_EmptyStorage_CanAdd() kvp => Assert.Equal(new KeyValuePair("key", "value"), kvp), kvp => Assert.Equal(default, kvp), kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), kvp => Assert.Equal(default, kvp)); } @@ -1098,6 +1104,12 @@ public void TryAdd_ArrayStorage_CanAdd() kvp => Assert.Equal(new KeyValuePair("key0", "value0"), kvp), kvp => Assert.Equal(new KeyValuePair("key1", "value1"), kvp), kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), kvp => Assert.Equal(default, kvp)); } @@ -1120,6 +1132,12 @@ public void TryAdd_ArrayStorage_DoesNotAddWhenKeyIsPresent() kvp => Assert.Equal(new KeyValuePair("key0", "value0"), kvp), kvp => Assert.Equal(default, kvp), kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), + kvp => Assert.Equal(default, kvp), kvp => Assert.Equal(default, kvp)); } @@ -1232,7 +1250,7 @@ public void ListStorage_SwitchesToDictionaryAfter10_Add() // Assert 2 Assert.Null(dict._arrayStorage); - Assert.Equal(5, dict.Count); + Assert.Equal(11, dict.Count); } [Fact] @@ -1291,7 +1309,6 @@ public void ListStorage_SwitchesToDictionaryAfter10_Index() dict["key10"] = "value"; // Assert 2 - storage = Assert.IsType[]>(dict._arrayStorage); Assert.Null(dict._arrayStorage); Assert.Equal(11, dict.Count); }