Skip to content

Commit

Permalink
provide Hardware Intrinsics information (#2051)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
adamsitnik committed Jul 26, 2022
1 parent d6020e9 commit 32ddeb5
Show file tree
Hide file tree
Showing 10 changed files with 331 additions and 41 deletions.
7 changes: 6 additions & 1 deletion src/BenchmarkDotNet/Environments/BenchmarkEnvironmentInfo.cs
Expand Up @@ -6,6 +6,7 @@
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Portability;
using BenchmarkDotNet.Portability.Cpu;
using BenchmarkDotNet.Validators;
using JetBrains.Annotations;

Expand All @@ -15,13 +16,15 @@ public class BenchmarkEnvironmentInfo
{
internal const string RuntimeInfoPrefix = "Runtime=";
internal const string GcInfoPrefix = "GC=";
internal const string HardwareIntrinsicsPrefix = "HardwareIntrinsics=";

[PublicAPI] public string Architecture { get; protected set; }
[PublicAPI] public string Configuration { get; protected set; }
[PublicAPI] public string RuntimeVersion { get; protected set; }
[PublicAPI] public bool HasAttachedDebugger { get; protected set; }
[PublicAPI] public bool HasRyuJit { get; protected set; }
[PublicAPI] public string JitInfo { get; protected set; }
[PublicAPI] public string HardwareIntrinsicsShort { get; protected set; }
[PublicAPI] public bool IsServerGC { get; protected set; }
[PublicAPI] public bool IsConcurrentGC { get; protected set; }
[PublicAPI] public long GCAllocationQuantum { get; protected set; }
Expand All @@ -34,6 +37,7 @@ protected BenchmarkEnvironmentInfo()
Configuration = RuntimeInformation.GetConfiguration();
HasRyuJit = RuntimeInformation.HasRyuJit();
JitInfo = RuntimeInformation.GetJitInfo();
HardwareIntrinsicsShort = HardwareIntrinsics.GetShortInfo();
IsServerGC = GCSettings.IsServerGC;
IsConcurrentGC = GCSettings.LatencyMode != GCLatencyMode.Batch;
HasAttachedDebugger = Debugger.IsAttached;
Expand All @@ -49,6 +53,7 @@ public virtual IEnumerable<string> ToFormattedString()
yield return "Benchmark Process Environment Information:";
yield return $"{RuntimeInfoPrefix}{GetRuntimeInfo()}";
yield return $"{GcInfoPrefix}{GetGcConcurrentFlag()} {GetGcServerFlag()}";
yield return $"{HardwareIntrinsicsPrefix}{HardwareIntrinsics.GetFullInfo(RuntimeInformation.GetCurrentPlatform())} {HardwareIntrinsics.GetVectorSize()}";
}

[PublicAPI] protected string GetConfigurationFlag() => Configuration == RuntimeInformation.Unknown || Configuration == RuntimeInformation.ReleaseConfigurationName
Expand All @@ -61,7 +66,7 @@ public virtual IEnumerable<string> ToFormattedString()

internal string GetRuntimeInfo()
{
string jitInfo = string.Join(" ", new[] { JitInfo, GetConfigurationFlag(), GetDebuggerFlag() }.Where(title => title != ""));
string jitInfo = string.Join(" ", new[] { JitInfo, HardwareIntrinsicsShort, GetConfigurationFlag(), GetDebuggerFlag() }.Where(title => title != ""));
return $"{RuntimeVersion}, {Architecture} {jitInfo}";
}

Expand Down
2 changes: 2 additions & 0 deletions src/BenchmarkDotNet/Exporters/Json/JsonExporterBase.cs
Expand Up @@ -58,6 +58,8 @@ public override void ExportToLog(Summary summary, ILogger logger)
{ "MethodTitle", report.BenchmarkCase.Descriptor.WorkloadMethodDisplayInfo },
{ "Parameters", report.BenchmarkCase.Parameters.PrintInfo },
{ "FullName", FullNameProvider.GetBenchmarkName(report.BenchmarkCase) }, // do NOT remove this property, it is used for xunit-performance migration
// 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
{ "HardwareIntrinsics", report.GetHardwareIntrinsicsInfo() ?? "" },
// { "Properties", r.Benchmark.Job.ToSet().ToDictionary(p => p.Name, p => p.Value) }, // TODO
{ "Statistics", report.ResultStatistics }
};
Expand Down
272 changes: 272 additions & 0 deletions src/BenchmarkDotNet/Portability/Cpu/HardwareIntrinsics.cs
@@ -0,0 +1,272 @@
using System.Collections.Generic;
using BenchmarkDotNet.Environments;
#if NET6_0_OR_GREATER
using System.Runtime.Intrinsics.X86;
using System.Runtime.Intrinsics.Arm;
using System.Numerics;
#elif NETSTANDARD2_0_OR_GREATER
using System;
#endif

