Skip to content

Commit f4e8de3

Browse files
adamsitnikkant2002jkotas
authored
Update NativeAOT docs, fix the support for local NativeAOT builds (#1997)
* fix local NativeAOT builds benchmarking * update the docs * Apply suggestions from code review Co-authored-by: Andrii Kurdiumov <[email protected]> Co-authored-by: Jan Kotas <[email protected]> Co-authored-by: Andrii Kurdiumov <[email protected]> Co-authored-by: Jan Kotas <[email protected]>
1 parent c165ba1 commit f4e8de3

File tree

8 files changed

+140
-115
lines changed

8 files changed

+140
-115
lines changed

docs/articles/configs/toolchains.md

Lines changed: 112 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,21 @@ Example: `dotnet run -c Release -- --coreRun "C:\Projects\corefx\bin\testhost\ne
190190

191191
## NativeAOT
192192

193-
BenchmarkDotNet supports [NativeAOT](https://github.com/dotnet/runtime/tree/main/src/coreclr/nativeaot/docs)! However, you might want to know how it works to get a better understanding of the results that you get.
193+
BenchmarkDotNet supports [NativeAOT](https://github.com/dotnet/runtime/tree/main/src/coreclr/nativeaot)! However, you might want to know how it works to get a better understanding of the results that you get.
194194

195-
* NativeAOT is a flavor of .NET Core. Which means that:
196-
* you have to target .NET Core to be able to build NativeAOT benchmarks (example: `<TargetFramework>net7.0</TargetFramework>` in the .csproj file)
197-
* you have to specify the NativeAOT runtime in an explicit way, either by using `[SimpleJob]` attribute or by using the fluent Job config API `Job.ShortRun.With(NativeAotRuntime.$version)` or console line arguments `--runtimes nativeaot7.0`
198-
* to run NativeAOT benchmark you run the app as a .NET Core/.NET process (example: `dotnet run -c Release -f net5.01`) and BenchmarkDotNet does all the NativeAOT compilation for you. If you want to check what files are generated you need to apply `[KeepBenchmarkFiles]` attribute to the class which defines benchmarks.
195+
As every AOT solution, NativeAOT has some [limitations](https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/docs/limitations.md) like limited reflection support or lack of dynamic assembly loading. Because of that, the host process (what you run from command line) is never an AOT process, but just a regular .NET process. This process (called Host process) uses reflection to read benchmarks metadata (find all `[Benchmark]` methods etc), generates a new project that references the benchmarks and compiles it using ILCompiler. Such compilation produces a native executable, which is later started by the Host process. This process (called Benchmark or Child process) performs the actual benchmarking and reports the results back to the Host process. By default BenchmarkDotNet uses the latest version of `Microsoft.DotNet.ILCompiler` to build the NativeAOT benchmark according to [this instructions](https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/docs/compiling.md).
199196

200-
By default BenchmarkDotNet uses the latest version of `Microsoft.DotNet.ILCompiler` to build the NativeAOT benchmark according to [this instructions](https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/docs/compiling.md).
197+
This is why you need to:
198+
- install [pre-requisites](https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/docs/prerequisites.md) required by NativeAOT compiler
199+
- target .NET to be able to run NativeAOT benchmarks (example: `<TargetFramework>net7.0</TargetFramework>` in the .csproj file)
200+
- run the app as a .NET process (example: `dotnet run -c Release -f net7.0`).
201+
- specify the NativeAOT runtime in an explicit way, either by using console line arguments `--runtimes nativeaot7.0` (the recommended approach), or by using`[SimpleJob]` attribute or by using the fluent Job config API `Job.ShortRun.With(NativeAotRuntime.Net70)`:
202+
203+
```cmd
204+
dotnet run -c Release -f net7.0 --runtimes nativeaot7.0
205+
```
206+
207+
or:
201208

202209
```cs
203210
var config = DefaultConfig.Instance
@@ -208,6 +215,8 @@ BenchmarkSwitcher
208215
.Run(args, config);
209216
```
210217

218+
or:
219+
211220
```cs
212221
[SimpleJob(RuntimeMoniker.NativeAot70)] // compiles the benchmarks as net7.0 and uses the latest NativeAOT to build a native app
213222
public class TheTypeWithBenchmarks
@@ -216,7 +225,7 @@ public class TheTypeWithBenchmarks
216225
}
217226
```
218227

219-
**Note**: BenchmarkDotNet is going to run `dotnet restore` on the auto-generated project. The first time it does so, it's going to take a **LOT** of time to download all the dependencies (few minutes). Just give it some time and don't press `Ctrl+C` too fast ;)
228+
### Customization
220229

221230
If you want to benchmark some particular version of NativeAOT (or from a different NuGet feed) you have to specify it in an explicit way:
222231

@@ -228,45 +237,119 @@ var config = DefaultConfig.Instance
228237
microsoftDotNetILCompilerVersion: "7.0.0-*", // the version goes here
229238
nuGetFeedUrl: "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet7/nuget/v3/index.json") // this address might change over time
230239
.DisplayName("NativeAOT NuGet")
231-
.TargetFrameworkMoniker("net5.0")
240+
.TargetFrameworkMoniker("net7.0")
232241
.ToToolchain()));
233242
```
234243

