-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Reduce ConcurrentDictionary.TryGetValue overhead #37081
Conversation
Tagging subscribers to this area: @eiriktsarpalis |
...ries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs
Show resolved
Hide resolved
That's a really nice clean up :) |
...ries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs
Show resolved
Hide resolved
...ries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Collections.Concurrent/src/System/ThrowHelper.cs
Outdated
Show resolved
Hide resolved
...ries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs
Outdated
Show resolved
Hide resolved
...ries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs
Outdated
Show resolved
Hide resolved
...ries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs
Outdated
Show resolved
Hide resolved
#1989 proposed to use FastMod in HashSet, but we bailed on the PR because we could not use #if BIT64 since it's not in corelib. But I see you bypassed that problem by doing a runtime check against IntPtr length. Coudl we do that in HashSet too? [edit] Does the JIT treat IntPtr.Size as a constant and elide dead codepaths? If so, then it has no real runtime penalty. |
...ries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs
Show resolved
Hide resolved
It sure does :) |
@saucecontrol I guess someone could revive #2143 then. That would be a perf win in HashSet. |
...ries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs
Show resolved
Hide resolved
I can take care of it. |
...ries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs
Show resolved
Hide resolved
...ries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs
Outdated
Show resolved
Hide resolved
No functional changes.
From ECMA 335: "The native size types (native int, native unsigned int, and &) are always naturally aligned (4 bytes or 8 bytes, depending on the architecture)" and "A conforming CLI shall guarantee that read and write access to properly aligned memory locations no larger than the native word size (the size of type native int) is atomic (see §I.12.6.2) when all the write accesses to a location are the same size. "
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) | ||
{ | ||
if (key == null) ThrowKeyNullException(); | ||
return TryGetValueInternal(key, _comparer.GetHashCode(key), out value); |
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.
@stephentoub Why did you choose to duplicate the logic that is in TryGetValueInternal(...)
? Is this worth 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.
Do you mean the duplication between TryGetValue and TryGetValueInternal? The difference is that TryGetValueInternal is used when we already have a hashcode, whereas TryGetValue is used when we don't and need to compute one. In order to compute one, I'd need to go through the same decision tree that TryGetValueInternal does, which means we'd be doing it twice. Rather than paying to do it twice, I duplicated the code, as TryGetValue is in most cases the hot path / most important to be fast member of ConcurrentDictionary.
The first commit is purely style; I initially found myself fixing up things as I was making changes for perf, and wanted those cleanly separated.
The rest of the commits are primarily trying to make
ConcurrentDictionary<>.TryGetValue
faster, in particular by porting the kinds of changes that have been made in the past toDictionary<>
. The biggest impact comes when no comparer is specified, especially when the key is a value type.cc: @eiriktsarpalis, @benaadams, @AntonLapounov, @GrabYourPitchforks