BenchmarkDotNet icon indicating copy to clipboard operation
BenchmarkDotNet copied to clipboard

DisassemblyDiagnoser does not show asm of called delegate

Open dadhi opened this issue 7 years ago • 2 comments

Here is simplified benchmark on .NET Core, but I have similar results on Full CLR:

    [DisassemblyDiagnoser(recursiveDepth: 100)]
    public class CallDelegate
    {
        private static readonly Func<string> _showTime = () => "Now is: " + DateTime.Now;

        [Benchmark(Baseline = true)]
        public object Direct() => "Now is: " + DateTime.Now;

        [Benchmark]
        public object CallFunc() => _showTime();
    }

Here is the results:

image

Here is the asm CallDelegate_CallFunc_DefaultJob-asm.raw.html


00007ffb`7f260e10 FastExpressionCompiler.Benchmarks.Program+CallDelegate.CallFunc()
00007ffb`7f260e14 48b928162d7ffb7f0000 mov rcx,7FFB7F2D1628h 
00007ffb`7f260e1e ba2d000000      mov     edx,2Dh
00007ffb`7f260e23 e82846ad5f      call    coreclr!MetaDataGetDispenser+0x73970 (00007ffb`ded35450) not managed method
00007ffb`7f260e28 48b9586d45725e020000 mov rcx,25E72456D58h  
00007ffb`7f260e32 488b01          mov     rax,qword ptr [rcx]  
00007ffb`7f260e35 488b4808        mov     rcx,qword ptr [rax+8]  
00007ffb`7f260e39 488b4018        mov     rax,qword ptr [rax+18h]

The asm for Direct benchmark is a very long (recursiveDepth: 100)

BTW, My original benchmark target was https://github.com/dadhi/FastExpressionCompiler/blob/master/test/FastExpressionCompiler.Benchmarks/SimpleExpr_ParamPlusParam.cs

dadhi avatar Mar 03 '18 11:03 dadhi

I've taken a look at this today and I don't have any good news.

With the most recent changes, the following benchmark for .NET Core 2.1:

public class CallDelegate
{
    private static readonly Func<string> _showTime = () => "Now is: " + DateTime.Now;

    [Benchmark]
    public object CallFunc() => _showTime();
}

disassembles to:

## .NET Core 2.1.14 (CoreCLR 4.6.28207.04, CoreFX 4.6.28208.01), X64 RyuJIT
```assembly
; BenchmarkDotNet.Samples.CallDelegate.CallFunc()
       sub       rsp,28
       mov       rcx,7FF8472B4FF0
       mov       edx,16
       call      CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE 
       mov       rcx,13CEDAF74F8
       mov       rax,[rcx]
       lea       rcx,[rax+8]
       mov       rcx,[rcx]
       mov       rax,[rax+18]
       add       rsp,28
       jmp       rax
; Total bytes of code 55

In IL the call to a delegate is represented as a virtual call which is the most indirect call we can get ;/:

.method public hidebysig 
	instance object CallFunc () cil managed 
{
	.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor() = (
		01 00 00 00
	)
	// Method begins at RVA 0x2187
	// Code size 11 (0xb)
	.maxstack 8

	// return _showTime();
	IL_0000: ldsfld class [System.Runtime]System.Func`1<string> TryNewDisassembler.CallDelegate::_showTime
	IL_0005: callvirt instance !0 class [System.Runtime]System.Func`1<string>::Invoke()
	// (no C# code)
	IL_000a: ret
} // end of method CallDelegate::CallFunc

@0xd4d @leculver do you have any suggestions about how we could disassemble the delegate calls?

adamsitnik avatar Jan 10 '20 16:01 adamsitnik

@janvorli do you have any suggestion for disassembling delegate calls? Sample:

.NET 6.0.9 (6.0.922.41905), X64 RyuJIT AVX2

; BenchmarkDotNet.Samples.CallDelegate.CallFunc()
       push      rbp
       sub       rsp,30
       lea       rbp,[rsp+30]
       xor       eax,eax
       mov       [rbp-8],rax
       mov       [rbp+10],rcx
       mov       rcx,7FFAA1F42258
       mov       edx,50
       call      CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE
       mov       rcx,1A118007330
       mov       rcx,[rcx]
       mov       [rbp-8],rcx
       mov       rcx,[rbp-8]
       mov       rcx,[rcx+8]
       mov       rax,[rbp-8]
       call      qword ptr [rax+18]
       nop
       add       rsp,30
       pop       rbp
       ret
; Total bytes of code 79

adamsitnik avatar Oct 05 '22 11:10 adamsitnik