Skip to content

Commit 1c935dc

Browse files
authored
NativeAOT: IlcOptimizationPreference & IlcInstructionSet (#1994)
* allow the users to specify IlcOptimizationPreference, use "Speed" as default * allow the users to specify Instruction Set * specify platform in explicit way * provide default Instruction Set based on host process
1 parent 35ba9d5 commit 1c935dc

File tree

4 files changed

+135
-5
lines changed

4 files changed

+135
-5
lines changed

src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using System.IO;
55
using System.Linq;
66
using System.Text;
7+
using BenchmarkDotNet.Environments;
8+
using BenchmarkDotNet.Extensions;
79
using BenchmarkDotNet.Loggers;
810
using BenchmarkDotNet.Portability;
911
using BenchmarkDotNet.Running;
@@ -26,7 +28,8 @@ internal Generator(string ilCompilerVersion,
2628
string runtimeFrameworkVersion, string targetFrameworkMoniker, string cliPath,
2729
string runtimeIdentifier, IReadOnlyDictionary<string, string> feeds, bool useNuGetClearTag,
2830
bool useTempFolderForRestore, string packagesRestorePath,
29-
bool rootAllApplicationAssemblies, bool ilcGenerateCompleteTypeMetadata, bool ilcGenerateStackTraceData)
31+
bool rootAllApplicationAssemblies, bool ilcGenerateCompleteTypeMetadata, bool ilcGenerateStackTraceData,
32+
string ilcOptimizationPreference, string ilcInstructionSet)
3033
: base(targetFrameworkMoniker, cliPath, GetPackagesDirectoryPath(useTempFolderForRestore, packagesRestorePath), runtimeFrameworkVersion)
3134
{
3235
this.ilCompilerVersion = ilCompilerVersion;
@@ -38,6 +41,8 @@ internal Generator(string ilCompilerVersion,
3841
this.rootAllApplicationAssemblies = rootAllApplicationAssemblies;
3942
this.ilcGenerateCompleteTypeMetadata = ilcGenerateCompleteTypeMetadata;
4043
this.ilcGenerateStackTraceData = ilcGenerateStackTraceData;
44+
this.ilcOptimizationPreference = ilcOptimizationPreference;
45+
this.ilcInstructionSet = ilcInstructionSet;
4146
}
4247

4348
private readonly string ilCompilerVersion;
@@ -50,6 +55,8 @@ internal Generator(string ilCompilerVersion,
5055
private readonly bool rootAllApplicationAssemblies;
5156
private readonly bool ilcGenerateCompleteTypeMetadata;
5257
private readonly bool ilcGenerateStackTraceData;
58+
private readonly string ilcOptimizationPreference;
59+
private readonly string ilcInstructionSet;
5360

5461
private bool IsNuGet => feeds.ContainsKey(NativeAotNuGetFeed) && !string.IsNullOrWhiteSpace(ilCompilerVersion);
5562

@@ -127,11 +134,13 @@ private string GenerateProjectForNuGetBuild(BuildPartition buildPartition, Artif
127134
<AssemblyName>{artifactsPaths.ProgramName}</AssemblyName>
128135
<AssemblyTitle>{artifactsPaths.ProgramName}</AssemblyTitle>
129136
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
137+
<PlatformTarget>{buildPartition.Platform.ToConfig()}</PlatformTarget>
130138
<TreatWarningsAsErrors>False</TreatWarningsAsErrors>
131139
<DebugSymbols>false</DebugSymbols>
132140
<UseSharedCompilation>false</UseSharedCompilation>
133141
<Deterministic>true</Deterministic>
134142
<RunAnalyzers>false</RunAnalyzers>
143+
<IlcOptimizationPreference>{ilcOptimizationPreference}</IlcOptimizationPreference>
135144
{GetTrimmingSettings()}
136145
<IlcGenerateCompleteTypeMetadata>{ilcGenerateCompleteTypeMetadata}</IlcGenerateCompleteTypeMetadata>
137146
<IlcGenerateStackTraceData>{ilcGenerateStackTraceData}</IlcGenerateStackTraceData>
@@ -149,6 +158,9 @@ private string GenerateProjectForNuGetBuild(BuildPartition buildPartition, Artif
149158
<ItemGroup>
150159
{string.Join(Environment.NewLine, GetRdXmlFiles(buildPartition.RepresentativeBenchmarkCase.Descriptor.Type, logger).Select(file => $"<RdXmlFile Include=\"{file}\" />"))}
151160
</ItemGroup>
161+
<ItemGroup>
162+
{GetInstructionSetSettings(buildPartition)}
163+
</ItemGroup>
152164
</Project>";
153165

154166
private string GenerateProjectForLocalBuild(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger) => $@"
@@ -162,11 +174,13 @@ private string GenerateProjectForLocalBuild(BuildPartition buildPartition, Artif
162174
<AssemblyName>{artifactsPaths.ProgramName}</AssemblyName>
163175
<AssemblyTitle>{artifactsPaths.ProgramName}</AssemblyTitle>
164176
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
177+
<PlatformTarget>{buildPartition.Platform.ToConfig()}</PlatformTarget>
165178
<TreatWarningsAsErrors>False</TreatWarningsAsErrors>
166179
<DebugSymbols>false</DebugSymbols>
167180
<UseSharedCompilation>false</UseSharedCompilation>
168181
<Deterministic>true</Deterministic>
169182
<RunAnalyzers>false</RunAnalyzers>
183+
<IlcOptimizationPreference>{ilcOptimizationPreference}</IlcOptimizationPreference>
170184
{GetTrimmingSettings()}
171185
<IlcGenerateCompleteTypeMetadata>{ilcGenerateCompleteTypeMetadata}</IlcGenerateCompleteTypeMetadata>
172186
<IlcGenerateStackTraceData>{ilcGenerateStackTraceData}</IlcGenerateStackTraceData>
@@ -184,6 +198,9 @@ private string GenerateProjectForLocalBuild(BuildPartition buildPartition, Artif
184198
<ItemGroup>
185199
{string.Join(Environment.NewLine, GetRdXmlFiles(buildPartition.RepresentativeBenchmarkCase.Descriptor.Type, logger).Select(file => $"<RdXmlFile Include=\"{file}\" />"))}
186200
</ItemGroup>
201+
<ItemGroup>
202+
{GetInstructionSetSettings(buildPartition)}
203+
</ItemGroup>
187204
</Project>";
188205

189206
private string GetTrimmingSettings()
@@ -192,6 +209,14 @@ private string GetTrimmingSettings()
192209
// TrimMode is set in explicit way as for older versions it might have different default value
193210
: "<TrimMode>link</TrimMode><TrimmerDefaultAction>link</TrimmerDefaultAction>";
194211

212+
private string GetInstructionSetSettings(BuildPartition buildPartition)
213+
{
214+
string instructionSet = ilcInstructionSet ?? GetCurrentInstructionSet(buildPartition.Platform);
215+
return !string.IsNullOrEmpty(instructionSet)
216+
? $@"<IlcArg Include=""--instructionset:{instructionSet}"" />"
217+
: "";
218+
}
219+
195220
public IEnumerable<string> GetRdXmlFiles(Type benchmarkTarget, ILogger logger)
196221
{
197222
yield return GeneratedRdXmlFileName;
@@ -237,5 +262,56 @@ private void GenerateReflectionFile(ArtifactsPaths artifactsPaths)
237262
else
238263
throw new InvalidOperationException($"Can't get directory of projectFilePath ('{artifactsPaths.ProjectFilePath}')");
239264
}
265+
266+
private string GetCurrentInstructionSet(Platform platform)
267+
=> string.Join(",", GetHostProcessInstructionSets(platform));
268+
269+
// based on https://github.com/dotnet/runtime/blob/ce61c09a5f6fc71d8f717d3fc4562f42171869a0/src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs#L727
270+
private static IEnumerable<string> GetHostProcessInstructionSets(Platform platform)
271+
{
272+
switch (platform)
273+
{
274+
case Platform.X86:
275+
case Platform.X64:
276+
if (GetIsSupported("System.Runtime.Intrinsics.X86.X86Base")) yield return "base";
277+
if (GetIsSupported("System.Runtime.Intrinsics.X86.Sse")) yield return "sse";
278+
if (GetIsSupported("System.Runtime.Intrinsics.X86.Sse2")) yield return "sse2";
279+
if (GetIsSupported("System.Runtime.Intrinsics.X86.Sse3")) yield return "sse3";
280+
if (GetIsSupported("System.Runtime.Intrinsics.X86.Sse41")) yield return "sse4.1";
281+
if (GetIsSupported("System.Runtime.Intrinsics.X86.Sse42")) yield return "sse4.2";
282+
if (GetIsSupported("System.Runtime.Intrinsics.X86.Avx")) yield return "avx";
283+
if (GetIsSupported("System.Runtime.Intrinsics.X86.Avx2")) yield return "avx2";
284+
if (GetIsSupported("System.Runtime.Intrinsics.X86.Aes")) yield return "aes";
285+
if (GetIsSupported("System.Runtime.Intrinsics.X86.Bmi1")) yield return "bmi";
286+
if (GetIsSupported("System.Runtime.Intrinsics.X86.Bmi2")) yield return "bmi2";
287+
if (GetIsSupported("System.Runtime.Intrinsics.X86.Fma")) yield return "fma";
288+
if (GetIsSupported("System.Runtime.Intrinsics.X86.Lzcnt")) yield return "lzcnt";
289+
if (GetIsSupported("System.Runtime.Intrinsics.X86.Pclmulqdq")) yield return "pclmul";
290+
if (GetIsSupported("System.Runtime.Intrinsics.X86.Popcnt")) yield return "popcnt";
291+
if (GetIsSupported("System.Runtime.Intrinsics.X86.AvxVnni")) yield return "avxvnni";
292+
break;
293+
case Platform.Arm64:
294+
if (GetIsSupported("System.Runtime.Intrinsics.Arm.ArmBase")) yield return "base";
295+
if (GetIsSupported("System.Runtime.Intrinsics.Arm.AdvSimd")) yield return "neon";
296+
if (GetIsSupported("System.Runtime.Intrinsics.Arm.Aes")) yield return "aes";
297+
if (GetIsSupported("System.Runtime.Intrinsics.Arm.Crc32")) yield return "crc";
298+
if (GetIsSupported("System.Runtime.Intrinsics.Arm.Dp")) yield return "dotprod";
299+
if (GetIsSupported("System.Runtime.Intrinsics.Arm.Rdm")) yield return "rdma";
300+
if (GetIsSupported("System.Runtime.Intrinsics.Arm.Sha1")) yield return "sha1";
301+
if (GetIsSupported("System.Runtime.Intrinsics.Arm.Sha256")) yield return "sha2";
302+
// todo: handle "lse"
303+
break;
304+
default:
305+
yield break;
306+
}
307+
}
308+
309+
private static bool GetIsSupported(string typeName)
310+
{
311+
Type type = Type.GetType(typeName);
312+
if (type == null) return false;
313+
314+
return (bool)type.GetProperty("IsSupported", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static).GetValue(null, null);
315+
}
240316
}
241317
}

src/BenchmarkDotNet/Toolchains/NativeAot/NativeAotToolchain.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ internal NativeAotToolchain(string displayName,
2828
string runtimeFrameworkVersion, string targetFrameworkMoniker, string runtimeIdentifier,
2929
string customDotNetCliPath, string packagesRestorePath,
3030
Dictionary<string, string> feeds, bool useNuGetClearTag, bool useTempFolderForRestore,
31-
bool rootAllApplicationAssemblies, bool ilcGenerateCompleteTypeMetadata, bool ilcGenerateStackTraceData)
31+
bool rootAllApplicationAssemblies, bool ilcGenerateCompleteTypeMetadata, bool ilcGenerateStackTraceData,
32+
string ilcOptimizationPreference, string ilcInstructionSet)
3233
: base(displayName,
3334
new Generator(ilCompilerVersion, runtimeFrameworkVersion, targetFrameworkMoniker, customDotNetCliPath,
3435
runtimeIdentifier, feeds, useNuGetClearTag, useTempFolderForRestore, packagesRestorePath,
35-
rootAllApplicationAssemblies, ilcGenerateCompleteTypeMetadata, ilcGenerateStackTraceData),
36+
rootAllApplicationAssemblies, ilcGenerateCompleteTypeMetadata, ilcGenerateStackTraceData,
37+
ilcOptimizationPreference, ilcInstructionSet),
3638
new DotNetCliPublisher(customDotNetCliPath, GetExtraArguments(runtimeIdentifier), GetEnvironmentVariables(ilcPath)),
3739
new Executor())
3840
{

src/BenchmarkDotNet/Toolchains/NativeAot/NativeAotToolchainBuilder.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public class NativeAotToolchainBuilder : CustomDotNetCliToolchainBuilder
1717
private bool rootAllApplicationAssemblies;
1818
private bool ilcGenerateCompleteTypeMetadata = true;
1919
private bool ilcGenerateStackTraceData = true;
20+
private string ilcOptimizationPreference = "Speed";
21+
private string ilcInstructionSet = null;
2022

2123
private bool isIlCompilerConfigured;
2224

@@ -110,6 +112,34 @@ public NativeAotToolchainBuilder IlcGenerateStackTraceData(bool value)
110112
return this;
111113
}
112114

115+
/// <summary>
116+
/// Options related to code generation.
117+
/// </summary>
118+
/// <param name="value">"Speed" to favor code execution speed (default), "Size" to favor smaller code size</param>
119+
[PublicAPI]
120+
public NativeAotToolchainBuilder IlcOptimizationPreference(string value = "Speed")
121+
{
122+
ilcOptimizationPreference = value;
123+
124+
return this;
125+
}
126+
127+
/// <summary>
128+
/// By default, the compiler targets the minimum instruction set supported by the target OS and architecture.
129+
/// This option allows targeting newer instruction sets for better performance.
130+
/// The native binary will require the instruction sets to be supported by the hardware in order to run.
131+
/// For example, `avx2,bmi2,fma,pclmul,popcnt,aes` will produce binary that takes advantage of instruction sets
132+
/// that are typically present on current Intel and AMD processors.
133+
/// </summary>
134+
/// <param name="value">Specify empty string ("", not null) to use the defaults.</param>
135+
[PublicAPI]
136+
public NativeAotToolchainBuilder IlcInstructionSet(string value)
137+
{
138+
ilcInstructionSet = value;
139+
140+
return this;
141+
}
142+
113143
[PublicAPI]
114144
public override IToolchain ToToolchain()
115145
{
@@ -130,7 +160,9 @@ public override IToolchain ToToolchain()
130160
useTempFolderForRestore: useTempFolderForRestore,
131161
rootAllApplicationAssemblies: rootAllApplicationAssemblies,
132162
ilcGenerateCompleteTypeMetadata: ilcGenerateCompleteTypeMetadata,
133-
ilcGenerateStackTraceData: ilcGenerateStackTraceData
163+
ilcGenerateStackTraceData: ilcGenerateStackTraceData,
164+
ilcOptimizationPreference: ilcOptimizationPreference,
165+
ilcInstructionSet: ilcInstructionSet
134166
);
135167
}
136168
}

