-
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
An edge case when devirtualization doesn't help at all #103280
Comments
JIT is not allowed to make such assumptions, you might construct/load a new type dynamically The issue here is that Basically, the minimal repro is: [MethodImpl(MethodImplOptions.NoInlining)]
static bool Test<T>(StronglyTypedKey<T> a, StronglyTypedKey<T> b) =>
EqualityComparer<StronglyTypedKey<T>>.Default.Equals(a, b);
public readonly record struct StronglyTypedKey<T>(T Value); |
So, but if that type cannot be unloaded then that option should be possible, right? There's always a fallback for anything which cannot be devirtualized or assumed correctly.
Could you explain how is it related to indirect calls? Not into all the internals, so cannot understand how the described problem by Andy is related to that.
Technically, yes, but I mentioned runtime/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs Lines 65 to 82 in 4f9cd4d
Therefore, another possible solution for that problem is an intrinsic method which can be treated by the JIT as constant like public static class RuntimeHelpers
{
public static bool IsSharedGeneric<T>() => IsSharedGenericHolder<T>.Value;
private static class IsSharedGenericHolder<T>
{
private static readonly bool Value = IsSharedGeneric(typeof(T));
}
private static bool IsSharedGeneric(Type type)
{
if (type.IsGeneric)
foreach (var argument in type.GetGenericArguments())
{
if (!type.IsValueType || IsSharedGeneric(argument))
return true;
}
return false;
}
} Then |
Description
Recently I wrote an article explaining benchmark results which João Antunes had in his own article, and it's seems that it's indeed an edge case when devirtualization gives nothing except code size. The problem hides in the slow resolution of the default equality comparer.
The code above produces shared generic
Dictionary<StronglyTypedKey<__Cannon>>
type which seems fine at the first glance, but under the hood it always resolvesEqualityComparer<T>.Default
since the conditiontypeof(TKey).IsValueType
is always satisfied and the_comparer
field isn't set. That resolution happens inside of devirtualizedGetHashCode
andEquals
methods ofStronglyTypedKey<T>
.Proposals
It is neither a
Dictionary<TKey, TValue>
's fault, nor the runtime's. It's just an edge case, but which can e correctly handled anyway.I see two options:
The JIT should avoid resolution of
EqualityComparer<T>.Default
to match a devirtualized code, and useT
instead since there's only one comparer forT
anyway and it never changes during the execution. And only if no devirtualized block exists forT
, fallback toEqualityComparer<T>.Default
.Add a method to the
RuntimeHelpers
type which will return if theT
is a shared generic or not, and then use it instead oftypeof(T).IsValueType
for hash-based collections.The text was updated successfully, but these errors were encountered: