Skip to content

Commit 32ddeb5

Browse files
authored
provide Hardware Intrinsics information (#2051)
* move Hardware Intrinsics support detection logic to a separate type, avoid reflection for .NET 6+ code path * print full info in "Benchmark Process Environment Information" but only short in the Summary table * include HardwareIntrinsics info in exported JSON files
1 parent d6020e9 commit 32ddeb5

10 files changed

+331
-41
lines changed

src/BenchmarkDotNet/Environments/BenchmarkEnvironmentInfo.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using BenchmarkDotNet.Engines;
77
using BenchmarkDotNet.Jobs;
88
using BenchmarkDotNet.Portability;
9+
using BenchmarkDotNet.Portability.Cpu;
910
using BenchmarkDotNet.Validators;
1011
using JetBrains.Annotations;
1112

@@ -15,13 +16,15 @@ public class BenchmarkEnvironmentInfo
1516
{
1617
internal const string RuntimeInfoPrefix = "Runtime=";
1718
internal const string GcInfoPrefix = "GC=";
19+
internal const string HardwareIntrinsicsPrefix = "HardwareIntrinsics=";
1820

1921
[PublicAPI] public string Architecture { get; protected set; }
2022
[PublicAPI] public string Configuration { get; protected set; }
2123
[PublicAPI] public string RuntimeVersion { get; protected set; }
2224
[PublicAPI] public bool HasAttachedDebugger { get; protected set; }
2325
[PublicAPI] public bool HasRyuJit { get; protected set; }
2426
[PublicAPI] public string JitInfo { get; protected set; }
27+
[PublicAPI] public string HardwareIntrinsicsShort { get; protected set; }
2528
[PublicAPI] public bool IsServerGC { get; protected set; }
2629
[PublicAPI] public bool IsConcurrentGC { get; protected set; }
2730
[PublicAPI] public long GCAllocationQuantum { get; protected set; }
@@ -34,6 +37,7 @@ protected BenchmarkEnvironmentInfo()
3437
Configuration = RuntimeInformation.GetConfiguration();
3538
HasRyuJit = RuntimeInformation.HasRyuJit();
3639
JitInfo = RuntimeInformation.GetJitInfo();
40+
HardwareIntrinsicsShort = HardwareIntrinsics.GetShortInfo();
3741
IsServerGC = GCSettings.IsServerGC;
3842
IsConcurrentGC = GCSettings.LatencyMode != GCLatencyMode.Batch;
3943
HasAttachedDebugger = Debugger.IsAttached;
@@ -49,6 +53,7 @@ public virtual IEnumerable<string> ToFormattedString()
4953
yield return "Benchmark Process Environment Information:";
5054
yield return $"{RuntimeInfoPrefix}{GetRuntimeInfo()}";
5155
yield return $"{GcInfoPrefix}{GetGcConcurrentFlag()} {GetGcServerFlag()}";
56+
yield return $"{HardwareIntrinsicsPrefix}{HardwareIntrinsics.GetFullInfo(RuntimeInformation.GetCurrentPlatform())} {HardwareIntrinsics.GetVectorSize()}";
5257
}
5358

5459
[PublicAPI] protected string GetConfigurationFlag() => Configuration == RuntimeInformation.Unknown || Configuration == RuntimeInformation.ReleaseConfigurationName
@@ -61,7 +66,7 @@ [PublicAPI] protected string GetConfigurationFlag() => Configuration == RuntimeI
6166

6267
internal string GetRuntimeInfo()
6368
{
64-
string jitInfo = string.Join(" ", new[] { JitInfo, GetConfigurationFlag(), GetDebuggerFlag() }.Where(title => title != ""));
69+
string jitInfo = string.Join(" ", new[] { JitInfo, HardwareIntrinsicsShort, GetConfigurationFlag(), GetDebuggerFlag() }.Where(title => title != ""));
6570
return $"{RuntimeVersion}, {Architecture} {jitInfo}";
6671
}
6772

src/BenchmarkDotNet/Exporters/Json/JsonExporterBase.cs

+2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ public override void ExportToLog(Summary summary, ILogger logger)
5858
{ "MethodTitle", report.BenchmarkCase.Descriptor.WorkloadMethodDisplayInfo },
5959
{ "Parameters", report.BenchmarkCase.Parameters.PrintInfo },
6060
{ "FullName", FullNameProvider.GetBenchmarkName(report.BenchmarkCase) }, // do NOT remove this property, it is used for xunit-performance migration
61+
// Hardware Intrinsics can be disabled using env vars, that is why they might be different per benchmark and are not exported as part of HostEnvironmentInfo
62+
{ "HardwareIntrinsics", report.GetHardwareIntrinsicsInfo() ?? "" },
6163
// { "Properties", r.Benchmark.Job.ToSet().ToDictionary(p => p.Name, p => p.Value) }, // TODO
6264
{ "Statistics", report.ResultStatistics }
6365
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
using System.Collections.Generic;
2+
using BenchmarkDotNet.Environments;
3+
#if NET6_0_OR_GREATER
4+
using System.Runtime.Intrinsics.X86;
5+
using System.Runtime.Intrinsics.Arm;
6+
using System.Numerics;
7+
#elif NETSTANDARD2_0_OR_GREATER
8+
using System;
9+
#endif
10+
11+
namespace BenchmarkDotNet.Portability.Cpu
12+
{
13+
internal static class HardwareIntrinsics
14+
{
15+
internal static string GetVectorSize()
16+
{
17+
#if NET6_0_OR_GREATER
18+
if (Vector.IsHardwareAccelerated)
19+
return $"VectorSize={Vector<byte>.Count * 8}";
20+
#endif
21+
return string.Empty;
22+
}
23+
24+
internal static string GetShortInfo()
25+
{
26+
if (IsX86Avx2Supported)
27+
return "AVX2";
28+
else if (IsX86AvxSupported)
29+
return "AVX";
30+
else if (IsX86Sse42Supported)
31+
return "SSE4.2";
32+
else if (IsX86Sse41Supported)
33+
return "SSE4.1";
34+
else if (IsX86Sse3Supported)
35+
return "SSE3";
36+
else if (IsX86Sse2Supported)
37+
return "SSE2";
38+
else if (IsX86SseSupported)
39+
return "SSE";
40+
else if (IsArmAdvSimdSupported)
41+
return "AdvSIMD";
42+
else if (IsArmBaseSupported)
43+
return "base";
44+
else
45+
return string.Empty;
46+
}
47+
48+
internal static string GetFullInfo(Platform platform)
49+
{
50+
return string.Join(",", GetCurrentProcessInstructionSets(platform));
51+
52+
static IEnumerable<string> GetCurrentProcessInstructionSets(Platform platform)
53+
{
54+
switch (platform)
55+
{
56+
case Platform.X86:
57+
case Platform.X64:
58+
if (IsX86Avx2Supported) yield return "AVX2";
59+
else if (IsX86AvxSupported) yield return "AVX";
60+
else if (IsX86Sse42Supported) yield return "SSE4.2";
61+
else if (IsX86Sse41Supported) yield return "SSE4.1";
62+
else if (IsX86Sse3Supported) yield return "SSE3";
63+
else if (IsX86Sse2Supported) yield return "SSE2";
64+
else if (IsX86SseSupported) yield return "SSE";
65+
66+
if (IsX86AesSupported) yield return "AES";
67+
if (IsX86Bmi1Supported) yield return "BMI1";
68+
if (IsX86Bmi2Supported) yield return "BMI2";
69+
if (IsX86FmaSupported) yield return "FMA";
70+
if (IsX86LzcntSupported) yield return "LZCNT";
71+
if (IsX86PclmulqdqSupported) yield return "PCLMUL";
72+
if (IsX86PopcntSupported) yield return "POPCNT";
73+
if (IsX86AvxVnniSupported) yield return "AvxVnni";
74+
// TODO: Add MOVBE when API is added.
75+
break;
76+
case Platform.Arm64:
77+
if (IsArmAdvSimdSupported) yield return "AdvSIMD";
78+
79+
if (IsArmAesSupported) yield return "AES";
80+
if (IsArmCrc32Supported) yield return "CRC32";
81+
if (IsArmDpSupported) yield return "DP";
82+
if (IsArmRdmSupported) yield return "RDM";
83+
if (IsArmSha1Supported) yield return "SHA1";
84+
if (IsArmSha256Supported) yield return "SHA256";
85+
break;
86+
default:
87+
yield break;
88+
}
89+
}
90+
}
91+
92+
internal static bool IsX86BaseSupported =>
93+
#if NET6_0_OR_GREATER
94+
X86Base.IsSupported;
95+
#elif NETSTANDARD
96+
GetIsSupported("System.Runtime.Intrinsics.X86.X86Base");
97+
#endif
98+
99+
internal static bool IsX86SseSupported =>
100+
#if NET6_0_OR_GREATER
101+
Sse.IsSupported;
102+
#elif NETSTANDARD
103+
GetIsSupported("System.Runtime.Intrinsics.X86.Sse");
104+
#endif
105+
106+
internal static bool IsX86Sse2Supported =>
107+
#if NET6_0_OR_GREATER
108+
Sse2.IsSupported;
109+
#elif NETSTANDARD
110+
GetIsSupported("System.Runtime.Intrinsics.X86.Sse2");
111+
#endif
112+
113+
internal static bool IsX86Sse3Supported =>
114+
#if NET6_0_OR_GREATER
115+
Sse3.IsSupported;
116+
#elif NETSTANDARD
117+
GetIsSupported("System.Runtime.Intrinsics.X86.Sse3");
118+
#endif
119+
120+
internal static bool IsX86Sse41Supported =>
121+
#if NET6_0_OR_GREATER
122+
Sse41.IsSupported;
123+
#elif NETSTANDARD
124+
GetIsSupported("System.Runtime.Intrinsics.X86.Sse41");
125+
#endif
126+
127+
internal static bool IsX86Sse42Supported =>
128+
#if NET6_0_OR_GREATER
129+
Sse42.IsSupported;
130+
#elif NETSTANDARD
131+
GetIsSupported("System.Runtime.Intrinsics.X86.Sse42");
132+
#endif
133+
134+
internal static bool IsX86AvxSupported =>
135+
#if NET6_0_OR_GREATER
136+
Avx.IsSupported;
137+
#elif NETSTANDARD
138+
GetIsSupported("System.Runtime.Intrinsics.X86.Avx");
139+
#endif
140+
141+
internal static bool IsX86Avx2Supported =>
142+
#if NET6_0_OR_GREATER
143+
Avx2.IsSupported;
144+
#elif NETSTANDARD
145+
GetIsSupported("System.Runtime.Intrinsics.X86.Avx2");
146+
#endif
147+
148+
internal static bool IsX86AesSupported =>
149+
#if NET6_0_OR_GREATER
150+
System.Runtime.Intrinsics.X86.Aes.IsSupported;
151+
#elif NETSTANDARD
152+
GetIsSupported("System.Runtime.Intrinsics.X86.Aes");
153+
#endif
154+
155+
internal static bool IsX86Bmi1Supported =>
156+
#if NET6_0_OR_GREATER
157+
Bmi1.IsSupported;
158+
#elif NETSTANDARD
159+
GetIsSupported("System.Runtime.Intrinsics.X86.Bmi1");
160+
#endif
161+
162+
internal static bool IsX86Bmi2Supported =>
163+
#if NET6_0_OR_GREATER
164+
Bmi2.IsSupported;
165+
#elif NETSTANDARD
166+
GetIsSupported("System.Runtime.Intrinsics.X86.Bmi2");
167+
#endif
168+
169+
internal static bool IsX86FmaSupported =>
170+
#if NET6_0_OR_GREATER
171+
Fma.IsSupported;
172+
#elif NETSTANDARD
173+
GetIsSupported("System.Runtime.Intrinsics.X86.Fma");
174+
#endif
175+
176+
internal static bool IsX86LzcntSupported =>
177+
#if NET6_0_OR_GREATER
178+
Lzcnt.IsSupported;
179+
#elif NETSTANDARD
180+
GetIsSupported("System.Runtime.Intrinsics.X86.Lzcnt");
181+
#endif
182+
183+
internal static bool IsX86PclmulqdqSupported =>
184+
#if NET6_0_OR_GREATER
185+
Pclmulqdq.IsSupported;
186+
#elif NETSTANDARD
187+
GetIsSupported("System.Runtime.Intrinsics.X86.Pclmulqdq");
188+
#endif
189+
190+
internal static bool IsX86PopcntSupported =>
191+
#if NET6_0_OR_GREATER
192+
Popcnt.IsSupported;
193+
#elif NETSTANDARD
194+
GetIsSupported("System.Runtime.Intrinsics.X86.Popcnt");
195+
#endif
196+
197+
internal static bool IsX86AvxVnniSupported =>
198+
#if NET6_0_OR_GREATER
199+
#pragma warning disable CA2252 // This API requires opting into preview features
200+
AvxVnni.IsSupported;
201+
#pragma warning restore CA2252 // This API requires opting into preview features
202+
#elif NETSTANDARD
203+
GetIsSupported("System.Runtime.Intrinsics.X86.AvxVnni");
204+
#endif
205+
206+
internal static bool IsArmBaseSupported =>
207+
#if NET6_0_OR_GREATER
208+
ArmBase.IsSupported;
209+
#elif NETSTANDARD
210+
GetIsSupported("System.Runtime.Intrinsics.Arm.ArmBase");
211+
#endif
212+
213+
internal static bool IsArmAdvSimdSupported =>
214+
#if NET6_0_OR_GREATER
215+
AdvSimd.IsSupported;
216+
#elif NETSTANDARD
217+
GetIsSupported("System.Runtime.Intrinsics.Arm.AdvSimd");
218+
#endif
219+
220+
internal static bool IsArmAesSupported =>
221+
#if NET6_0_OR_GREATER
222+
System.Runtime.Intrinsics.Arm.Aes.IsSupported;
223+
#elif NETSTANDARD
224+
GetIsSupported("System.Runtime.Intrinsics.Arm.Aes");
225+
#endif
226+
227+
internal static bool IsArmCrc32Supported =>
228+
#if NET6_0_OR_GREATER
229+
Crc32.IsSupported;
230+
#elif NETSTANDARD
231+
GetIsSupported("System.Runtime.Intrinsics.Arm.Crc32");
232+
#endif
233+
234+
internal static bool IsArmDpSupported =>
235+
#if NET6_0_OR_GREATER
236+
Dp.IsSupported;
237+
#elif NETSTANDARD
238+
GetIsSupported("System.Runtime.Intrinsics.Arm.Dp");
239+
#endif
240+
241+
internal static bool IsArmRdmSupported =>
242+
#if NET6_0_OR_GREATER
243+
Rdm.IsSupported;
244+
#elif NETSTANDARD
245+
GetIsSupported("System.Runtime.Intrinsics.Arm.Rdm");
246+
#endif
247+
248+
internal static bool IsArmSha1Supported =>
249+
#if NET6_0_OR_GREATER
250+
Sha1.IsSupported;
251+
#elif NETSTANDARD
252+
GetIsSupported("System.Runtime.Intrinsics.Arm.Sha1");
253+
#endif
254+
255+
internal static bool IsArmSha256Supported =>
256+
#if NET6_0_OR_GREATER
257+
Sha256.IsSupported;
258+
#elif NETSTANDARD
259+
GetIsSupported("System.Runtime.Intrinsics.Arm.Sha256");
260+
#endif
261+
262+
#if NETSTANDARD
263+
private static bool GetIsSupported(string typeName)
264+
{
265+
Type type = Type.GetType(typeName);
266+
if (type == null) return false;
267+
268+
return (bool)type.GetProperty("IsSupported", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static).GetValue(null, null);
269+
}
270+
#endif
271+
}
272+
}

src/BenchmarkDotNet/Portability/RuntimeInformation.cs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Diagnostics.CodeAnalysis;
55
using System.Linq;
66
using System.Management;
7+
using System.Numerics;
78
using System.Reflection;
89
using System.Runtime.InteropServices;
910
using System.Text.RegularExpressions;

src/BenchmarkDotNet/Reports/BenchmarkReportExtensions.cs

+4
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@ public static class BenchmarkReportExtensions
88
{
99
private const string DisplayedRuntimeInfoPrefix = "// " + BenchmarkEnvironmentInfo.RuntimeInfoPrefix;
1010
private const string DisplayedGcInfoPrefix = "// " + BenchmarkEnvironmentInfo.GcInfoPrefix;
11+
private const string DisplayedHardwareIntrinsicsPrefix = "// " + BenchmarkEnvironmentInfo.HardwareIntrinsicsPrefix;
1112

1213
[CanBeNull]
1314
public static string GetRuntimeInfo(this BenchmarkReport report) => report.GetInfoFromOutput(DisplayedRuntimeInfoPrefix);
1415

1516
[CanBeNull]
1617
public static string GetGcInfo(this BenchmarkReport report) => report.GetInfoFromOutput(DisplayedGcInfoPrefix);
1718

19+
[CanBeNull]
20+
public static string GetHardwareIntrinsicsInfo(this BenchmarkReport report) => report.GetInfoFromOutput(DisplayedHardwareIntrinsicsPrefix);
21+
1822
[CanBeNull]
1923
private static string GetInfoFromOutput(this BenchmarkReport report, string prefix)
2024
{

0 commit comments

Comments
 (0)