244+
The builder allows to configure more settings:
245+
- specify packages restore path by using `PackagesRestorePath($path)`
246+
- rooting all application assemblies by using `RootAllApplicationAssemblies($bool)`. This is disabled by default.
247+
- generating complete type metadata by using `IlcGenerateCompleteTypeMetadata($bool)`. This option is enabled by default.
248+
- generating stack trace metadata by using `IlcGenerateStackTraceData($bool)`. This option is enabled by default.
249+
- set optimization preference by using `IlcOptimizationPreference($value)`. The default is `Speed`, you can configure it to `Size` or nothing
250+
- set instruction set for the target OS, architecture and hardware by using `IlcInstructionSet($value)`. By default BDN recognizes most of the instruction sets on your machine and enables them.
251+
252+
BenchmarkDotNet supports [rd.xml](https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/docs/rd-xml-format.md) files. To get given file respected by BenchmarkDotNet you need to place it in the same folder as the project that defines benchmarks and name it `rd.xml` or in case of multiple files give them `.rd.xml` extension. The alternative to `rd.xml` files is annotating types with [DynamicallyAccessedMembers](https://devblogs.microsoft.com/dotnet/app-trimming-in-net-5/) attribute.
253+
254+
If given benchmark is not supported by NativeAOT, you need to apply `[AotFilter]` attribute for it. Example:
255+
256+
```cs
257+
[Benchmark]
258+
[AotFilter("Not supported by design.")]
259+
public object CreateInstanceNames() => System.Activator.CreateInstance(_assemblyName, _typeName);
260+
```
261+
262+
### Generated files
263+
264+
By default BenchmarkDotNet removes the generates files after finishing the run. To keep them on the disk you need to pass `--keepFiles true` command line argument or apply `[KeepBenchmarkFiles]` attribute to the class which defines benchmark(s). Then, read the folder from the tool output. In the example below it's `D:\projects\performance\artifacts\bin\MicroBenchmarks\Release\net7.0\Job-KRLVKQ`:
265+
266+
```log
267+
// ***** Building 1 exe(s) in Parallel: Start *****
268+
// start dotnet restore -r win-x64 /p:UseSharedCompilation=false /p:BuildInParallel=false /m:1 /p:Deterministic=true /p:Optimize=true in D:\projects\performance\artifacts\bin\MicroBenchmarks\Release\net7.0\Job-KRLVKQ
269+
// command took 2.74s and exited with 0
270+
// start dotnet build -c Release -r win-x64 --no-restore /p:UseSharedCompilation=false /p:BuildInParallel=false /m:1 /p:Deterministic=true /p:Optimize=true in D:\projects\performance\artifacts\bin\MicroBenchmarks\Release\net7.0\Job-KRLVKQ
271+
// command took 3.82s and exited with 0
272+
```
273+
274+
If you go to `D:\projects\performance\artifacts\bin\MicroBenchmarks\Release\net7.0\Job-KRLVKQ`, you can see the generated project file (named `BenchmarkDotNet.Autogenerated.csproj`), code (file name ends with `.notcs`) and find the native executable (in the `bin\**\native` subfolder). Example:
275+
276+
```cmd
277+
cd D:\projects\performance\artifacts\bin\MicroBenchmarks\Release\net7.0\Job-KRLVKQ
278+
cat .\BenchmarkDotNet.Autogenerated.csproj
279+
```
280+
281+
```log
282+
<Project Sdk="Microsoft.NET.Sdk">
283+
<PropertyGroup>
284+
<ImportDirectoryBuildProps>false</ImportDirectoryBuildProps>
285+
<ImportDirectoryBuildTargets>false</ImportDirectoryBuildTargets>
286+
<OutputType>Exe</OutputType>
287+
<TargetFramework>net7.0</TargetFramework>
288+
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
289+
<RuntimeFrameworkVersion></RuntimeFrameworkVersion>
290+
<AssemblyName>Job-KRLVKQ</AssemblyName>
291+
<AssemblyTitle>Job-KRLVKQ</AssemblyTitle>
292+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
293+
<PlatformTarget>x64</PlatformTarget>
294+
<TreatWarningsAsErrors>False</TreatWarningsAsErrors>
295+
<DebugSymbols>false</DebugSymbols>
296+
<UseSharedCompilation>false</UseSharedCompilation>
297+
<Deterministic>true</Deterministic>
298+
<RunAnalyzers>false</RunAnalyzers>
299+
<IlcOptimizationPreference>Speed</IlcOptimizationPreference>
300+
<TrimMode>link</TrimMode><TrimmerDefaultAction>link</TrimmerDefaultAction>
301+
<IlcGenerateCompleteTypeMetadata>True</IlcGenerateCompleteTypeMetadata>
302+
<IlcGenerateStackTraceData>True</IlcGenerateStackTraceData>
303+
<EnsureNETCoreAppRuntime>false</EnsureNETCoreAppRuntime>
304+
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
305+
</PropertyGroup>
306+
<PropertyGroup>
307+
<ServerGarbageCollection>false</ServerGarbageCollection>
308+
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
309+
</PropertyGroup>
310+
<ItemGroup>
311+
<Compile Include="Job-KRLVKQ.notcs" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
312+
</ItemGroup>
313+
<ItemGroup>
314+
<PackageReference Include="Microsoft.DotNet.ILCompiler" Version="7.0.0-*" />
315+
<ProjectReference Include="D:\projects\performance\src\benchmarks\micro\MicroBenchmarks.csproj" />
316+
</ItemGroup>
317+
<ItemGroup>
318+
<RdXmlFile Include="bdn_generated.rd.xml" />
319+
</ItemGroup>
320+
<ItemGroup>
321+
<IlcArg Include="--instructionset:base,sse,sse2,sse3,sse4.1,sse4.2,avx,avx2,aes,bmi,bmi2,fma,lzcnt,pclmul,popcnt" />
322+
</ItemGroup>
323+
</Project>
324+
```
325+
235326
### Compiling source to native code using the ILCompiler you built
236327

237-
If you are a NativeAOT contributor and you want to benchmark your local build of NativeAOT you have to provide necessary info (IlcPath):
328+
If you are a NativeAOT contributor and you want to benchmark your local build of NativeAOT you have to provide necessary info (path to shipping packages).
329+
330+
You can do that from command line:
331+
332+
```cmd
333+
dotnet run -c Release -f net7.0 --runtimes nativeaot7.0 --ilcPackages D:\projects\runtime\artifacts\packages\Release\Shipping\
334+
```
335+
336+
or explicitly in the code:
337+
238338

239339
```cs
240340
var config = DefaultConfig.Instance
241341
.With(Job.ShortRun
242342
.With(NativeAotToolchain.CreateBuilder()
243-
.UseLocalBuild(@"C:\Projects\corert\bin\Windows_NT.x64.Release") // IlcPath
343+
.UseLocalBuild(@"C:\Projects\runtime\artifacts\packages\Release\Shipping\")
244344
.DisplayName("NativeAOT local build")
245345
.TargetFrameworkMoniker("net7.0")
246346
.ToToolchain()));
247347
```
248348

249-
BenchmarkDotNet is going to follow [these instructrions](https://github.com/dotnet/corert/blob/7f902d4d8b1c3280e60f5e06c71951a60da173fb/Documentation/how-to-build-and-run-ilcompiler-in-console-shell-prompt.md#compiling-source-to-native-code-using-the-ilcompiler-you-built) to get it working for you.
250-
251-
### Using CPP Code Generator
252-
253-
> This approach uses transpiler to convert IL to C++, and then uses platform specific C++ compiler and linker for compiling/linking the application. The transpiler is a lot less mature than the RyuJIT path. If you came here to give CoreRT a try on your .NET Core program, use the RyuJIT option above.
349+
BenchmarkDotNet is going to follow [these instructrions](https://github.com/dotnet/runtime/blob/main/docs/workflow/building/coreclr/nativeaot.md#building) to get it working for you.
254350

255-
If you want to test [CPP Code Generator](https://github.com/dotnet/corert/blob/7f902d4d8b1c3280e60f5e06c71951a60da173fb/Documentation/how-to-build-and-run-ilcompiler-in-console-shell-prompt.md#using-cpp-code-generator) you have to use `UseCppCodeGenerator` method:
256-
257-
```cs
258-
var config = DefaultConfig.Instance
259-
.With(Job.Default
260-
.With(
261-
NativeAotToolchain.CreateBuilder()
262-
.UseLocalBuild(@"C:\Projects\corert\bin\Windows_NT.x64.Release") // IlcPath
263-
.UseCppCodeGenerator() // ENABLE IT
264-
.TargetFrameworkMoniker("net7.0")
265-
.DisplayName("CPP")
266-
.ToToolchain()));
267-
```
351+
**Note**: BenchmarkDotNet is going to run `dotnet restore` on the auto-generated project and restore the packages to a temporary folder. It might take some time, but the next time you rebuild dotnet/runtime repo and run the same command BenchmarkDotNet is going to use the new ILCompiler package.
268352

269-
**Note**: You might get some `The method or operation is not implemented.` errors as of today if the code that you are trying to benchmark is using some features that are not implemented by NativeAOT/transpiler yet...
270353

271354
## Wasm
272355

src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ public class CommandLineOptions
9797
[Option("ilCompilerVersion", Required = false, HelpText = "Optional version of Microsoft.DotNet.ILCompiler which should be used to run with NativeAOT. Example: \"7.0.0-preview.3.22123.2\"")]
9898
public string ILCompilerVersion { get; set; }
9999

100-
[Option("ilcPath", Required = false, HelpText = "Optional IlcPath which should be used to run with private NativeAOT build.")]
101-
public DirectoryInfo IlcPath { get; set; }
100+
[Option("ilcPackages", Required = false, HelpText = @"Optional path to shipping packages produced by local dotnet/runtime build. Example: 'D:\projects\runtime\artifacts\packages\Release\Shipping\'")]
101+
public DirectoryInfo IlcPackages { get; set; }
102102

103103
[Option("launchCount", Required = false, HelpText = "How many times we should launch process with target benchmark. The default is 1.")]
104104
public int? LaunchCount { get; set; }

src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,9 @@ private static bool Validate(CommandLineOptions options, ILogger logger)
157157
return false;
158158
}
159159

160-
if (options.IlcPath.IsNotNullButDoesNotExist())
160+
if (options.IlcPackages.IsNotNullButDoesNotExist())
161161
{
162-
logger.WriteLineError($"The provided {nameof(options.IlcPath)} \"{options.IlcPath}\" does NOT exist.");
162+
logger.WriteLineError($"The provided {nameof(options.IlcPackages)} \"{options.IlcPackages}\" does NOT exist.");
163163
return false;
164164
}
165165

@@ -395,8 +395,8 @@ private static Job CreateAotJob(Job baseJob, CommandLineOptions options, Runtime
395395
if (options.RestorePath != null)
396396
builder.PackagesRestorePath(options.RestorePath.FullName);
397397

398-
if (options.IlcPath != null)
399-
builder.UseLocalBuild(options.IlcPath.FullName);
398+
if (options.IlcPackages != null)
399+
builder.UseLocalBuild(options.IlcPackages);
400400
else if (!string.IsNullOrEmpty(options.ILCompilerVersion))
401401
builder.UseNuGet(options.ILCompilerVersion, nuGetFeedUrl);
402402
else

0 commit comments

Comments
 (0)