Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BenchmarkDotNet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<Project Path="tests/BenchmarkDotNet.IntegrationTests.CustomPaths/BenchmarkDotNet.IntegrationTests.CustomPaths.csproj" />
<Project Path="tests/BenchmarkDotNet.IntegrationTests.DisabledOptimizations/BenchmarkDotNet.IntegrationTests.DisabledOptimizations.csproj" />
<Project Path="tests/BenchmarkDotNet.IntegrationTests.EnabledOptimizations/BenchmarkDotNet.IntegrationTests.EnabledOptimizations.csproj" />
<Project Path="tests/BenchmarkDotNet.IntegrationTests.ETW/BenchmarkDotNet.IntegrationTests.ETW.csproj" />
<Project Path="tests/BenchmarkDotNet.IntegrationTests.FSharp/BenchmarkDotNet.IntegrationTests.FSharp.fsproj" />
<Project Path="tests/BenchmarkDotNet.IntegrationTests.ManualRunning.MultipleFrameworks/BenchmarkDotNet.IntegrationTests.ManualRunning.MultipleFrameworks.csproj" />
<Project Path="tests/BenchmarkDotNet.IntegrationTests.ManualRunning/BenchmarkDotNet.IntegrationTests.ManualRunning.csproj" />
Expand Down
6 changes: 6 additions & 0 deletions samples/BenchmarkDotNet.Samples/IntroHardwareCounters.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;

