Skip to content

Commit

Permalink
Refactor CpuInfo detection, fix #2577
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreyAkinshin committed Aug 25, 2024
1 parent d2f73e8 commit 64b3d85
Show file tree
Hide file tree
Showing 33 changed files with 854 additions and 815 deletions.
239 changes: 119 additions & 120 deletions src/BenchmarkDotNet/Environments/ProcessorBrandStringHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,153 +3,152 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.RegularExpressions;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Portability.Cpu;
using Perfolizer.Horology;

namespace BenchmarkDotNet.Environments
namespace BenchmarkDotNet.Environments;

public static class ProcessorBrandStringHelper
{
public static class ProcessorBrandStringHelper
/// <summary>
/// Transform a processor brand string to a nice form for summary.
/// </summary>
/// <param name="cpuInfo">The CPU information</param>
/// <param name="includeMaxFrequency">Whether to include determined max frequency information</param>
/// <returns>Prettified version</returns>
public static string Prettify(CpuInfo? cpuInfo, bool includeMaxFrequency = false)
{
/// <summary>
/// Transform a processor brand string to a nice form for summary.
/// </summary>
/// <param name="cpuInfo">The CPU information</param>
/// <param name="includeMaxFrequency">Whether to include determined max frequency information</param>
/// <returns>Prettified version</returns>
public static string Prettify(CpuInfo cpuInfo, bool includeMaxFrequency = false)
{
if (cpuInfo == null || string.IsNullOrEmpty(cpuInfo.ProcessorName))
{
return "Unknown processor";
}
string? processorName = cpuInfo?.ProcessorName;
if (processorName == null || processorName.IsBlank())
return "Unknown processor";

// Remove parts which don't provide any useful information for user
var processorName = cpuInfo.ProcessorName.Replace("@", "").Replace("(R)", "").Replace("(TM)", "");
// Remove parts which don't provide any useful information for user
processorName = processorName.Replace("@", "").Replace("(R)", "").Replace("(TM)", "");

// If we have found physical core(s), we can safely assume we can drop extra info from brand
if (cpuInfo.PhysicalCoreCount.HasValue && cpuInfo.PhysicalCoreCount.Value > 0)
processorName = Regex.Replace(processorName, @"(\w+?-Core Processor)", "").Trim();
// If we have found physical core(s), we can safely assume we can drop extra info from brand
if (cpuInfo.PhysicalCoreCount is > 0)
processorName = Regex.Replace(processorName, @"(\w+?-Core Processor)", "").Trim();

string frequencyString = GetBrandStyledActualFrequency(cpuInfo.NominalFrequency);
if (includeMaxFrequency && frequencyString != null && !processorName.Contains(frequencyString))
{
// show Max only if there's already a frequency name to differentiate the two
string maxFrequency = processorName.Contains("Hz") ? $"(Max: {frequencyString})" : frequencyString;
processorName = $"{processorName} {maxFrequency}";
}
string frequencyString = GetBrandStyledNominalFrequency(cpuInfo.NominalFrequency);
if (includeMaxFrequency && frequencyString != null && !processorName.Contains(frequencyString))
{
// show Max only if there's already a frequency name to differentiate the two
string maxFrequency = processorName.Contains("Hz") ? $"(Max: {frequencyString})" : frequencyString;
processorName = $"{processorName} {maxFrequency}";
}

// Remove double spaces
processorName = Regex.Replace(processorName.Trim(), @"\s+", " ");
// Remove double spaces
processorName = Regex.Replace(processorName.Trim(), @"\s+", " ");

// Add microarchitecture name if known
string microarchitecture = ParseMicroarchitecture(processorName);
if (microarchitecture != null)
processorName = $"{processorName} ({microarchitecture})";
// Add microarchitecture name if known
string microarchitecture = ParseMicroarchitecture(processorName);
if (microarchitecture != null)
processorName = $"{processorName} ({microarchitecture})";

return processorName;
}
return processorName;
}

/// <summary>
/// Presents actual processor's frequency into brand string format
/// </summary>
/// <param name="frequency"></param>
private static string GetBrandStyledActualFrequency(Frequency? frequency)
{
if (frequency == null)
return null;
return $"{frequency.Value.ToGHz().ToString("N2", DefaultCultureInfo.Instance)}GHz";
}
/// <summary>
/// Presents actual processor's frequency into brand string format
/// </summary>
/// <param name="frequency"></param>
private static string? GetBrandStyledNominalFrequency(Frequency? frequency)
{
if (frequency == null)
return null;
return $"{frequency.Value.ToGHz().ToString("N2", DefaultCultureInfo.Instance)}GHz";
}

/// <summary>
/// Parse a processor name and tries to return a microarchitecture name.
/// Works only for well-known microarchitectures.
/// </summary>
private static string? ParseMicroarchitecture(string processorName)
/// <summary>
/// Parse a processor name and tries to return a microarchitecture name.
/// Works only for well-known microarchitectures.
/// </summary>
private static string? ParseMicroarchitecture(string processorName)
{
if (processorName.StartsWith("Intel Core"))
{
if (processorName.StartsWith("Intel Core"))
string model = processorName.Substring("Intel Core".Length).Trim();

// Core i3/5/7/9
if (
model.Length > 4 &&
model[0] == 'i' &&
(model[1] == '3' || model[1] == '5' || model[1] == '7' || model[1] == '9') &&
(model[2] == '-' || model[2] == ' '))
{
string model = processorName.Substring("Intel Core".Length).Trim();

// Core i3/5/7/9
if (
model.Length > 4 &&
model[0] == 'i' &&
(model[1] == '3' || model[1] == '5' || model[1] == '7' || model[1] == '9') &&
(model[2] == '-' || model[2] == ' '))
{
string modelNumber = model.Substring(3);
if (modelNumber.StartsWith("CPU"))
modelNumber = modelNumber.Substring(3).Trim();
if (modelNumber.Contains("CPU"))
modelNumber = modelNumber.Substring(0, modelNumber.IndexOf("CPU", StringComparison.Ordinal)).Trim();
return ParseIntelCoreMicroarchitecture(modelNumber);
}
string modelNumber = model.Substring(3);
if (modelNumber.StartsWith("CPU"))
modelNumber = modelNumber.Substring(3).Trim();
if (modelNumber.Contains("CPU"))
modelNumber = modelNumber.Substring(0, modelNumber.IndexOf("CPU", StringComparison.Ordinal)).Trim();
return ParseIntelCoreMicroarchitecture(modelNumber);
}

return null;
}

private static readonly Lazy<Dictionary<string, string>> KnownMicroarchitectures = new Lazy<Dictionary<string, string>>(() =>
return null;
}

private static readonly Lazy<Dictionary<string, string>> KnownMicroarchitectures = new Lazy<Dictionary<string, string>>(() =>
{
var data = ResourceHelper.LoadResource("BenchmarkDotNet.Environments.microarchitectures.txt").Split('\r', '\n');
var dictionary = new Dictionary<string, string>();
string? currentMicroarchitecture = null;
foreach (string line in data)
{
var data = ResourceHelper.LoadResource("BenchmarkDotNet.Environments.microarchitectures.txt").Split('\r', '\n');
var dictionary = new Dictionary<string, string>();
string? currentMicroarchitecture = null;
foreach (string line in data)
if (line.StartsWith("//") || string.IsNullOrWhiteSpace(line))
continue;
if (line.StartsWith("#"))
{
if (line.StartsWith("//") || string.IsNullOrWhiteSpace(line))
continue;
if (line.StartsWith("#"))
{
currentMicroarchitecture = line.Substring(1).Trim();
continue;
}
string modelNumber = line.Trim();
if (dictionary.ContainsKey(modelNumber))
throw new Exception($"{modelNumber} is defined twice in microarchitectures.txt");
if (currentMicroarchitecture == null)
throw new Exception($"{modelNumber} doesn't have defined microarchitecture in microarchitectures.txt");
dictionary[modelNumber] = currentMicroarchitecture;
currentMicroarchitecture = line.Substring(1).Trim();
continue;
}
return dictionary;
});
string modelNumber = line.Trim();
if (dictionary.ContainsKey(modelNumber))
throw new Exception($"{modelNumber} is defined twice in microarchitectures.txt");
if (currentMicroarchitecture == null)
throw new Exception($"{modelNumber} doesn't have defined microarchitecture in microarchitectures.txt");
dictionary[modelNumber] = currentMicroarchitecture;
}
// see http://www.intel.com/content/www/us/en/processors/processor-numbers.html
[SuppressMessage("ReSharper", "StringLiteralTypo")]
internal static string? ParseIntelCoreMicroarchitecture(string modelNumber)
{
if (KnownMicroarchitectures.Value.TryGetValue(modelNumber, out string? microarchitecture))
return microarchitecture;
return dictionary;
});

// see http://www.intel.com/content/www/us/en/processors/processor-numbers.html
[SuppressMessage("ReSharper", "StringLiteralTypo")]
internal static string? ParseIntelCoreMicroarchitecture(string modelNumber)
{
if (KnownMicroarchitectures.Value.TryGetValue(modelNumber, out string? microarchitecture))
return microarchitecture;

if (modelNumber.Length >= 3 && modelNumber.Substring(0, 3).All(char.IsDigit) &&
(modelNumber.Length == 3 || !char.IsDigit(modelNumber[3])))
return "Nehalem";
if (modelNumber.Length >= 4 && modelNumber.Substring(0, 4).All(char.IsDigit))
if (modelNumber.Length >= 3 && modelNumber.Substring(0, 3).All(char.IsDigit) &&
(modelNumber.Length == 3 || !char.IsDigit(modelNumber[3])))
return "Nehalem";
if (modelNumber.Length >= 4 && modelNumber.Substring(0, 4).All(char.IsDigit))
{
char generation = modelNumber[0];
switch (generation)
{
char generation = modelNumber[0];
switch (generation)
{
case '2':
return "Sandy Bridge";
case '3':
return "Ivy Bridge";
case '4':
return "Haswell";
case '5':
return "Broadwell";
case '6':
return "Skylake";
case '7':
return "Kaby Lake";
case '8':
return "Coffee Lake";
default:
return null;
}
case '2':
return "Sandy Bridge";
case '3':
return "Ivy Bridge";
case '4':
return "Haswell";
case '5':
return "Broadwell";
case '6':
return "Skylake";
case '7':
return "Kaby Lake";
case '8':
return "Coffee Lake";
default:
return null;
}
return null;
}
return null;
}
}
18 changes: 18 additions & 0 deletions src/BenchmarkDotNet/Portability/Cpu/CompositeCpuInfoDetector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Linq;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Portability.Cpu.Linux;
using BenchmarkDotNet.Portability.Cpu.macOS;
using BenchmarkDotNet.Portability.Cpu.Windows;