namespace BenchmarkDotNet.Portability.Cpu
{
internal static class HardwareIntrinsics
{
internal static string GetVectorSize()
{
#if NET6_0_OR_GREATER
if (Vector.IsHardwareAccelerated)
return $"VectorSize={Vector<byte>.Count * 8}";
#endif
return string.Empty;
}

internal static string GetShortInfo()
{
if (IsX86Avx2Supported)
return "AVX2";
else if (IsX86AvxSupported)
return "AVX";
else if (IsX86Sse42Supported)
return "SSE4.2";
else if (IsX86Sse41Supported)
return "SSE4.1";
else if (IsX86Sse3Supported)
return "SSE3";
else if (IsX86Sse2Supported)
return "SSE2";
else if (IsX86SseSupported)
return "SSE";
else if (IsArmAdvSimdSupported)
return "AdvSIMD";
else if (IsArmBaseSupported)
return "base";
else
return string.Empty;
}

internal static string GetFullInfo(Platform platform)
{
return string.Join(",", GetCurrentProcessInstructionSets(platform));

static IEnumerable<string> GetCurrentProcessInstructionSets(Platform platform)
{
switch (platform)
{
case Platform.X86:
case Platform.X64:
if (IsX86Avx2Supported) yield return "AVX2";
else if (IsX86AvxSupported) yield return "AVX";
else if (IsX86Sse42Supported) yield return "SSE4.2";
else if (IsX86Sse41Supported) yield return "SSE4.1";
else if (IsX86Sse3Supported) yield return "SSE3";
else if (IsX86Sse2Supported) yield return "SSE2";
else if (IsX86SseSupported) yield return "SSE";

if (IsX86AesSupported) yield return "AES";
if (IsX86Bmi1Supported) yield return "BMI1";
if (IsX86Bmi2Supported) yield return "BMI2";
if (IsX86FmaSupported) yield return "FMA";
if (IsX86LzcntSupported) yield return "LZCNT";
if (IsX86PclmulqdqSupported) yield return "PCLMUL";
if (IsX86PopcntSupported) yield return "POPCNT";
if (IsX86AvxVnniSupported) yield return "AvxVnni";
// TODO: Add MOVBE when API is added.
break;
case Platform.Arm64:
if (IsArmAdvSimdSupported) yield return "AdvSIMD";

if (IsArmAesSupported) yield return "AES";
if (IsArmCrc32Supported) yield return "CRC32";
if (IsArmDpSupported) yield return "DP";
if (IsArmRdmSupported) yield return "RDM";
if (IsArmSha1Supported) yield return "SHA1";
if (IsArmSha256Supported) yield return "SHA256";
break;
default:
yield break;
}
}
}

internal static bool IsX86BaseSupported =>
#if NET6_0_OR_GREATER
X86Base.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.X86Base");
#endif

internal static bool IsX86SseSupported =>
#if NET6_0_OR_GREATER
Sse.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.Sse");
#endif

internal static bool IsX86Sse2Supported =>
#if NET6_0_OR_GREATER
Sse2.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.Sse2");
#endif

internal static bool IsX86Sse3Supported =>
#if NET6_0_OR_GREATER
Sse3.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.Sse3");
#endif

internal static bool IsX86Sse41Supported =>
#if NET6_0_OR_GREATER
Sse41.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.Sse41");
#endif

internal static bool IsX86Sse42Supported =>
#if NET6_0_OR_GREATER
Sse42.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.Sse42");
#endif

internal static bool IsX86AvxSupported =>
#if NET6_0_OR_GREATER
Avx.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.Avx");
#endif

internal static bool IsX86Avx2Supported =>
#if NET6_0_OR_GREATER
Avx2.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.Avx2");
#endif

internal static bool IsX86AesSupported =>
#if NET6_0_OR_GREATER
System.Runtime.Intrinsics.X86.Aes.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.Aes");
#endif

internal static bool IsX86Bmi1Supported =>
#if NET6_0_OR_GREATER
Bmi1.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.Bmi1");
#endif

internal static bool IsX86Bmi2Supported =>
#if NET6_0_OR_GREATER
Bmi2.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.Bmi2");
#endif

internal static bool IsX86FmaSupported =>
#if NET6_0_OR_GREATER
Fma.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.Fma");
#endif

internal static bool IsX86LzcntSupported =>
#if NET6_0_OR_GREATER
Lzcnt.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.Lzcnt");
#endif

internal static bool IsX86PclmulqdqSupported =>
#if NET6_0_OR_GREATER
Pclmulqdq.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.Pclmulqdq");
#endif

internal static bool IsX86PopcntSupported =>
#if NET6_0_OR_GREATER
Popcnt.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.Popcnt");
#endif

internal static bool IsX86AvxVnniSupported =>
#if NET6_0_OR_GREATER
#pragma warning disable CA2252 // This API requires opting into preview features
AvxVnni.IsSupported;
#pragma warning restore CA2252 // This API requires opting into preview features
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.AvxVnni");
#endif

internal static bool IsArmBaseSupported =>
#if NET6_0_OR_GREATER
ArmBase.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.Arm.ArmBase");
#endif

internal static bool IsArmAdvSimdSupported =>
#if NET6_0_OR_GREATER
AdvSimd.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.Arm.AdvSimd");
#endif

internal static bool IsArmAesSupported =>
#if NET6_0_OR_GREATER
System.Runtime.Intrinsics.Arm.Aes.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.Arm.Aes");
#endif

internal static bool IsArmCrc32Supported =>
#if NET6_0_OR_GREATER
Crc32.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.Arm.Crc32");
#endif

internal static bool IsArmDpSupported =>
#if NET6_0_OR_GREATER
Dp.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.Arm.Dp");
#endif

internal static bool IsArmRdmSupported =>
#if NET6_0_OR_GREATER
Rdm.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.Arm.Rdm");
#endif

internal static bool IsArmSha1Supported =>
#if NET6_0_OR_GREATER
Sha1.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.Arm.Sha1");
#endif

internal static bool IsArmSha256Supported =>
#if NET6_0_OR_GREATER
Sha256.IsSupported;
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.Arm.Sha256");
#endif

#if NETSTANDARD
private static bool GetIsSupported(string typeName)
{
Type type = Type.GetType(typeName);
if (type == null) return false;

return (bool)type.GetProperty("IsSupported", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static).GetValue(null, null);
}
#endif
}
}
1 change: 1 addition & 0 deletions src/BenchmarkDotNet/Portability/RuntimeInformation.cs
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Management;
using System.Numerics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
Expand Down
4 changes: 4 additions & 0 deletions src/BenchmarkDotNet/Reports/BenchmarkReportExtensions.cs
Expand Up @@ -8,13 +8,17 @@ public static class BenchmarkReportExtensions
{
private const string DisplayedRuntimeInfoPrefix = "// " + BenchmarkEnvironmentInfo.RuntimeInfoPrefix;
private const string DisplayedGcInfoPrefix = "// " + BenchmarkEnvironmentInfo.GcInfoPrefix;
private const string DisplayedHardwareIntrinsicsPrefix = "// " + BenchmarkEnvironmentInfo.HardwareIntrinsicsPrefix;

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

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

[CanBeNull]
public static string GetHardwareIntrinsicsInfo(this BenchmarkReport report) => report.GetInfoFromOutput(DisplayedHardwareIntrinsicsPrefix);

[CanBeNull]
private static string GetInfoFromOutput(this BenchmarkReport report, string prefix)
{
Expand Down

0 comments on commit 32ddeb5

Please sign in to comment.