tests/BenchmarkDotNet.IntegrationTests/NativeAotTests.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using BenchmarkDotNet.Jobs;
66
using BenchmarkDotNet.Portability;
77
using BenchmarkDotNet.Tests.XUnit;
8+
using BenchmarkDotNet.Toolchains.NativeAot;
89
using Xunit.Abstractions;
910

1011
namespace BenchmarkDotNet.IntegrationTests
@@ -23,21 +24,40 @@ public void LatestNativeAotVersionIsSupported()
2324
if (ContinuousIntegration.IsAppVeyorOnWindows()) // too time consuming for AppVeyor (1h limit)
2425
return;
2526

27+
var toolchain = NativeAotToolchain.CreateBuilder().UseNuGet().IlcInstructionSet(IsAvx2Supported() ? "avx2" : "").ToToolchain();
28+
2629
var config = ManualConfig.CreateEmpty()
2730
.AddJob(Job.Dry
28-
.WithRuntime(NativeAotRuntime.GetCurrentVersion())); // we test against latest version for current TFM to make sure we avoid issues like #1055
31+
.WithRuntime(NativeAotRuntime.GetCurrentVersion()) // we test against latest version for current TFM to make sure we avoid issues like #1055
32+
.WithToolchain(toolchain)
33+
.WithEnvironmentVariable(NativeAotBenchmark.EnvVarKey, IsAvx2Supported().ToString().ToLower()));
2934

3035
CanExecute<NativeAotBenchmark>(config);
3136
}
37+
38+
private bool IsAvx2Supported()
39+
{
40+
#if NET6_0_OR_GREATER
41+
return System.Runtime.Intrinsics.X86.Avx2.IsSupported;
42+
#else
43+
return false;
44+
#endif
45+
}
3246
}
3347

3448
public class NativeAotBenchmark
3549
{
50+
internal const string EnvVarKey = "AVX2_IsSupported";
51+
3652
[Benchmark]
3753
public void Check()
3854
{
3955
if (!RuntimeInformation.IsNativeAOT)
4056
throw new Exception("This is NOT NativeAOT");
57+
#if NET6_0_OR_GREATER
58+
if (System.Runtime.Intrinsics.X86.Avx2.IsSupported != bool.Parse(Environment.GetEnvironmentVariable(EnvVarKey)))
59+
throw new Exception("Unexpected Instruction Set");
60+
#endif
4161
}
4262
}
4363
}

0 commit comments

Comments
 (0)