namespace BenchmarkDotNet.Portability.Cpu;

internal class CompositeCpuInfoDetector(params ICpuInfoDetector[] detectors) : ICpuInfoDetector
{
public bool IsApplicable() => detectors.Any(loader => loader.IsApplicable());

public CpuInfo? Detect() => detectors
.Where(loader => loader.IsApplicable())
.Select(loader => loader.Detect())
.WhereNotNull()
.FirstOrDefault();
}
60 changes: 28 additions & 32 deletions src/BenchmarkDotNet/Portability/Cpu/CpuInfo.cs
Original file line number Diff line number Diff line change
@@ -1,37 +1,33 @@
using Perfolizer.Horology;
using BenchmarkDotNet.Portability.Cpu.Linux;
using BenchmarkDotNet.Portability.Cpu.macOS;
using BenchmarkDotNet.Portability.Cpu.Windows;
using Perfolizer.Horology;

namespace BenchmarkDotNet.Portability.Cpu
namespace BenchmarkDotNet.Portability.Cpu;

public class CpuInfo(
string? processorName,
int? physicalProcessorCount,
int? physicalCoreCount,
int? logicalCoreCount,
Frequency? nominalFrequency,
Frequency? maxFrequency = null)
{
public class CpuInfo
{
public string ProcessorName { get; }
public int? PhysicalProcessorCount { get; }
public int? PhysicalCoreCount { get; }
public int? LogicalCoreCount { get; }
public Frequency? NominalFrequency { get; }
public Frequency? MinFrequency { get; }
public Frequency? MaxFrequency { get; }
public static readonly CpuInfo Empty = new (null, null, null, null, null, null);
public static CpuInfo FromName(string processorName) => new (processorName, null, null, null, null);
public static CpuInfo FromNameAndFrequency(string processorName, Frequency nominalFrequency) => new (processorName, null, null, null, nominalFrequency);

private static readonly CompositeCpuInfoDetector Detector = new (
new WindowsCpuInfoDetector(),
new LinuxCpuInfoDetector(),
new MacOsCpuInfoDetector());

internal CpuInfo(string processorName, Frequency? nominalFrequency)
: this(processorName, null, null, null, nominalFrequency, null, null)
{
}
public static CpuInfo? DetectCurrent() => Detector.Detect();

public CpuInfo(string processorName,
int? physicalProcessorCount,
int? physicalCoreCount,
int? logicalCoreCount,
Frequency? nominalFrequency,
Frequency? minFrequency,
Frequency? maxFrequency)
{
ProcessorName = processorName;
PhysicalProcessorCount = physicalProcessorCount;
PhysicalCoreCount = physicalCoreCount;
LogicalCoreCount = logicalCoreCount;
NominalFrequency = nominalFrequency;
MinFrequency = minFrequency;
MaxFrequency = maxFrequency;
}
}
public string? ProcessorName { get; } = processorName;
public int? PhysicalProcessorCount { get; } = physicalProcessorCount;
public int? PhysicalCoreCount { get; } = physicalCoreCount;
public int? LogicalCoreCount { get; } = logicalCoreCount;
public Frequency? NominalFrequency { get; } = nominalFrequency ?? maxFrequency;
public Frequency? MaxFrequency { get; } = maxFrequency ?? nominalFrequency;
}
Loading

0 comments on commit 64b3d85

Please sign in to comment.