-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Faster Dictionary clone #41944
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
Faster Dictionary clone #41944
Conversation
|
Tagging subscribers to this area: @eiriktsarpalis, @jeffhandley |
src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs
Outdated
Show resolved
Hide resolved
|
@danmosemsft it would be nice if we also made this improvement in DictionarySlim. For specific cache-like use cases we roll our own dictionary and it would be nice to not have to do that. |
|
runtime (Libraries Test Run release coreclr Windows_NT x86 Release) issue #41494 |
|
Helix gateway timeout on "runtime (Libraries Test Run release coreclr Windows_NT x86 Release)" |
3a519d8 to
9c6de72
Compare
|
Broken compress variant, but I'm quite impressed there are tests for these scenarios :) |
src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs
Outdated
Show resolved
Hide resolved
0eea740 to
36abff2
Compare
src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs
Outdated
Show resolved
Hide resolved
387ec43 to
dc6f15e
Compare
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
|
It would be nice to share performance numbers for the different cases. |
@mjsabby the copy constructor specifically? It would be easy to add if someone would offer a PR. I am unsure what to do with DictionarySlim. It's encouraging that you're using it, or plan to. That may suggest we should remove the "Experimental" or even put it into the core collections. But @GrabYourPitchforks had concerns about it's "unsafeness" (I think related to mutating the entry in place). Also the name may suggest to people it's optimized for small number of items, which it's not (I happened to just run across @benaadams type with the same name) |
|
The code LGTM. But we don't have evidence that this is a hot path for anybody. I'm somewhat paranoid that we're going to use this code as a new performance profile, then in the future we'll discover some obscure edge case which means we need to go down a somewhat slower path, then some automated system will kick in and say "Ah, but you regressed this method's performance by 17%!", and then we'll spend days chasing it down - all while continuing not to have evidence that this is actually a hot path for real-world apps. Basically, I'm afraid that it's signing us up for continuing maintenance work for a code path that we didn't want to invest significant resources into. |
Signals Rosyln would move away from ImmuntableDictionary for a fast Dictionary clone method dotnet/roslyn#47503 (comment)
/cc @sharwell and
/cc @CyrusNajmabadi Also for lockless read Dictionary, with very infrequent updates (where reallocating the entire dictionary is more lightweight than taking a lock on every read, or using class LookupTable
{
private Dictionary<int, string>? _lookUp;
private readonly object _lock = new ();
public bool TryGetValue(int key, [MaybeNullWhen(false)] out string value)
{
// No locks required for read
var lookUp = _lookUp;
if (lookUp is null)
{
value = default;
return false;
}
return lookUp.TryGetValue(key, out value);
}
public void Add(int key, string value)
{
lock (_lock)
{
// Clone + Add
var dict = _lookUp?.Clone() ?? new (capacity: 1);
dict.Add(key, value);
// Switch reference
_lookUp = dict;
}
}
public void AddRange(IEnumerable<KeyValuePair<int, string>> collection)
{
lock (_lock)
{
// Clone + Add
var dict = _lookUp?.Clone() ?? new(capacity: 8);
foreach (var entry in collection)
{
dict.Add(entry.Key, entry.Value);
}
// Switch reference
_lookUp = dict;
}
}
} |
|
Its also why I wanted to add a clone extension method in #41939 public static class DictionaryExtensions
{
public static Dictionary<TKey, TValue> Clone<TKey, TValue>(this Dictionary<TKey, TValue> source) where TKey : notnull
{
return new Dictionary<TKey, TValue>(source, source.Comparer);
}
}Because it's not clear that passing a |
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
48eba29 to
3121db3
Compare
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
eba83bc to
b8a8d73
Compare
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
|
Released |
Nice! |
jkotas
left a comment
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.
LGTM. Thanks!
src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs
Outdated
Show resolved
Hide resolved
|
Is there any pending feedback on this PR? If not I'll just merge. |
|
Don't think so; CI wasn't behaving (one job failed in unrelated area), when it was was going to rebase for a clean set of greens, but looking at the open PRs doesn't look like CI is there yet... |
a963341 to
3c2c6c4
Compare
3c2c6c4 to
39aafb5
Compare
|
Squashed and Rebased |
|
Failing tests seem like unrelated timeouts. |
|
Thanks @benaadams! |
|
Aside it should also improve runtime/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/SparseTensor.cs Lines 108 to 112 in edb9019
runtime/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/SparseTensor.cs Lines 156 to 160 in edb9019
|
|
Just wanted to update that we are seeing these wins in the performance lab for both strings and ints. Run Information
Regressions in System.Collections.CtorFromCollection
Reprogit clone https://github.com/dotnet/performance.git
py .\performance\scripts\benchmarks_ci.py -f netcoreapp5.0 --filter 'System.Collections.CtorFromCollection<String>*'DetailsHistogramSystem.Collections.CtorFromCollection.Dictionary(Size: 512)DocsProfiling workflow for dotnet/runtime repository Run Information
Regressions in System.Collections.CtorFromCollection
Reprogit clone https://github.com/dotnet/performance.git
py .\performance\scripts\benchmarks_ci.py -f netcoreapp5.0 --filter 'System.Collections.CtorFromCollection<Int32>*'DetailsHistogramSystem.Collections.CtorFromCollection.Dictionary(Size: 512)DocsProfiling workflow for dotnet/runtime repository |
|
Love it - thank you @DrewScoggins for following up with that positive data. |
_1.png)
_1.png)
Resolves #41939
Fast
.Clone()extension method can be made on top of this change asString keys are the biggest improvement since they cost most to rehash
https://github.com/dotnet/performance/compare/master...benaadams:DictionaryClone?expand=1