|
1 | 1 | ## Highlights
|
2 | 2 |
|
3 |
| -TODO |
| 3 | +* **.NET 5 support** |
| 4 | + As you probably, now, .NET Core 5 was officially [rebranded](https://github.com/dotnet/runtime/pull/33694) to .NET 5. |
| 5 | + The new version of BenchmarkDotNet supports the new runtime after rebranding. |
| 6 | + [#1399](https://github.com/dotnet/BenchmarkDotNet/pull/1399) |
| 7 | + [465ebf](https://github.com/dotnet/BenchmarkDotNet/commit/465ebf3fdbf20f0e9219c4c957fb33c13256fdcd) |
| 8 | +* **Perfolizer adoption** |
| 9 | + The internal statistical engine of BenchmarkDotNet became mature enough to be transformed into an independent project. |
| 10 | + Meet [perfolizer](https://github.com/AndreyAkinshin/perfolizer) — a toolkit for performance analysis! |
| 11 | + While BenchmarkDotNet focuses on obtaining reliable measurements, perfolizer focuses on the decent analysis of measured data. |
| 12 | + You still can use all the statistical algorithms from BenchmarkDotNet, |
| 13 | + but you can also install perfolizer as a [standalone NuGet package](https://www.nuget.org/packages/Perfolizer/). |
| 14 | + You can find more details in the [official announcement](https://aakinshin.net/posts/introducing-perfolizer/). |
| 15 | + [#1386](https://github.com/dotnet/BenchmarkDotNet/pull/1386) |
| 16 | + [54a061](https://github.com/dotnet/BenchmarkDotNet/commit/54a06102a6e0cc4169d23c6f9cd2779ee408d2bf) |
| 17 | +* **Cross-platform disassembler** |
| 18 | + Now the `DisassemblyDiagnoser` is cross-platform! |
| 19 | + The disassembling logic was also improved, now it handles runtime helper methods and references to method tables properly. |
| 20 | + Internally, it uses the [Iced](https://github.com/0xd4d/iced) library for formatting assembly code. |
| 21 | + Special thanks to [@adamsitnik](https://github.com/adamsitnik) for the implementation and [@0xd4d](https://github.com/0xd4d) for Iced! |
| 22 | + [#1332](https://github.com/dotnet/BenchmarkDotNet/pull/1332) |
| 23 | + [#899](https://github.com/dotnet/BenchmarkDotNet/issues/899) |
| 24 | + [#1316](https://github.com/dotnet/BenchmarkDotNet/issues/1316) |
| 25 | + [#1364](https://github.com/dotnet/BenchmarkDotNet/issues/1364) |
| 26 | + [294320](https://github.com/dotnet/BenchmarkDotNet/commit/294320be9525b0ecfefd0351381756d3a3b77211) |
| 27 | +* **EventPipe-based cross-platform profiler** |
| 28 | + Now you can easily profiler your benchmarks on Windows, Linux, and macOS! |
| 29 | + Just mark your class with the `[EventPipeProfiler(...)]` attribute and get a `.speedscope.json` file that you can browse in [SpeedScope](https://www.speedscope.app/). |
| 30 | + Special thanks to [@WojciechNagorski](https://github.com/WojciechNagorski) for the implementation! |
| 31 | + [#1321](https://github.com/dotnet/BenchmarkDotNet/pull/1321) |
| 32 | + [#1315](https://github.com/dotnet/BenchmarkDotNet/issues/1315) |
| 33 | + [c648ff](https://github.com/dotnet/BenchmarkDotNet/commit/c648ff956662abae512c579ffa7f1dc12178f6c3) |
| 34 | +* **New fluent API** |
| 35 | + We continue to improve our API and make it easier for reading and writing. |
| 36 | + Special thanks to [@WojciechNagorski](https://github.com/WojciechNagorski) for the implementation! |
| 37 | + [#1273](https://github.com/dotnet/BenchmarkDotNet/pull/1273) |
| 38 | + [#1234](https://github.com/dotnet/BenchmarkDotNet/issues/1234) |
| 39 | + [640d88](https://github.com/dotnet/BenchmarkDotNet/commit/640d885ae0daddcee7c9ba9b5f1bf5790b9b5ae3) |
| 40 | +* **Ref readonly support** |
| 41 | + Now you can use `ref readonly` in benchmark signatures. |
| 42 | + Special thanks to [@adamsitnik](https://github.com/adamsitnik) for the implementation! |
| 43 | + [#1389](https://github.com/dotnet/BenchmarkDotNet/pull/1389) |
| 44 | + [#1388](https://github.com/dotnet/BenchmarkDotNet/issues/1388) |
| 45 | + [9ac777](https://github.com/dotnet/BenchmarkDotNet/commit/9ac7770682a45afb6cf4ec353f9fa3c69ece67ce) |
| 46 | + |
| 47 | +## Cross-platform disassembler |
| 48 | + |
| 49 | +Just mark your benchmark class with the `[DisassemblyDiagnoser]` attribute |
| 50 | + and you will get the disassembly listings for all the benchmarks. |
| 51 | +The formatting looks pretty nice thanks to [Iced](https://github.com/0xd4d/iced). |
| 52 | +It works like a charm on Windows, Linux, and macOS. |
| 53 | + |
| 54 | +```cs |
| 55 | +[DisassemblyDiagnoser] |
| 56 | +public class IntroDisassembly |
| 57 | +{ |
| 58 | + private int[] field = Enumerable.Range(0, 100).ToArray(); |
| 59 | + |
| 60 | + [Benchmark] |
| 61 | + public int SumLocal() |
| 62 | + { |
| 63 | + var local = field; // we use local variable that points to the field |
| 64 | +
|
| 65 | + int sum = 0; |
| 66 | + for (int i = 0; i < local.Length; i++) |
| 67 | + sum += local[i]; |
| 68 | + |
| 69 | + return sum; |
| 70 | + } |
| 71 | + |
| 72 | + [Benchmark] |
| 73 | + public int SumField() |
| 74 | + { |
| 75 | + int sum = 0; |
| 76 | + for (int i = 0; i < field.Length; i++) |
| 77 | + sum += field[i]; |
| 78 | + |
| 79 | + return sum; |
| 80 | + } |
| 81 | +} |
| 82 | +``` |
| 83 | + |
| 84 | +**.NET Core 2.1.16 (CoreCLR 4.6.28516.03, CoreFX 4.6.28516.10), X64 RyuJIT** |
| 85 | + |
| 86 | +```x86asm |
| 87 | +; BenchmarkDotNet.Samples.IntroDisassembly.SumLocal() |
| 88 | + mov rax,[rcx+8] |
| 89 | + xor edx,edx |
| 90 | + xor ecx,ecx |
| 91 | + mov r8d,[rax+8] |
| 92 | + test r8d,r8d |
| 93 | + jle short M00_L01 |
| 94 | +M00_L00: |
| 95 | + movsxd r9,ecx |
| 96 | + add edx,[rax+r9*4+10] |
| 97 | + inc ecx |
| 98 | + cmp r8d,ecx |
| 99 | + jg short M00_L00 |
| 100 | +M00_L01: |
| 101 | + mov eax,edx |
| 102 | + ret |
| 103 | +; Total bytes of code 35 |
| 104 | +``` |
| 105 | + |
| 106 | +**.NET Core 2.1.16 (CoreCLR 4.6.28516.03, CoreFX 4.6.28516.10), X64 RyuJIT** |
| 107 | + |
| 108 | +```x86asm |
| 109 | +; BenchmarkDotNet.Samples.IntroDisassembly.SumField() |
| 110 | + sub rsp,28 |
| 111 | + xor eax,eax |
| 112 | + xor edx,edx |
| 113 | + mov rcx,[rcx+8] |
| 114 | + cmp dword ptr [rcx+8],0 |
| 115 | + jle short M00_L01 |
| 116 | +M00_L00: |
| 117 | + mov r8,rcx |
| 118 | + cmp edx,[r8+8] |
| 119 | + jae short M00_L02 |
| 120 | + movsxd r9,edx |
| 121 | + add eax,[r8+r9*4+10] |
| 122 | + inc edx |
| 123 | + cmp [rcx+8],edx |
| 124 | + jg short M00_L00 |
| 125 | +M00_L01: |
| 126 | + add rsp,28 |
| 127 | + ret |
| 128 | +M00_L02: |
| 129 | + call CORINFO_HELP_RNGCHKFAIL |
| 130 | + int 3 |
| 131 | +; Total bytes of code 53 |
| 132 | +``` |
| 133 | + |
| 134 | +Now we handle runtime helper methods and references to method tables properly. Example: |
| 135 | + |
| 136 | +Before: |
| 137 | + |
| 138 | +```x86asm |
| 139 | +; MicroBenchmarks.WithCallsAfter.Benchmark(Int32) |
| 140 | + push rsi |
| 141 | + sub rsp,20h |
| 142 | + mov rsi,rcx |
| 143 | + cmp edx,7FFFFFFFh |
| 144 | + jne M00_L00 |
| 145 | + call MicroBenchmarks.WithCallsAfter.Static() |
| 146 | + mov rcx,rsi |
| 147 | + call MicroBenchmarks.WithCallsAfter.Instance() |
| 148 | + mov rcx,rsi |
| 149 | + call MicroBenchmarks.WithCallsAfter.Recursive() |
| 150 | + mov rcx,rsi |
| 151 | + mov rax,qword ptr [rsi] |
| 152 | + mov rax,qword ptr [rax+40h] |
| 153 | + call qword ptr [rax+20h] |
| 154 | + mov rcx,rsi |
| 155 | + mov edx,1 |
| 156 | + mov rax,7FF8F4217050h |
| 157 | + add rsp,20h |
| 158 | + pop rsi |
| 159 | + jmp rax |
| 160 | +M00_L00: |
| 161 | + mov rcx,offset System_Private_CoreLib+0xa31d48 |
| 162 | + call coreclr!MetaDataGetDispenser+0x322a0 |
| 163 | + mov rsi,rax |
| 164 | + mov ecx,0ACFAh |
| 165 | + mov rdx,7FF8F42F4680h |
| 166 | + call coreclr!MetaDataGetDispenser+0x17140 |
| 167 | + mov rdx,rax |
| 168 | + mov rcx,rsi |
| 169 | + call System.InvalidOperationException..ctor(System.String) |
| 170 | + mov rcx,rsi |
| 171 | + call coreclr!coreclr_shutdown_2+0x39f0 |
| 172 | + int 3 |
| 173 | + add byte ptr [rax],al |
| 174 | + sbb dword ptr [00007ff9`26284e30],eax |
| 175 | + add dword ptr [rax+40h],esp |
| 176 | + add byte ptr [rax],al |
| 177 | + add byte ptr [rax],al |
| 178 | + add byte ptr [rax],al |
| 179 | + add byte ptr [rax-70BC4CCh],ah |
| 180 | +; Total bytes of code 157 |
| 181 | +``` |
| 182 | + |
| 183 | +After: |
| 184 | + |
| 185 | +```x86asm |
| 186 | +; BenchmarkDotNet.Samples.WithCallsAfter.Benchmark(Int32) |
| 187 | + push rsi |
| 188 | + sub rsp,20 |
| 189 | + mov rsi,rcx |
| 190 | + cmp edx,7FFFFFFF |
| 191 | + jne M00_L00 |
| 192 | + call BenchmarkDotNet.Samples.WithCallsAfter.Static() |
| 193 | + mov rcx,rsi |
| 194 | + call BenchmarkDotNet.Samples.WithCallsAfter.Instance() |
| 195 | + mov rcx,rsi |
| 196 | + call BenchmarkDotNet.Samples.WithCallsAfter.Recursive() |
| 197 | + mov rcx,rsi |
| 198 | + mov rax,[rsi] |
| 199 | + mov rax,[rax+40] |
| 200 | + call qword ptr [rax+20] |
| 201 | + mov rcx,rsi |
| 202 | + mov edx,1 |
| 203 | + mov rax BenchmarkDotNet.Samples.WithCallsAfter.Benchmark(Boolean) |
| 204 | + add rsp,20 |
| 205 | + pop rsi |
| 206 | + jmp rax |
| 207 | +M00_L00: |
| 208 | + mov rcx MT_System.InvalidOperationException |
| 209 | + call CORINFO_HELP_NEWSFAST |
| 210 | + mov rsi,rax |
| 211 | + mov ecx,12D |
| 212 | + mov rdx,7FF954FF83F0 |
| 213 | + call CORINFO_HELP_STRCNS |
| 214 | + mov rdx,rax |
| 215 | + mov rcx,rsi |
| 216 | + call System.InvalidOperationException..ctor(System.String) |
| 217 | + mov rcx,rsi |
| 218 | + call CORINFO_HELP_THROW |
| 219 | + int 3 |
| 220 | +; Total bytes of code 134 |
| 221 | +``` |
| 222 | + |
| 223 | +See also: [Cross-runtime .NET disassembly with BenchmarkDotNet](https://aakinshin.net/posts/dotnet-crossruntime-disasm/). |
| 224 | + |
| 225 | +Special thanks to [@adamsitnik](https://github.com/adamsitnik) for the implementation and [@0xd4d](https://github.com/0xd4d) for Iced! |
| 226 | + |
| 227 | +## EventPipe-based cross-platform profiler |
| 228 | + |
| 229 | +Now you can easily profiler your benchmarks on Windows, Linux, and macOS! |
| 230 | + |
| 231 | +If you want to use the new profiler, you should just mark your benchmark class with the `[EventPipeProfiler(...)]` attribute: |
| 232 | + |
| 233 | +``` |
| 234 | +[EventPipeProfiler(EventPipeProfile.CpuSampling)] // <-- Enables new profiler |
| 235 | +public class IntroEventPipeProfiler |
| 236 | +{ |
| 237 | + [Benchmark] |
| 238 | + public void Sleep() => Thread.Sleep(2000); |
| 239 | +} |
| 240 | +``` |
| 241 | + |
| 242 | +Once the benchmark run is finished, you get a `.speedscope.json` file that can be opened in [SpeedScope](https://www.speedscope.app/): |
| 243 | + |
| 244 | + |
| 245 | + |
| 246 | +The new profiler supports several modes: |
| 247 | + |
| 248 | +* `CpuSampling` - Useful for tracking CPU usage and general .NET runtime information. This is the default option. |
| 249 | +* `GcVerbose` - Tracks GC collections and samples object allocations. |
| 250 | +* `GcCollect` - Tracks GC collections only at very low overhead. |
| 251 | +* `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) |
| 252 | + |
| 253 | +Please see Wojciech Nagórski's [blog post](https://wojciechnagorski.com/2020/04/cross-platform-profiling-.net-code-with-benchmarkdotnet/) for all the details. |
| 254 | + |
| 255 | +Special thanks to [@WojciechNagorski](https://github.com/WojciechNagorski) for the implementation! |
| 256 | + |
| 257 | +## New fluent API |
| 258 | + |
| 259 | +We continue to improve our API and make it easier for reading and writing. |
| 260 | +The old API is still existing, but it is marked as obsolete and will be removed in the further library versions. |
| 261 | +The most significant changes: |
| 262 | + |
| 263 | +**Changes in Job configuration** |
| 264 | + |
| 265 | + |
| 266 | + |
| 267 | +**Changes in IConfig/ManualConfig** |
| 268 | + |
| 269 | + |
| 270 | + |
| 271 | +**Full fluent API** |
| 272 | + |
| 273 | + |
| 274 | + |
| 275 | +Special thanks to [@WojciechNagorski](https://github.com/WojciechNagorski) for the implementation! |
| 276 | + |
| 277 | +## Ref readonly support |
| 278 | + |
| 279 | +Now you can use `ref readonly` in benchmark signatures. |
| 280 | +Here is an example: |
| 281 | + |
| 282 | +```cs |
| 283 | +public class RefReadonlyBenchmark |
| 284 | +{ |
| 285 | + static readonly int[] array = { 1 }; |
| 286 | + |
| 287 | + [Benchmark] |
| 288 | + public ref readonly int RefReadonly() => ref RefReadonlyMethod(); |
| 289 | + |
| 290 | + static ref readonly int RefReadonlyMethod() => ref array[0]; |
| 291 | +} |
| 292 | +``` |
| 293 | + |
| 294 | +Special thanks to [@adamsitnik](https://github.com/adamsitnik) for the implementation! |
0 commit comments