-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Fix Dictionary perf regression for non-string ref type keys #75663
Conversation
Tagging subscribers to this area: @dotnet/area-system-collections Issue DetailsSeveral releases ago, some performance improvements were made to Dictionary that were particularly valuable for value type keys, enabling equality comparisons to be devirtualized and possibly inlined. For non-string reference type keys, however, this can end up being a regression, as every dictionary access then needs to access This fixes the regression by ensuring we always store a comparer in the dictionary's constructor if TKey is a reference type. The same fix is applied to
------------ |------------------ |---------:|------:| using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Collections.Generic;
using System.Linq;
[MemoryDiagnoser(displayGenColumns: false)]
[HideColumns("Job", "Error", "StdDev")]
public partial class Program
{
static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
private C[] _keys;
private Dictionary<C, int> _d;
[GlobalSetup]
public void Setup()
{
_keys = Enumerable.Range(0, 1000).Select(i => new C { Value = i }).ToArray();
_d = new Dictionary<C, int>(_keys.Select(c => KeyValuePair.Create(c, c.Value)));
}
[Benchmark]
public int TryGetValue()
{
int count = 0;
foreach (C c in _keys)
if (_d.TryGetValue(c, out _))
count++;
return count;
}
}
class C
{
public int Value;
public override int GetHashCode() => Value;
public override bool Equals(object obj) => obj is C c && Value == c.Value;
}
|
Several releases ago, some performance improvements were made to Dictionary that were particularly valuable for value type keys, enabling equality comparisons to be devirtualized and possibly inlined. For non-string reference type keys, however, this can end up being a regression, as every dictionary access then needs to access `EqualityComparer<TKey>.Default`, which can be more expensive with shared generics. The access is hoisted out of a loop, but at least one per call is still needed. This fixes the regression by ensuring we always store a comparer in the dictionary's constructor if TKey is a reference type. The same fix is applied to `HashSet<T>`. This also means we can delete some now unreachable code.
2bf09e8
to
f5d5e3a
Compare
src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs
Outdated
Show resolved
Hide resolved
...System.Private.CoreLib/src/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have micro-benchmarks that cover the case that this is improving?
...System.Private.CoreLib/src/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs
Outdated
Show resolved
Hide resolved
...System.Private.CoreLib/src/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs
Outdated
Show resolved
Hide resolved
...System.Private.CoreLib/src/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.cs
Show resolved
Hide resolved
Not merged; we generally only cover int and string, e.g. |
Several releases ago, some performance improvements were made to Dictionary that were particularly valuable for value type keys, enabling equality comparisons to be devirtualized and possibly inlined. For non-string reference type keys, however, this can end up being a regression, as every dictionary access then needs to access
EqualityComparer<TKey>.Default
, which can be more expensive with shared generics. The access is hoisted out of a loop, but at least one per call is still needed.This fixes the regression by ensuring we always store a comparer in the dictionary's constructor if TKey is a reference type. The same fix is applied to
HashSet<T>
. This also means we can delete some now unreachable code.