Generate disassembly of .NET functions
In this post you will learn how to generate the disassembly of .NET functions and how to diff many of them.
It was not so long ago when I added a new feature to BenchmarkDotNet that allows you to generate a diff raport for many dissassembly of .NET function. You can see all the changes in following PRs: #927, #937 and #1022.
In this post I will be using the benchmark code from PR dotnet/coreclr#13626 to CoreCLR repo. In metioned PR @mikedn enables JIT to generate more efficient BT instruction in some situations. It is great example to show my exporter in action.
Generate disassembly
In BenchmarkDotNet you can easily generate disassembler for .NET function. All you need is add [DisassemblyDiagnoser]
attribute to your benchmark, like below:
[DisassemblyDiagnoser]
public class IntroDisassembly
{
[Benchmark(OperationsPerInvoke = 10_000_000)]
public int LoweringTESTtoBT()
{
int y = 0, x = 0;
while (x++ < 10_000_000)
if ((x & (1 << y)) == 0)
y++;
return y;
}
}
You can also do this with the -d
or --disasm
command line arguments.
Running this benchmark will create lot of reports in directoryBenchmarkDotNet.Artifacts\results
. Among them is ProjectName.IntroDisassembly-asm.pretty.md
that looks like:
.NET Core 2.1.6 (CoreCLR 4.6.27019.06, CoreFX 4.6.27019.05), 64bit RyuJIT
; BenchmarkDotNet.Samples.IntroDisassemblyCode.LoweringTESTtoBT()
xor eax,eax
xor edx,edx
jmp M00_L01
M00_L00:
bt ecx,eax
mov edx,ecx
jb M00_L01
inc eax
M00_L01:
lea ecx,[rdx+1]
cmp edx,989680h
jl M00_L00
ret
; Total bytes of code 27
You can use this feature to understand why one solution is better then other or you can learn about how C# code which you wrote works on your computer.
Nothing new so far ;)
Generates diff of two disassembly
Sometimes your code works fast on newer version of .NET Core and works slow on the older one. That is quite common case, because .NET Core is improving very fast. In other case you probably would see performance regression and create issue to CoreCLR repo.
If you want to generate disassembly of function for multiple .NET versions, just configure your test, e. g.:
[Config(typeof(MultipleJits))]
public class IntroDisassembly
{
[Benchmark(OperationsPerInvoke = 10_000_000)]
public int LoweringTESTtoBT()
{
int y = 0, x = 0;
while (x++ < 10_000_000)
if ((x & (1 << y)) == 0)
y++;
return y;
}
}
public class MultipleJits : ManualConfig
{
public MultipleJits()
{
// .NET core 2.0
Add(Job.ShortRun.With(Platform.X64).With(CsProjCoreToolchain.NetCoreApp20));
// .NET core 2.1
Add(Job.ShortRun.With(Platform.X64).With(CsProjCoreToolchain.NetCoreApp21));
// Add disassembly diagnoser with printDiff = true
Add(DisassemblyDiagnoser.Create(new DisassemblyDiagnoserConfig(printAsm: true, printPrologAndEpilog: false, recursiveDepth: 3, printDiff: true)));
}
}
In this example I added two jobs. One for .NET Core 2.0 and other for .NET Core 2.1. I added also DisassemblyDiagnoser
with printDiff
option set to true
.
After benchmark run, you can see that @mikedn’s optimization made this code much faster on .NET Core 2.1:
Method | Toolchain | Mean | Error | StdDev |
----------------- |-------------- |----------:|----------:|----------:|
LoweringTESTtoBT | .NET Core 2.0 | 2.3164 ns | 0.8837 ns | 0.0484 ns |
LoweringTESTtoBT | .NET Core 2.1 | 0.6531 ns | 0.2882 ns | 0.0158 ns |
In directory BenchmarkDotNet.Artifacts\results
you will see also ProjectName.IntroDisassembly-asm.pretty.diff.md
that looks:
BenchmarkDotNet.Samples.IntroDisassemblyCode Diff for LoweringTESTtoBT method between: .NET Core 2.0.9 (CoreCLR 4.6.26614.01, CoreFX 4.6.26614.01), 64bit RyuJIT .NET Core 2.1.6 (CoreCLR 4.6.27019.06, CoreFX 4.6.27019.05), 64bit RyuJIT
; BenchmarkDotNet.Samples.IntroDisassemblyCode.LoweringTESTtoBT()
xor eax,eax
xor edx,edx
jmp M00_L01
M00_L00:
- mov ecx,eax
- and ecx,1Fh
- mov edx,1
- shl edx,cl
- mov ecx,dword ptr [rsp+4]
- test edx,ecx
+ bt ecx,eax
mov edx,ecx
- jne M00_L01
+ jb M00_L01
inc eax
M00_L01:
lea ecx,[rdx+1]
- mov dword ptr [rsp+4],ecx
cmp edx,989680h
jl M00_L00
- add rsp,8
-; Total bytes of code 49
+ ret
+; Total bytes of code 27
Limitations
Please note that this exporter internally uses the git diff
command, so it requires a GIT installed on the system.
Summary
Thanks to this report you can easily see what has been changed in the assembly code on various versions of .NET runtimes. You can also copy and paste it to GitHub.