namespace BenchmarkDotNet.Samples
{
Expand All @@ -8,6 +11,9 @@ namespace BenchmarkDotNet.Samples
HardwareCounter.BranchInstructions)]
public class IntroHardwareCounters
{
public static void Run() =>
BenchmarkRunner.Run<IntroHardwareCounters>(DefaultConfig.Instance.AddJob(Job.Dry));

private const int N = 32767;
private readonly int[] sorted, unsorted;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;

namespace BenchmarkDotNet.Samples
{
/// <summary>
/// Demonstration of benchmark setup using <see cref="HardwareCounter"/>, which cannot be automatically matched to your configuration.
/// </summary>
/// <remarks>
/// Before starting, make sure the values from <see cref="HardwareCounterProfile"/> are available on your configuration.
/// You can get a list of available counters using the command `tracelog.exe -profilesources Help`.
/// </remarks>
[HardwareCounters(HardwareCounter.CacheMisses, HardwareCounter.BranchInstructions)]
public class IntroHardwareCountersWithProfile
{
/// <summary>
/// The profile replaces the value from <see cref="HardwareCounter"/> with the one expected by your configuration.
/// </summary>
class HardwareCounterProfile : IHardwareCounterProfile
{
public IEnumerable<string> GetVariants(HardwareCounter hardwareCounter)
{
if (hardwareCounter == HardwareCounter.CacheMisses)
{
// Example for `AMD Ryzen 7 5700G`
yield return "IcacheMisses";
yield return "DcacheMisses";
}
else
{
yield return hardwareCounter.ToString();
}
}
}

public static void Run() =>
BenchmarkRunner.Run<IntroHardwareCountersWithProfile>(DefaultConfig.Instance
.AddJob(Job.Dry)
.WithHardwareCounterProfile(new HardwareCounterProfile()));

private const int N = 32767;
private readonly int[] sorted, unsorted;

public IntroHardwareCountersWithProfile()
{
var random = new Random(0);
unsorted = new int[N];
sorted = new int[N];
for (int i = 0; i < N; i++)
sorted[i] = unsorted[i] = random.Next(256);
Array.Sort(sorted);
}

private static int Branch(int[] data)
{
int sum = 0;
for (int i = 0; i < N; i++)
if (data[i] >= 128)
sum += data[i];
return sum;
}

private static int Branchless(int[] data)
{
int sum = 0;
for (int i = 0; i < N; i++)
{
int t = (data[i] - 128) >> 31;
sum += ~t & data[i];
}
return sum;
}

[Benchmark]
public int SortedBranch() => Branch(sorted);

[Benchmark]
public int UnsortedBranch() => Branch(unsorted);

[Benchmark]
public int SortedBranchless() => Branchless(sorted);

[Benchmark]
public int UnsortedBranchless() => Branchless(unsorted);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using BenchmarkDotNet.Diagnosers;
using Microsoft.Diagnostics.Tracing.Session;

namespace BenchmarkDotNet.Diagnostics.Windows;

public sealed class DefaultHardwareCounterProvider : IHardwareCounterProvider
{
public static readonly DefaultHardwareCounterProvider Instance = new ();

public Dictionary<string, ProfileSourceInfo> GetAvailableCounters() => TraceEventProfileSources.GetInfo();

public void Configure(IEnumerable<PreciseMachineCounter> machineCounters)
{
TraceEventProfileSources.Set( // it's a must have to get the events enabled!!
machineCounters.Select(counter => counter.ProfileSourceId).ToArray(),
machineCounters.Select(counter => counter.Interval).ToArray());
}
}
12 changes: 8 additions & 4 deletions src/BenchmarkDotNet.Diagnostics.Windows/EtwProfiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public class EtwProfiler : IDiagnoser, IHardwareCountersDiagnoser, IProfiler
private readonly Dictionary<BenchmarkCase, string> benchmarkToEtlFile;
private readonly Dictionary<BenchmarkCase, PreciseMachineCounter[]> benchmarkToCounters;

public IHardwareCounterProvider HardwareCounterProvider { get; set; } = new DefaultHardwareCounterProvider();

private Session kernelSession = default!;
private Session userSession = default!;
private Session heapSession = default!;
Expand Down Expand Up @@ -55,7 +57,7 @@ public EtwProfiler(EtwProfilerConfig config)
public RunMode GetRunMode(BenchmarkCase benchmarkCase) => runMode;

public IAsyncEnumerable<ValidationError> ValidateAsync(ValidationParameters validationParameters)
=> HardwareCounters.Validate(validationParameters, mandatory: false).ToAsyncEnumerable();
=> HardwareCounters.Validate(validationParameters, HardwareCounterProvider, mandatory: false).ToAsyncEnumerable();

public ValueTask HandleAsync(HostSignal signal, DiagnoserActionParameters parameters, CancellationToken cancellationToken)
{
Expand All @@ -64,7 +66,7 @@ public ValueTask HandleAsync(HostSignal signal, DiagnoserActionParameters parame
Start(parameters);
else if (signal == HostSignal.AfterProcessExit)
Stop(parameters);
return new();
return new ();
}

public IEnumerable<Metric> ProcessResults(DiagnoserResults results)
Expand All @@ -90,13 +92,15 @@ public void DisplayResults(ILogger logger)

private void Start(DiagnoserActionParameters parameters)
{
var profileSourceInfos = HardwareCounterProvider.GetAvailableCounters();
var counters = benchmarkToCounters[parameters.BenchmarkCase] = parameters.Config
.GetHardwareCounters()
.Select(counter => HardwareCounters.FromCounter(counter, config.IntervalSelectors.TryGetValue(counter, out var selector) ? selector : GetInterval))
.SelectMany(counter => HardwareCounters.FromCounter(counter, parameters.Config.HardwareCounterProfile, profileSourceInfos,
config.IntervalSelectors.TryGetValue(counter, out var selector) ? selector : GetInterval))
.ToArray();

if (counters.Any()) // we need to enable the counters before starting the kernel session
HardwareCounters.Enable(counters);
HardwareCounterProvider.Configure(counters);

try
{
Expand Down
67 changes: 31 additions & 36 deletions src/BenchmarkDotNet.Diagnostics.Windows/HardwareCounters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,10 @@ namespace BenchmarkDotNet.Diagnostics.Windows
{
public static class HardwareCounters
{
private static readonly Dictionary<HardwareCounter, string> EtwTranslations
= new Dictionary<HardwareCounter, string>
{
{ HardwareCounter.Timer, "Timer" },
{ HardwareCounter.TotalIssues, "TotalIssues" },
{ HardwareCounter.BranchInstructions, "BranchInstructions" },
{ HardwareCounter.CacheMisses, "CacheMisses" },
{ HardwareCounter.BranchMispredictions, "BranchMispredictions" },
{ HardwareCounter.TotalCycles, "TotalCycles" },
{ HardwareCounter.UnhaltedCoreCycles, "UnhaltedCoreCycles" },
{ HardwareCounter.InstructionRetired, "InstructionRetired" },
{ HardwareCounter.UnhaltedReferenceCycles, "UnhaltedReferenceCycles" },
{ HardwareCounter.LlcReference, "LLCReference" },
{ HardwareCounter.LlcMisses, "LLCMisses" },
{ HardwareCounter.BranchInstructionRetired, "BranchInstructionRetired" },
{ HardwareCounter.BranchMispredictsRetired, "BranchMispredictsRetired" }
};

public static IEnumerable<ValidationError> Validate(ValidationParameters validationParameters, bool mandatory)
public static IEnumerable<ValidationError> Validate(
ValidationParameters validationParameters,
IHardwareCounterProvider provider,
bool mandatory)
{
if (!OsDetector.IsWindows())
{
Expand All @@ -43,21 +28,30 @@ public static IEnumerable<ValidationError> Validate(ValidationParameters validat
if (TraceEventSession.IsElevated() != true)
yield return new ValidationError(true, "Must be elevated (Admin) to use ETW Kernel Session (required for Hardware Counters and EtwProfiler).");

var availableCpuCounters = TraceEventProfileSources.GetInfo();
var availableCpuCounters = provider.GetAvailableCounters();

foreach (var hardwareCounter in validationParameters.Config.GetHardwareCounters())
{
if (!EtwTranslations.TryGetValue(hardwareCounter, out string counterName))
string[] counterVariants = validationParameters.Config.HardwareCounterProfile.GetVariants(hardwareCounter).ToArray();

if (counterVariants.Length == 0)
{
yield return new ValidationError(true,
$"Counter {hardwareCounter} not recognized. " +
$"Please make sure that you are using counter available on your machine. " +
$"You can get the list of available counters by running `tracelog.exe -profilesources Help`");
$"Please ensure that you are using a counter that is supported by your hardware counter provider. ");
continue;
}

if (!availableCpuCounters.ContainsKey(counterName))
yield return new ValidationError(true, $"The counter {counterName} is not available. Please make sure you are Windows 8+ without Hyper-V");
foreach (string counterVariant in counterVariants)
{
if (!availableCpuCounters.ContainsKey(counterVariant))
{
yield return new ValidationError(true,
$"The counter {counterVariant} is not available. " +
$"Please make sure you are Windows 8+ without Hyper-V and that you are using counter available on your machine. " +
$"You can get the list of available counters by running `tracelog.exe -profilesources Help`");
}
}
}

foreach (var benchmark in validationParameters.Benchmarks)
Expand All @@ -69,18 +63,19 @@ public static IEnumerable<ValidationError> Validate(ValidationParameters validat
}
}

internal static PreciseMachineCounter FromCounter(HardwareCounter counter, Func<ProfileSourceInfo, int> intervalSelector)
public static IEnumerable<PreciseMachineCounter> FromCounter(
HardwareCounter counter,
IHardwareCounterProfile profile,
IReadOnlyDictionary<string, ProfileSourceInfo> profileSourceInfos,
Func<ProfileSourceInfo, int> intervalSelector)
{
var profileSource = TraceEventProfileSources.GetInfo()[EtwTranslations[counter]]; // it can't fail, diagnoser validates that first

return new PreciseMachineCounter(profileSource.ID, profileSource.Name, counter, intervalSelector(profileSource));
}

internal static void Enable(IEnumerable<PreciseMachineCounter> counters)
{
TraceEventProfileSources.Set( // it's a must have to get the events enabled!!
counters.Select(counter => counter.ProfileSourceId).ToArray(),
counters.Select(counter => counter.Interval).ToArray());
foreach (var counterVariant in profile.GetVariants(counter))
{
if (profileSourceInfos.TryGetValue(counterVariant, out var profileSource))
{
yield return new PreciseMachineCounter(profileSource.ID, profileSource.Name, counter, intervalSelector(profileSource));
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using BenchmarkDotNet.Diagnosers;
using Microsoft.Diagnostics.Tracing.Session;

namespace BenchmarkDotNet.Diagnostics.Windows;

public interface IHardwareCounterProvider
{
Dictionary<string, ProfileSourceInfo> GetAvailableCounters();

void Configure(IEnumerable<PreciseMachineCounter> machineCounters);
}
3 changes: 3 additions & 0 deletions src/BenchmarkDotNet/Configs/ConfigExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public static class ConfigExtensions

[PublicAPI] public static ManualConfig WithOrderer(this IConfig config, IOrderer orderer) => config.With(m => m.WithOrderer(orderer));

[PublicAPI] public static ManualConfig WithHardwareCounterProfile(this IConfig config, IHardwareCounterProfile newHardwareCounterProfile)
=> config.With(c => c.WithHardwareCounterProfile(newHardwareCounterProfile));

[PublicAPI] public static ManualConfig AddHardwareCounters(this IConfig config, params HardwareCounter[] counters) => config.With(c => c.AddHardwareCounters(counters));

[PublicAPI] public static ManualConfig AddFilter(this IConfig config, params IFilter[] filters) => config.With(c => c.AddFilter(filters));
Expand Down
1 change: 1 addition & 0 deletions src/BenchmarkDotNet/Configs/DebugConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public abstract class DebugConfig : IConfig

public IOrderer Orderer => DefaultOrderer.Instance;
public ICategoryDiscoverer? CategoryDiscoverer => DefaultCategoryDiscoverer.Instance;
public IHardwareCounterProfile? HardwareCounterProfile => null;
public SummaryStyle SummaryStyle => SummaryStyle.Default;
public ConfigUnionRule UnionRule => ConfigUnionRule.Union;
public TimeSpan BuildTimeout => DefaultConfig.Instance.BuildTimeout;
Expand Down
3 changes: 3 additions & 0 deletions src/BenchmarkDotNet/Configs/DefaultConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,11 @@ public IEnumerable<IValidator> GetValidators()
}

public IOrderer? Orderer => null;

public ICategoryDiscoverer? CategoryDiscoverer => null;

public IHardwareCounterProfile? HardwareCounterProfile => null;

public ConfigUnionRule UnionRule => ConfigUnionRule.Union;

public CultureInfo? CultureInfo => null;
Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Configs/IConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public interface IConfig

IOrderer? Orderer { get; }
ICategoryDiscoverer? CategoryDiscoverer { get; }

IHardwareCounterProfile? HardwareCounterProfile { get; }
SummaryStyle? SummaryStyle { get; }

ConfigUnionRule UnionRule { get; }
Expand Down
3 changes: 3 additions & 0 deletions src/BenchmarkDotNet/Configs/ImmutableConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ internal ImmutableConfig(
CultureInfo cultureInfo,
IOrderer orderer,
ICategoryDiscoverer categoryDiscoverer,
IHardwareCounterProfile hardwareCounterProfile,
SummaryStyle summaryStyle,
ConfigOptions options,
TimeSpan buildTimeout,
Expand All @@ -73,6 +74,7 @@ internal ImmutableConfig(
CultureInfo = cultureInfo;
Orderer = orderer;
CategoryDiscoverer = categoryDiscoverer;
HardwareCounterProfile = hardwareCounterProfile;
SummaryStyle = summaryStyle;
Options = options;
BuildTimeout = buildTimeout;
Expand All @@ -86,6 +88,7 @@ internal ImmutableConfig(
public ConfigOptions Options { get; }
public IOrderer Orderer { get; }
public ICategoryDiscoverer CategoryDiscoverer { get; }
public IHardwareCounterProfile HardwareCounterProfile { get; }
public SummaryStyle SummaryStyle { get; }
public TimeSpan BuildTimeout { get; }
public WakeLockType WakeLock { get; }
Expand Down
1 change: 1 addition & 0 deletions src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public static ImmutableConfig Create(IConfig source)
source.CultureInfo ?? DefaultCultureInfo.Instance,
source.Orderer ?? DefaultOrderer.Instance,
source.CategoryDiscoverer ?? DefaultCategoryDiscoverer.Instance,
source.HardwareCounterProfile ?? DefaultHardwareCounterProfile.Instance,
source.SummaryStyle ?? SummaryStyle.Default,
source.Options,
source.BuildTimeout,
Expand Down
8 changes: 8 additions & 0 deletions src/BenchmarkDotNet/Configs/ManualConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class ManualConfig : IConfig
[PublicAPI] public CultureInfo? CultureInfo { get; set; }
[PublicAPI] public IOrderer? Orderer { get; set; }
[PublicAPI] public ICategoryDiscoverer? CategoryDiscoverer { get; set; }
[PublicAPI] public IHardwareCounterProfile? HardwareCounterProfile { get; set; }
[PublicAPI] public SummaryStyle? SummaryStyle { get; set; }
[PublicAPI] public TimeSpan BuildTimeout { get; set; } = DefaultConfig.Instance.BuildTimeout;
[PublicAPI] public WakeLockType WakeLock { get; set; } = DefaultConfig.Instance.WakeLock;
Expand Down Expand Up @@ -100,6 +101,12 @@ public ManualConfig WithCategoryDiscoverer(ICategoryDiscoverer categoryDiscovere
return this;
}

public ManualConfig WithHardwareCounterProfile(IHardwareCounterProfile hardwareCounterProfile)
{
HardwareCounterProfile = hardwareCounterProfile;
return this;
}

public ManualConfig WithBuildTimeout(TimeSpan buildTimeout)
{
BuildTimeout = buildTimeout;
Expand Down Expand Up @@ -220,6 +227,7 @@ public void Add(IConfig config)
eventProcessors.AddRangeDistinct(config.GetEventProcessors());
Orderer = config.Orderer ?? Orderer;
CategoryDiscoverer = config.CategoryDiscoverer ?? CategoryDiscoverer;
HardwareCounterProfile = config.HardwareCounterProfile ?? HardwareCounterProfile;
ArtifactsPath = config.ArtifactsPath ?? ArtifactsPath;
CultureInfo = config.CultureInfo ?? CultureInfo;
SummaryStyle = config.SummaryStyle ?? SummaryStyle;
Expand Down
Loading
Loading