-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Closed
Labels
JitUntriagedCLR JIT issues needing additional triageCLR JIT issues needing additional triagearea-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMICLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Milestone
Description
Consider the following benchmark example:
public interface IFoo
{
object Foo();
}
public interface IFooGeneric
{
T FooGeneric<T>();
}
public sealed class Impl: IFoo, IFooGeneric
{
[MethodImpl(MethodImplOptions.NoInlining)]
public object Foo() => null;
[MethodImpl(MethodImplOptions.NoInlining)]
public T FooGeneric<T>() => default;
}
public class DevirtBenchmark
{
const int Operations = 100000;
static Impl ImplInstance;
static IFoo IFooInstance;
static IFooGeneric IFooGenericInstance;
[GlobalSetup(Target = "ImplFooGeneric,IFooViaImpl,IFooGenericViaImpl")]
public void SetupImpl() => ImplInstance = new Impl();
[GlobalSetup(Target = "IFoo")]
public void SetupIFoo() => IFooInstance = new Impl();
[GlobalSetup(Target = "IFooGeneric")]
public void SetupIFooGenric() => IFooGenericInstance = new Impl();
[Benchmark(OperationsPerInvoke = Operations)]
public void ImplFooGeneric()
{
for (int i = 0; i < Operations; i++)
{
ImplInstance.FooGeneric<object>();
}
}
[Benchmark(OperationsPerInvoke = Operations)]
public void IFoo()
{
for (int i = 0; i < Operations; i++)
{
IFooInstance.Foo();
}
}
[Benchmark(OperationsPerInvoke = Operations)]
public void IFooViaImpl()
{
for (int i = 0; i < Operations; i++)
{
((IFoo)ImplInstance).Foo();
}
}
[Benchmark(OperationsPerInvoke = Operations)]
public void IFooGeneric()
{
for (int i = 0; i < Operations; i++)
{
IFooGenericInstance.FooGeneric<object>();
}
}
[Benchmark(OperationsPerInvoke = Operations)]
public void IFooGenericViaImpl()
{
for (int i = 0; i < Operations; i++)
{
((IFooGeneric)ImplInstance).FooGeneric<object>();
}
}
}
Results show the JIT can succesfully devirtualize IFooViaImpl
by observing the concrete type before the cast. Yet it fails to do so for IFooGenericViaImpl
while those - extremely slow calls - would benefit most from this working correctly.
// * Summary *
BenchmarkDotNet=v0.12.0, OS=macOS Mojave 10.14.6 (18G3020) [Darwin 18.7.0]
Intel Core i7-4980HQ CPU 2.80GHz (Haswell), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.101
[Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT DEBUG
DefaultJob : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
| Method | Mean | Error | StdDev |
|------------------- |---------:|----------:|----------:|
| ImplFooGeneric | 1.818 ns | 0.0081 ns | 0.0072 ns |
| IFoo | 2.360 ns | 0.0354 ns | 0.0331 ns |
| IFooViaImpl | 1.546 ns | 0.0056 ns | 0.0052 ns |
| IFooGeneric | 7.758 ns | 0.0605 ns | 0.0566 ns |
| IFooGenericViaImpl | 7.739 ns | 0.0242 ns | 0.0227 ns |
/cc @AndyAyersMS
EDIT: I have added a direct generic call for scale
What I also noticed is that the class itself needs to be sealed
as well for IFooViaImpl
to devirtualize, yet that method is already final
in IL, this seems like a rather small fix.
category:cq
theme:devirtualization
skill-level:expert
cost:large
Metadata
Metadata
Assignees
Labels
JitUntriagedCLR JIT issues needing additional triageCLR JIT issues needing additional triagearea-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMICLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI