Skip to content

Commit 64b3d85

Browse files
Refactor CpuInfo detection, fix #2577
1 parent d2f73e8 commit 64b3d85

33 files changed

+854
-815
lines changed

src/BenchmarkDotNet/Environments/ProcessorBrandStringHelper.cs

+119-120
Original file line numberDiff line numberDiff line change
@@ -3,153 +3,152 @@
33
using System.Diagnostics.CodeAnalysis;
44
using System.Linq;
55
using System.Text.RegularExpressions;
6+
using BenchmarkDotNet.Extensions;
67
using BenchmarkDotNet.Helpers;
78
using BenchmarkDotNet.Portability.Cpu;
89
using Perfolizer.Horology;
910

10-
namespace BenchmarkDotNet.Environments
11+
namespace BenchmarkDotNet.Environments;
12+
13+
public static class ProcessorBrandStringHelper
1114
{
12-
public static class ProcessorBrandStringHelper
15+
/// <summary>
16+
/// Transform a processor brand string to a nice form for summary.
17+
/// </summary>
18+
/// <param name="cpuInfo">The CPU information</param>
19+
/// <param name="includeMaxFrequency">Whether to include determined max frequency information</param>
20+
/// <returns>Prettified version</returns>
21+
public static string Prettify(CpuInfo? cpuInfo, bool includeMaxFrequency = false)
1322
{
14-
/// <summary>
15-
/// Transform a processor brand string to a nice form for summary.
16-
/// </summary>
17-
/// <param name="cpuInfo">The CPU information</param>
18-
/// <param name="includeMaxFrequency">Whether to include determined max frequency information</param>
19-
/// <returns>Prettified version</returns>
20-
public static string Prettify(CpuInfo cpuInfo, bool includeMaxFrequency = false)
21-
{
22-
if (cpuInfo == null || string.IsNullOrEmpty(cpuInfo.ProcessorName))
23-
{
24-
return "Unknown processor";
25-
}
23+
string? processorName = cpuInfo?.ProcessorName;
24+
if (processorName == null || processorName.IsBlank())
25+
return "Unknown processor";
2626

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

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

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

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

45-
// Add microarchitecture name if known
46-
string microarchitecture = ParseMicroarchitecture(processorName);
47-
if (microarchitecture != null)
48-
processorName = $"{processorName} ({microarchitecture})";
45+
// Add microarchitecture name if known
46+
string microarchitecture = ParseMicroarchitecture(processorName);
47+
if (microarchitecture != null)
48+
processorName = $"{processorName} ({microarchitecture})";
4949

50-
return processorName;
51-
}
50+
return processorName;
51+
}
5252

53-
/// <summary>
54-
/// Presents actual processor's frequency into brand string format
55-
/// </summary>
56-
/// <param name="frequency"></param>
57-
private static string GetBrandStyledActualFrequency(Frequency? frequency)
58-
{
59-
if (frequency == null)
60-
return null;
61-
return $"{frequency.Value.ToGHz().ToString("N2", DefaultCultureInfo.Instance)}GHz";
62-
}
53+
/// <summary>
54+
/// Presents actual processor's frequency into brand string format
55+
/// </summary>
56+
/// <param name="frequency"></param>
57+
private static string? GetBrandStyledNominalFrequency(Frequency? frequency)
58+
{
59+
if (frequency == null)
60+
return null;
61+
return $"{frequency.Value.ToGHz().ToString("N2", DefaultCultureInfo.Instance)}GHz";
62+
}
6363

64-
/// <summary>
65-
/// Parse a processor name and tries to return a microarchitecture name.
66-
/// Works only for well-known microarchitectures.
67-
/// </summary>
68-
private static string? ParseMicroarchitecture(string processorName)
64+
/// <summary>
65+
/// Parse a processor name and tries to return a microarchitecture name.
66+
/// Works only for well-known microarchitectures.
67+
/// </summary>
68+
private static string? ParseMicroarchitecture(string processorName)
69+
{
70+
if (processorName.StartsWith("Intel Core"))
6971
{
70-
if (processorName.StartsWith("Intel Core"))
72+
string model = processorName.Substring("Intel Core".Length).Trim();
73+
74+
// Core i3/5/7/9
75+
if (
76+
model.Length > 4 &&
77+
model[0] == 'i' &&
78+
(model[1] == '3' || model[1] == '5' || model[1] == '7' || model[1] == '9') &&
79+
(model[2] == '-' || model[2] == ' '))
7180
{
72-
string model = processorName.Substring("Intel Core".Length).Trim();
73-
74-
// Core i3/5/7/9
75-
if (
76-
model.Length > 4 &&
77-
model[0] == 'i' &&
78-
(model[1] == '3' || model[1] == '5' || model[1] == '7' || model[1] == '9') &&
79-
(model[2] == '-' || model[2] == ' '))
80-
{
81-
string modelNumber = model.Substring(3);
82-
if (modelNumber.StartsWith("CPU"))
83-
modelNumber = modelNumber.Substring(3).Trim();
84-
if (modelNumber.Contains("CPU"))
85-
modelNumber = modelNumber.Substring(0, modelNumber.IndexOf("CPU", StringComparison.Ordinal)).Trim();
86-
return ParseIntelCoreMicroarchitecture(modelNumber);
87-
}
81+
string modelNumber = model.Substring(3);
82+
if (modelNumber.StartsWith("CPU"))
83+
modelNumber = modelNumber.Substring(3).Trim();
84+
if (modelNumber.Contains("CPU"))
85+
modelNumber = modelNumber.Substring(0, modelNumber.IndexOf("CPU", StringComparison.Ordinal)).Trim();
86+
return ParseIntelCoreMicroarchitecture(modelNumber);
8887
}
89-
90-
return null;
9188
}
9289

93-
private static readonly Lazy<Dictionary<string, string>> KnownMicroarchitectures = new Lazy<Dictionary<string, string>>(() =>
90+
return null;
91+
}
92+
93+
private static readonly Lazy<Dictionary<string, string>> KnownMicroarchitectures = new Lazy<Dictionary<string, string>>(() =>
94+
{
95+
var data = ResourceHelper.LoadResource("BenchmarkDotNet.Environments.microarchitectures.txt").Split('\r', '\n');
96+
var dictionary = new Dictionary<string, string>();
97+
string? currentMicroarchitecture = null;
98+
foreach (string line in data)
9499
{
95-
var data = ResourceHelper.LoadResource("BenchmarkDotNet.Environments.microarchitectures.txt").Split('\r', '\n');
96-
var dictionary = new Dictionary<string, string>();
97-
string? currentMicroarchitecture = null;
98-
foreach (string line in data)
100+
if (line.StartsWith("//") || string.IsNullOrWhiteSpace(line))
101+
continue;
102+
if (line.StartsWith("#"))
99103
{
100-
if (line.StartsWith("//") || string.IsNullOrWhiteSpace(line))
101-
continue;
102-
if (line.StartsWith("#"))
103-
{
104-
currentMicroarchitecture = line.Substring(1).Trim();
105-
continue;
106-
}
107-
108-
string modelNumber = line.Trim();
109-
if (dictionary.ContainsKey(modelNumber))
110-
throw new Exception($"{modelNumber} is defined twice in microarchitectures.txt");
111-
if (currentMicroarchitecture == null)
112-
throw new Exception($"{modelNumber} doesn't have defined microarchitecture in microarchitectures.txt");
113-
dictionary[modelNumber] = currentMicroarchitecture;
104+
currentMicroarchitecture = line.Substring(1).Trim();
105+
continue;
114106
}
115107

116-
return dictionary;
117-
});
108+
string modelNumber = line.Trim();
109+
if (dictionary.ContainsKey(modelNumber))
110+
throw new Exception($"{modelNumber} is defined twice in microarchitectures.txt");
111+
if (currentMicroarchitecture == null)
112+
throw new Exception($"{modelNumber} doesn't have defined microarchitecture in microarchitectures.txt");
113+
dictionary[modelNumber] = currentMicroarchitecture;
114+
}
118115

119-
// see http://www.intel.com/content/www/us/en/processors/processor-numbers.html
120-
[SuppressMessage("ReSharper", "StringLiteralTypo")]
121-
internal static string? ParseIntelCoreMicroarchitecture(string modelNumber)
122-
{
123-
if (KnownMicroarchitectures.Value.TryGetValue(modelNumber, out string? microarchitecture))
124-
return microarchitecture;
116+
return dictionary;
117+
});
118+
119+
// see http://www.intel.com/content/www/us/en/processors/processor-numbers.html
120+
[SuppressMessage("ReSharper", "StringLiteralTypo")]
121+
internal static string? ParseIntelCoreMicroarchitecture(string modelNumber)
122+
{
123+
if (KnownMicroarchitectures.Value.TryGetValue(modelNumber, out string? microarchitecture))
124+
return microarchitecture;
125125

126-
if (modelNumber.Length >= 3 && modelNumber.Substring(0, 3).All(char.IsDigit) &&
127-
(modelNumber.Length == 3 || !char.IsDigit(modelNumber[3])))
128-
return "Nehalem";
129-
if (modelNumber.Length >= 4 && modelNumber.Substring(0, 4).All(char.IsDigit))
126+
if (modelNumber.Length >= 3 && modelNumber.Substring(0, 3).All(char.IsDigit) &&
127+
(modelNumber.Length == 3 || !char.IsDigit(modelNumber[3])))
128+
return "Nehalem";
129+
if (modelNumber.Length >= 4 && modelNumber.Substring(0, 4).All(char.IsDigit))
130+
{
131+
char generation = modelNumber[0];
132+
switch (generation)
130133
{
131-
char generation = modelNumber[0];
132-
switch (generation)
133-
{
134-
case '2':
135-
return "Sandy Bridge";
136-
case '3':
137-
return "Ivy Bridge";
138-
case '4':
139-
return "Haswell";
140-
case '5':
141-
return "Broadwell";
142-
case '6':
143-
return "Skylake";
144-
case '7':
145-
return "Kaby Lake";
146-
case '8':
147-
return "Coffee Lake";
148-
default:
149-
return null;
150-
}
134+
case '2':
135+
return "Sandy Bridge";
136+
case '3':
137+
return "Ivy Bridge";
138+
case '4':
139+
return "Haswell";
140+
case '5':
141+
return "Broadwell";
142+
case '6':
143+
return "Skylake";
144+
case '7':
145+
return "Kaby Lake";
146+
case '8':
147+
return "Coffee Lake";
148+
default:
149+
return null;
151150
}
152-
return null;
153151
}
152+
return null;
154153
}
155154
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Linq;
2+
using BenchmarkDotNet.Extensions;
3+
using BenchmarkDotNet.Portability.Cpu.Linux;
4+
using BenchmarkDotNet.Portability.Cpu.macOS;
5+
using BenchmarkDotNet.Portability.Cpu.Windows;
6+
7+
namespace BenchmarkDotNet.Portability.Cpu;
8+
9+
internal class CompositeCpuInfoDetector(params ICpuInfoDetector[] detectors) : ICpuInfoDetector
10+
{
11+
public bool IsApplicable() => detectors.Any(loader => loader.IsApplicable());
12+
13+
public CpuInfo? Detect() => detectors
14+
.Where(loader => loader.IsApplicable())
15+
.Select(loader => loader.Detect())
16+
.WhereNotNull()
17+
.FirstOrDefault();
18+
}
+28-32
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,33 @@
1-
using Perfolizer.Horology;
1+
using BenchmarkDotNet.Portability.Cpu.Linux;
2+
using BenchmarkDotNet.Portability.Cpu.macOS;
3+
using BenchmarkDotNet.Portability.Cpu.Windows;
4+
using Perfolizer.Horology;
25

3-
namespace BenchmarkDotNet.Portability.Cpu
6+
namespace BenchmarkDotNet.Portability.Cpu;
7+
8+
public class CpuInfo(
9+
string? processorName,
10+
int? physicalProcessorCount,
11+
int? physicalCoreCount,
12+
int? logicalCoreCount,
13+
Frequency? nominalFrequency,
14+
Frequency? maxFrequency = null)
415
{
5-
public class CpuInfo
6-
{
7-
public string ProcessorName { get; }
8-
public int? PhysicalProcessorCount { get; }
9-
public int? PhysicalCoreCount { get; }
10-
public int? LogicalCoreCount { get; }
11-
public Frequency? NominalFrequency { get; }
12-
public Frequency? MinFrequency { get; }
13-
public Frequency? MaxFrequency { get; }
16+
public static readonly CpuInfo Empty = new (null, null, null, null, null, null);
17+
public static CpuInfo FromName(string processorName) => new (processorName, null, null, null, null);
18+
public static CpuInfo FromNameAndFrequency(string processorName, Frequency nominalFrequency) => new (processorName, null, null, null, nominalFrequency);
19+
20+
private static readonly CompositeCpuInfoDetector Detector = new (
21+
new WindowsCpuInfoDetector(),
22+
new LinuxCpuInfoDetector(),
23+
new MacOsCpuInfoDetector());
1424

15-
internal CpuInfo(string processorName, Frequency? nominalFrequency)
16-
: this(processorName, null, null, null, nominalFrequency, null, null)
17-
{
18-
}
25+
public static CpuInfo? DetectCurrent() => Detector.Detect();
1926

20-
public CpuInfo(string processorName,
21-
int? physicalProcessorCount,
22-
int? physicalCoreCount,
23-
int? logicalCoreCount,
24-
Frequency? nominalFrequency,
25-
Frequency? minFrequency,
26-
Frequency? maxFrequency)
27-
{
28-
ProcessorName = processorName;
29-
PhysicalProcessorCount = physicalProcessorCount;
30-
PhysicalCoreCount = physicalCoreCount;
31-
LogicalCoreCount = logicalCoreCount;
32-
NominalFrequency = nominalFrequency;
33-
MinFrequency = minFrequency;
34-
MaxFrequency = maxFrequency;
35-
}
36-
}
27+
public string? ProcessorName { get; } = processorName;
28+
public int? PhysicalProcessorCount { get; } = physicalProcessorCount;
29+
public int? PhysicalCoreCount { get; } = physicalCoreCount;
30+
public int? LogicalCoreCount { get; } = logicalCoreCount;
31+
public Frequency? NominalFrequency { get; } = nominalFrequency ?? maxFrequency;
32+
public Frequency? MaxFrequency { get; } = maxFrequency ?? nominalFrequency;
3733
}

0 commit comments

Comments
 (0)