Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new SmallCapacityDictionary for dictionaries with a small amount of values. #31360

Merged
merged 28 commits into from
Apr 6, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Routing
public class RouteValueDictionary : IDictionary<string, object?>, IReadOnlyDictionary<string, object?>
{
// 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;
jkotalik marked this conversation as resolved.
Show resolved Hide resolved

internal KeyValuePair<string, object?>[] _arrayStorage;
internal PropertyStorage? _propertyStorage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
<ItemGroup>
<Reference Include="BenchmarkDotNet" />
<Reference Include="Microsoft.AspNetCore.Http" />

<Compile Include="$(SharedSourceRoot)BenchmarkRunner\*.cs" />
</ItemGroup>

Expand Down
150 changes: 150 additions & 0 deletions src/Http/Http/perf/Microbenchmarks/SmallCapacityDictionaryBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// 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;

namespace Microsoft.AspNetCore.Http
{
public class SmallCapacityDictionaryBenchmark
{
private SmallCapacityDictionary<string, string> _smallCapDict;
private SmallCapacityDictionary<string, string> _smallCapDictFour;
private SmallCapacityDictionary<string, string> _smallCapDictTen;
private Dictionary<string, string> _dict;
private Dictionary<string, string> _dictFour;
private Dictionary<string, string> _dictTen;

private KeyValuePair<string, string> _oneValue;
private List<KeyValuePair<string, string>> _fourValues;
private List<KeyValuePair<string, string>> _tenValues;

[IterationSetup]
public void Setup()
{
_oneValue = new KeyValuePair<string, string>("a", "b");

_fourValues = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("a", "b"),
new KeyValuePair<string, string>("c", "d"),
new KeyValuePair<string, string>("e", "f"),
new KeyValuePair<string, string>("g", "h"),
};

_tenValues = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("a", "b"),
new KeyValuePair<string, string>("c", "d"),
new KeyValuePair<string, string>("e", "f"),
new KeyValuePair<string, string>("g", "h"),
new KeyValuePair<string, string>("i", "j"),
new KeyValuePair<string, string>("k", "l"),
new KeyValuePair<string, string>("m", "n"),
new KeyValuePair<string, string>("o", "p"),
new KeyValuePair<string, string>("q", "r"),
new KeyValuePair<string, string>("s", "t"),
};

_smallCapDict = new SmallCapacityDictionary<string, string>(capacity: 1, StringComparer.OrdinalIgnoreCase);
_smallCapDictFour = new SmallCapacityDictionary<string, string>(capacity: 4, StringComparer.OrdinalIgnoreCase);
_smallCapDictTen = new SmallCapacityDictionary<string, string>(capacity: 10, StringComparer.OrdinalIgnoreCase);
_dict = new Dictionary<string, string>(1, StringComparer.OrdinalIgnoreCase);
_dictFour = new Dictionary<string, string>(4, StringComparer.OrdinalIgnoreCase);
_dictTen = new Dictionary<string, string>(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 SmallCapacityDictionary<string, string>(capacity: 1);
}

[Benchmark]
public void Dict()
{
_ = new Dictionary<string, string>(capacity: 1);
}


[Benchmark]
public void SmallDictFour()
{
_ = new SmallCapacityDictionary<string, string>(capacity: 4);
}

[Benchmark]
public void DictFour()
{
_ = new Dictionary<string, string>(capacity: 4);
}

[Benchmark]
public void SmallDictTen()
{
_ = new SmallCapacityDictionary<string, string>(capacity: 10);
}

[Benchmark]
public void DictTen()
{
_ = new Dictionary<string, string>(capacity: 10);
}
}
}
23 changes: 14 additions & 9 deletions src/Http/Http/src/Internal/RequestCookieCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Primitives;
using Microsoft.AspNetCore.Internal.Dictionary;
using Microsoft.Net.Http.Headers;
using System.Linq;

namespace Microsoft.AspNetCore.Http
{
Expand All @@ -19,20 +21,21 @@ internal class RequestCookieCollection : IRequestCookieCollection
private static readonly IEnumerator<KeyValuePair<string, string>> EmptyIEnumeratorType = EmptyEnumerator;
private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator;

private Dictionary<string, string>? Store { get; set; }
private SmallCapacityDictionary<string, string> Store { get; set; }

public RequestCookieCollection()
{
Store = new SmallCapacityDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}

public RequestCookieCollection(Dictionary<string, string> store)
public RequestCookieCollection(int capacity)
{
Store = store;
Store = new SmallCapacityDictionary<string, string>(capacity, StringComparer.OrdinalIgnoreCase);
}

public RequestCookieCollection(int capacity)
public RequestCookieCollection(Dictionary<string, string> store)
{
Store = new Dictionary<string, string>(capacity, StringComparer.OrdinalIgnoreCase);
Store = new SmallCapacityDictionary<string, string>(store.ToList(), capacity: store.Count, StringComparer.OrdinalIgnoreCase);
}

public string? this[string key]
Expand Down Expand Up @@ -121,7 +124,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;
jkotalik marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
Expand Down Expand Up @@ -172,10 +177,10 @@ IEnumerator IEnumerable.GetEnumerator()
public struct Enumerator : IEnumerator<KeyValuePair<string, string>>
{
// Do NOT make this readonly, or MoveNext will not work
private Dictionary<string, string>.Enumerator _dictionaryEnumerator;
private SmallCapacityDictionary<string, string>.Enumerator _dictionaryEnumerator;
private bool _notEmpty;

internal Enumerator(Dictionary<string, string>.Enumerator dictionaryEnumerator)
internal Enumerator(SmallCapacityDictionary<string, string>.Enumerator dictionaryEnumerator)
{
_dictionaryEnumerator = dictionaryEnumerator;
_notEmpty = true;
Expand All @@ -197,7 +202,7 @@ public KeyValuePair<string, string> Current
if (_notEmpty)
{
var current = _dictionaryEnumerator.Current;
return new KeyValuePair<string, string>(current.Key, current.Value);
return new KeyValuePair<string, string>(current.Key, (string)current.Value!);
}
return default(KeyValuePair<string, string>);
}
Expand Down
1 change: 1 addition & 0 deletions src/Http/Http/src/Microsoft.AspNetCore.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<Compile Include="..\..\Shared\HttpRuleParser.cs" LinkBase="Internal" />
<Compile Include="..\..\Shared\HttpParseResult.cs" LinkBase="Internal" />
<Compile Include="..\..\WebUtilities\src\AspNetCoreTempDirectory.cs" LinkBase="Internal" />
<Compile Include="..\..\..\Shared\Dictionary\SmallCapacityDictionary.cs" LinkBase="Internal" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Shared/CookieHeaderParserShared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Microsoft.Net.Http.Headers
{
internal static class CookieHeaderParserShared
{
public static bool TryParseValues(StringValues values, Dictionary<string, string> store, bool enableCookieNameEncoding, bool supportsMultipleValues)
public static bool TryParseValues(StringValues values, IDictionary<string, string> 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.
Expand Down
35 changes: 19 additions & 16 deletions src/Security/Authentication/Cookies/samples/CookieSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
jkotalik marked this conversation as resolved.
Show resolved Hide resolved
//{
// 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");
});
}
}
Expand Down
Loading