Skip to content

Nullable<T> interface check / dispatch is comparatively very slow #50915

@stephentoub

Description

@stephentoub

Repro:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

[MemoryDiagnoser]
[DisassemblyDiagnoser]
public class Program
{
    static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);

    private S _s = default(S);
    private C _c = new C();
    private S? _ns = (S?)default(S);

    [Benchmark] public int Struct() => CallM(_s);
    [Benchmark] public int Class() => CallM(_c);
    [Benchmark] public int Nullable() => CallM(_ns);
    [Benchmark] public int NullableSpecialized() => CallMSpecial(_ns);

    static int CallM<T>(T t)
    {
        if (t is IMyInterface)
        {
            return ((IMyInterface)t).M();
        }

        return 0;
    }

    static int CallMSpecial<T>(T? t) where T : struct
    {
        if (t.HasValue)
        {
            return CallM(t.GetValueOrDefault());
        }

        return 0;
    }
}

interface IMyInterface
{
    int M();
}

struct S : IMyInterface
{
    public int M() => 42;
}

class C : IMyInterface
{
    public int M() => 42;
}
Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated Code Size
Struct 0.4807 ns 0.0044 ns 0.0037 ns - - - - 16 B
Class 3.0842 ns 0.0045 ns 0.0038 ns - - - - 86 B
Nullable 88.6692 ns 0.5664 ns 0.4729 ns 0.0076 - - 48 B 132 B
NullableSpecialized 1.5149 ns 0.0087 ns 0.0077 ns - - - - 64 B

Is there anything we can do to make the nullable case faster and less inclined to box without needing a special code path to handle it (e.g. my CallMSpecial, which in my current actual code is another overload so that a nullable binds to it more tightly).

Here's what the codegen for the nullable case looks like:

; Program.CallM[[System.Nullable`1[[S, Benchmarks]], System.Private.CoreLib]](System.Nullable`1<S>)
       sub       rsp,28
       mov       [rsp+30],rcx
       lea       rdx,[rsp+30]
       mov       rcx,offset MT_System.Nullable`1[[S, Benchmarks]]
       call      CORINFO_HELP_BOX_NULLABLE
       mov       rdx,rax
       mov       rcx,offset MT_IMyInterface
       call      CORINFO_HELP_ISINSTANCEOFINTERFACE
       test      rax,rax
       je        short M01_L00
       lea       rdx,[rsp+30]
       mov       rcx,offset MT_System.Nullable`1[[S, Benchmarks]]
       call      CORINFO_HELP_BOX_NULLABLE
       mov       rdx,rax
       mov       rcx,offset MT_IMyInterface
       call      CORINFO_HELP_CHKCASTINTERFACE
       mov       rcx,rax
       mov       r11,7FFCDCEE05D0
       call      qword ptr [7FFCDD2705D0]
       nop
       add       rsp,28
       ret
M01_L00:
       xor       eax,eax
       add       rsp,28
       ret
; Total bytes of code 122

category:cq
theme:basic-cq
skill-level:intermediate
cost:medium
impact:small

Metadata

Metadata

Assignees

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIin-prThere is an active PR which will close this issue when it is merged

Type

No type

Projects

Status

Optimizations

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions