## Highlights

* **.NET 5 support**  
  As you probably know, .NET Core 5 was officially [rebranded](https://github.com/dotnet/runtime/pull/33694) to .NET 5.
  The new version of BenchmarkDotNet supports the new runtime after rebranding.  
  [#1399](https://github.com/dotnet/BenchmarkDotNet/pull/1399)
  [465ebf](https://github.com/dotnet/BenchmarkDotNet/commit/465ebf3fdbf20f0e9219c4c957fb33c13256fdcd)
* **Perfolizer adoption**  
  The internal statistical engine of BenchmarkDotNet became mature enough to be transformed into an independent project.
  Meet [perfolizer](https://github.com/AndreyAkinshin/perfolizer) — a toolkit for performance analysis!
  While BenchmarkDotNet focuses on obtaining reliable measurements, perfolizer focuses on the decent analysis of measured data.
  You still can use all the statistical algorithms from BenchmarkDotNet,
    but you can also install perfolizer as a [standalone NuGet package](https://www.nuget.org/packages/Perfolizer/).
  You can find more details in the [official announcement](https://aakinshin.net/posts/introducing-perfolizer/).  
  [#1386](https://github.com/dotnet/BenchmarkDotNet/pull/1386)
  [54a061](https://github.com/dotnet/BenchmarkDotNet/commit/54a06102a6e0cc4169d23c6f9cd2779ee408d2bf)
* **Cross-platform disassembler**  
  Now the `DisassemblyDiagnoser` is cross-platform!
  The disassembling logic was also improved, now it handles runtime helper methods and references to method tables properly.
  Internally, it uses the [Iced](https://github.com/0xd4d/iced) library for formatting assembly code.  
  Special thanks to [@adamsitnik](https://github.com/adamsitnik) for the implementation and [@0xd4d](https://github.com/0xd4d) for Iced!  
  [#1332](https://github.com/dotnet/BenchmarkDotNet/pull/1332)
  [#899](https://github.com/dotnet/BenchmarkDotNet/issues/899)
  [#1316](https://github.com/dotnet/BenchmarkDotNet/issues/1316)
  [#1364](https://github.com/dotnet/BenchmarkDotNet/issues/1364)
  [294320](https://github.com/dotnet/BenchmarkDotNet/commit/294320be9525b0ecfefd0351381756d3a3b77211)
* **EventPipe-based cross-platform profiler**  
  Now you can easily profiler your benchmarks on Windows, Linux, and macOS!
  Just mark your class with the `[EventPipeProfiler(...)]` attribute and get a `.speedscope.json` file that you can browse in [SpeedScope](https://www.speedscope.app/).  
  Special thanks to [@WojciechNagorski](https://github.com/WojciechNagorski) for the implementation!  
  [#1321](https://github.com/dotnet/BenchmarkDotNet/pull/1321)
  [#1315](https://github.com/dotnet/BenchmarkDotNet/issues/1315)
  [c648ff](https://github.com/dotnet/BenchmarkDotNet/commit/c648ff956662abae512c579ffa7f1dc12178f6c3)
* **New fluent API**  
  We continue to improve our API and make it easier for reading and writing.  
  Special thanks to [@WojciechNagorski](https://github.com/WojciechNagorski) for the implementation!  
  [#1273](https://github.com/dotnet/BenchmarkDotNet/pull/1273)
  [#1234](https://github.com/dotnet/BenchmarkDotNet/issues/1234)
  [640d88](https://github.com/dotnet/BenchmarkDotNet/commit/640d885ae0daddcee7c9ba9b5f1bf5790b9b5ae3)
* **Ref readonly support**  
  Now you can use `ref readonly` in benchmark signatures.  
  Special thanks to [@adamsitnik](https://github.com/adamsitnik) for the implementation!  
  [#1389](https://github.com/dotnet/BenchmarkDotNet/pull/1389)
  [#1388](https://github.com/dotnet/BenchmarkDotNet/issues/1388)
  [9ac777](https://github.com/dotnet/BenchmarkDotNet/commit/9ac7770682a45afb6cf4ec353f9fa3c69ece67ce)

## Cross-platform disassembler

Just mark your benchmark class with the `[DisassemblyDiagnoser]` attribute
  and you will get the disassembly listings for all the benchmarks.
The formatting looks pretty nice thanks to [Iced](https://github.com/0xd4d/iced).
It works like a charm on Windows, Linux, and macOS.

```cs
[DisassemblyDiagnoser]
public class IntroDisassembly
{
    private int[] field = Enumerable.Range(0, 100).ToArray();

    [Benchmark]
    public int SumLocal()
    {
        var local = field; // we use local variable that points to the field

        int sum = 0;
        for (int i = 0; i < local.Length; i++)
            sum += local[i];

        return sum;
    }

    [Benchmark]
    public int SumField()
    {
        int sum = 0;
        for (int i = 0; i < field.Length; i++)
            sum += field[i];

        return sum;
    }
}
```

**.NET Core 2.1.16 (CoreCLR 4.6.28516.03, CoreFX 4.6.28516.10), X64 RyuJIT**

```x86asm
; BenchmarkDotNet.Samples.IntroDisassembly.SumLocal()
       mov       rax,[rcx+8]
       xor       edx,edx
       xor       ecx,ecx
       mov       r8d,[rax+8]
       test      r8d,r8d
       jle       short M00_L01
M00_L00:
       movsxd    r9,ecx
       add       edx,[rax+r9*4+10]
       inc       ecx
       cmp       r8d,ecx
       jg        short M00_L00
M00_L01:
       mov       eax,edx
       ret
; Total bytes of code 35
```

**.NET Core 2.1.16 (CoreCLR 4.6.28516.03, CoreFX 4.6.28516.10), X64 RyuJIT**

```x86asm
; BenchmarkDotNet.Samples.IntroDisassembly.SumField()
       sub       rsp,28
       xor       eax,eax
       xor       edx,edx
       mov       rcx,[rcx+8]
       cmp       dword ptr [rcx+8],0
       jle       short M00_L01
M00_L00:
       mov       r8,rcx
       cmp       edx,[r8+8]
       jae       short M00_L02
       movsxd    r9,edx
       add       eax,[r8+r9*4+10]
       inc       edx
       cmp       [rcx+8],edx
       jg        short M00_L00
M00_L01:
       add       rsp,28
       ret
M00_L02:
       call      CORINFO_HELP_RNGCHKFAIL
       int       3
; Total bytes of code 53
```

Now we handle runtime helper methods and references to method tables properly. Example:

Before:

```x86asm
; MicroBenchmarks.WithCallsAfter.Benchmark(Int32)
       push    rsi
       sub     rsp,20h
       mov     rsi,rcx
       cmp     edx,7FFFFFFFh
       jne     M00_L00
       call    MicroBenchmarks.WithCallsAfter.Static()
       mov     rcx,rsi
       call    MicroBenchmarks.WithCallsAfter.Instance()
       mov     rcx,rsi
       call    MicroBenchmarks.WithCallsAfter.Recursive()
       mov     rcx,rsi
       mov     rax,qword ptr [rsi]
       mov     rax,qword ptr [rax+40h]
       call    qword ptr [rax+20h]
       mov     rcx,rsi
       mov     edx,1
       mov     rax,7FF8F4217050h
       add     rsp,20h
       pop     rsi
       jmp     rax
M00_L00:
       mov     rcx,offset System_Private_CoreLib+0xa31d48
       call    coreclr!MetaDataGetDispenser+0x322a0
       mov     rsi,rax
       mov     ecx,0ACFAh
       mov     rdx,7FF8F42F4680h
       call    coreclr!MetaDataGetDispenser+0x17140
       mov     rdx,rax
       mov     rcx,rsi
       call    System.InvalidOperationException..ctor(System.String)
       mov     rcx,rsi
       call    coreclr!coreclr_shutdown_2+0x39f0
       int     3
       add     byte ptr [rax],al
       sbb     dword ptr [00007ff9`26284e30],eax
       add     dword ptr [rax+40h],esp
       add     byte ptr [rax],al
       add     byte ptr [rax],al
       add     byte ptr [rax],al
       add     byte ptr [rax-70BC4CCh],ah
; Total bytes of code 157
```

After:

```x86asm
; BenchmarkDotNet.Samples.WithCallsAfter.Benchmark(Int32)
       push      rsi
       sub       rsp,20
       mov       rsi,rcx
       cmp       edx,7FFFFFFF
       jne       M00_L00 
       call      BenchmarkDotNet.Samples.WithCallsAfter.Static() 
       mov       rcx,rsi
       call      BenchmarkDotNet.Samples.WithCallsAfter.Instance() 
       mov       rcx,rsi
       call      BenchmarkDotNet.Samples.WithCallsAfter.Recursive() 
       mov       rcx,rsi
       mov       rax,[rsi]
       mov       rax,[rax+40]
       call      qword ptr [rax+20]
       mov       rcx,rsi
       mov       edx,1
       mov       rax BenchmarkDotNet.Samples.WithCallsAfter.Benchmark(Boolean) 
       add       rsp,20
       pop       rsi
       jmp       rax
M00_L00:
       mov       rcx MT_System.InvalidOperationException 
       call      CORINFO_HELP_NEWSFAST 
       mov       rsi,rax
       mov       ecx,12D
       mov       rdx,7FF954FF83F0
       call      CORINFO_HELP_STRCNS 
       mov       rdx,rax
       mov       rcx,rsi
       call      System.InvalidOperationException..ctor(System.String) 
       mov       rcx,rsi
       call      CORINFO_HELP_THROW 
       int       3
; Total bytes of code 134
```

See also: [Cross-runtime .NET disassembly with BenchmarkDotNet](https://aakinshin.net/posts/dotnet-crossruntime-disasm/).

Special thanks to [@adamsitnik](https://github.com/adamsitnik) for the implementation and [@0xd4d](https://github.com/0xd4d) for Iced!

## EventPipe-based cross-platform profiler

Now you can easily profiler your benchmarks on Windows, Linux, and macOS!

If you want to use the new profiler, you should just mark your benchmark class with the `[EventPipeProfiler(...)]` attribute:

```
[EventPipeProfiler(EventPipeProfile.CpuSampling)] // <-- Enables new profiler
public class IntroEventPipeProfiler
{
    [Benchmark]
    public void Sleep() => Thread.Sleep(2000);
}
```

Once the benchmark run is finished, you get a `.speedscope.json` file that can be opened in [SpeedScope](https://www.speedscope.app/):

![](https://wojciechnagorski.com/images/EventPipeProfiler/SpeedScope.png#mid)

The new profiler supports several modes:

* `CpuSampling` - Useful for tracking CPU usage and general .NET runtime information. This is the default option.
* `GcVerbose` - Tracks GC collections and samples object allocations.
* `GcCollect` - Tracks GC collections only at very low overhead.
* `Jit` - Logging when Just in time (JIT) compilation occurs. Logging of the internal workings of the Just In Time compiler. This is fairly verbose. It details decisions about interesting optimization (like inlining and tail call)

Please see Wojciech Nagórski's [blog post](https://wojciechnagorski.com/2020/04/cross-platform-profiling-.net-code-with-benchmarkdotnet/) for all the details.

Special thanks to [@WojciechNagorski](https://github.com/WojciechNagorski) for the implementation!

## New fluent API

We continue to improve our API and make it easier for reading and writing.
The old API is still existing, but it is marked as obsolete and will be removed in the further library versions.
The most significant changes:

**Changes in Job configuration**

![](https://user-images.githubusercontent.com/17333903/66208963-b49af000-e6b6-11e9-87b3-1e4bb5519273.PNG)

**Changes in IConfig/ManualConfig**

![](https://user-images.githubusercontent.com/17333903/66208975-b6fd4a00-e6b6-11e9-94cd-85c73ae0ec27.PNG)

**Full fluent API**

![](https://user-images.githubusercontent.com/17333903/66208977-b8c70d80-e6b6-11e9-8e43-6dac3921c121.PNG)

Special thanks to [@WojciechNagorski](https://github.com/WojciechNagorski) for the implementation!

## Ref readonly support

Now you can use `ref readonly` in benchmark signatures.
Here is an example:

```cs
public class RefReadonlyBenchmark
{
    static readonly int[] array = { 1 };

    [Benchmark]
    public ref readonly int RefReadonly() => ref RefReadonlyMethod();

    static ref readonly int RefReadonlyMethod() => ref array[0];
}
```

Special thanks to [@adamsitnik](https://github.com/adamsitnik) for the implementation!