Last active
December 30, 2018 08:24
-
-
Save ZodmanPerth/12711f70d8db12cf4af80ee7df7f641e to your computer and use it in GitHub Desktop.
PerformanceDotNet in LINQPad with report amalgamation. Blog post at http://www.redperegrine.net/2017/12/23/measuring-csharp-perf-with-benchmarkdotnet/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//This LINQPad file is discussed in my blog post: | |
// http://www.redperegrine.net/2017/12/10/measuring-csharp-perf-with-benchmarkdotnet/ | |
[MemoryDiagnoser] | |
[RyuJitX64Job, LegacyJitX86Job] | |
public class IntegerWorker | |
{ | |
[Benchmark] | |
public void NormalEnumerator() | |
{ | |
IntegerDriver driver = new IntegerDriver(); | |
int count = 0; | |
int sum = 0; | |
foreach (int i in driver) | |
{ | |
sum += i; | |
count++; | |
} | |
if (RunnerHelper.SupressBenchmarking) | |
RunnerHelper.DumpWorkerReport(sum, count); | |
} | |
[Benchmark] | |
public void HandCrankedEnumerator() | |
{ | |
IntegerDriver driver = new IntegerDriver(); | |
int count = 0; | |
int sum = 0; | |
driver.Reset(); | |
while (!driver.EOF) | |
{ | |
sum += driver.Current; | |
count++; | |
driver.MoveNext(); | |
} | |
if (RunnerHelper.SupressBenchmarking) | |
RunnerHelper.DumpWorkerReport(sum, count); | |
} | |
[Benchmark] | |
public void HandCrankedWithMoveNextState() | |
{ | |
IntegerDriver driver = new IntegerDriver(); | |
int count = 0; | |
int sum = 0; | |
if (driver.ResetWithState()) | |
do | |
{ | |
sum += driver.Current; | |
count++; | |
} | |
while (driver.MoveNextWithState()); | |
if (RunnerHelper.SupressBenchmarking) | |
RunnerHelper.DumpWorkerReport(sum, count); | |
} | |
[Benchmark] | |
public void ResultSetFullyCalculated() | |
{ | |
IntegerDriver driver = new IntegerDriver(); | |
int count = 0; | |
int sum = 0; | |
driver.Calculate(); | |
count = driver.ResultCount; | |
for (int i = 0; i < count; i++) | |
sum += driver.Current; | |
if (RunnerHelper.SupressBenchmarking) | |
RunnerHelper.DumpWorkerReport(sum, count); | |
} | |
} | |
[MemoryDiagnoser] | |
[RyuJitX64Job, LegacyJitX86Job] | |
public class ItemWorker | |
{ | |
[Benchmark] | |
public void NormalEnumerator() | |
{ | |
ItemDriver driver = new ItemDriver(); | |
int count = 0; | |
int sum = 0; | |
foreach (Item i in driver) | |
{ | |
sum += i.Value; | |
count++; | |
} | |
if (RunnerHelper.SupressBenchmarking) | |
RunnerHelper.DumpWorkerReport(sum, count); | |
} | |
[Benchmark] | |
public void HandCrankedEnumerator() | |
{ | |
ItemDriver driver = new ItemDriver(); | |
int count = 0; | |
int sum = 0; | |
driver.Reset(); | |
while (!driver.EOF) | |
{ | |
sum += driver.Current.Value; | |
count++; | |
driver.MoveNext(); | |
} | |
if (RunnerHelper.SupressBenchmarking) | |
RunnerHelper.DumpWorkerReport(sum, count); | |
} | |
[Benchmark] | |
public void HandCrankedWithMoveNextState() | |
{ | |
ItemDriver driver = new ItemDriver(); | |
int count = 0; | |
int sum = 0; | |
if (driver.ResetWithState()) | |
do | |
{ | |
sum += driver.Current.Value; | |
count++; | |
} | |
while (driver.MoveNextWithState()); | |
if (RunnerHelper.SupressBenchmarking) | |
RunnerHelper.DumpWorkerReport(sum, count); | |
} | |
[Benchmark] | |
public void ResultSetFullyCalculated() | |
{ | |
ItemDriver driver = new ItemDriver(); | |
int count = 0; | |
int sum = 0; | |
driver.Calculate(); | |
count = driver.ResultCount; | |
for (int i = 0; i < count; i++) | |
sum += driver.Current.Value; | |
if (RunnerHelper.SupressBenchmarking) | |
RunnerHelper.DumpWorkerReport(sum, count); | |
} | |
} | |
[MemoryDiagnoser] | |
[RyuJitX64Job, LegacyJitX86Job] | |
public class GenericWorker | |
{ | |
[Benchmark] | |
public void NormalEnumerator() | |
{ | |
GenericDriver<Item> driver = new GenericDriver<Item>(); | |
int count = 0; | |
int sum = 0; | |
foreach (Item i in driver) | |
{ | |
sum += i.Value; | |
count++; | |
} | |
if (RunnerHelper.SupressBenchmarking) | |
RunnerHelper.DumpWorkerReport(sum, count); | |
} | |
[Benchmark] | |
public void HandCrankedEnumerator() | |
{ | |
GenericDriver<Item> driver = new GenericDriver<Item>(); | |
int count = 0; | |
int sum = 0; | |
driver.Reset(); | |
while (!driver.EOF) | |
{ | |
sum += driver.Current.Value; | |
count++; | |
driver.MoveNext(); | |
} | |
if (RunnerHelper.SupressBenchmarking) | |
RunnerHelper.DumpWorkerReport(sum, count); | |
} | |
[Benchmark] | |
public void HandCrankedWithMoveNextState() | |
{ | |
GenericDriver<Item> driver = new GenericDriver<Item>(); | |
int count = 0; | |
int sum = 0; | |
if (driver.ResetWithState()) | |
do | |
{ | |
sum += driver.Current.Value; | |
count++; | |
} | |
while (driver.MoveNextWithState()); | |
if (RunnerHelper.SupressBenchmarking) | |
RunnerHelper.DumpWorkerReport(sum, count); | |
} | |
[Benchmark] | |
public void ResultSetFullyCalculated() | |
{ | |
GenericDriver<Item> driver = new GenericDriver<Item>(); | |
int count = 0; | |
int sum = 0; | |
driver.Calculate(); | |
count = driver.ResultCount; | |
for (int i = 0; i < count; i++) | |
sum += driver.Current.Value; | |
if (RunnerHelper.SupressBenchmarking) | |
RunnerHelper.DumpWorkerReport(sum, count); | |
} | |
} | |
static class RunnerHelper | |
{ | |
const string _lpPath = @"C:\Program Files (x86)\LINQPad5\BenchmarkDotNet.Artifacts\results\"; | |
public static string ReportType { get; set; } | |
public static string ReportFilePath | |
{ | |
get | |
{ | |
if (string.IsNullOrEmpty(ReportType)) throw new ArgumentNullException($"({nameof(ReportType)}"); | |
return $@"{_lpPath}UserQuery+{ReportType}-report.html"; | |
} | |
} | |
///<summary> | |
///Construct a file path in the same folder as the report with the passed file name | |
///</summary> | |
static string GenerateNewReportFilePath() | |
{ | |
return $@"{_lpPath}{ReportType}-{Size}.html"; | |
} | |
public static int Size { get; set; } | |
public static bool SupressBenchmarking { get; set; } | |
static StringBuilder _reportPaths; | |
///<summary> | |
///Copy the report from BenchmarkDotNet into the same folder with a new filename. | |
///</summary> | |
public static void BackupReport() | |
{ | |
if (File.Exists(ReportFilePath)) | |
{ | |
var filename = GenerateNewReportFilePath(); | |
if (_reportPaths == null) | |
_reportPaths = new StringBuilder(); | |
_reportPaths.AppendLine(filename); | |
if (File.Exists(filename)) | |
File.Delete(filename); | |
if (SupressBenchmarking) | |
$"Copying file:\n{ReportFilePath}\nTo file:\n{filename}".Dump(); | |
File.Copy(ReportFilePath, filename); | |
} | |
else | |
{ | |
if (SupressBenchmarking) | |
"Unable to backup report; not found.".Dump(); | |
} | |
} | |
///<summary> | |
///Dump the previously backed up reports with a heading. | |
///</summary> | |
public static void DumpReports(string heading = null) | |
{ | |
if (_reportPaths == null || _reportPaths.Length == 0) | |
"Nothing to report".Dump(); | |
if (!string.IsNullOrEmpty(heading)) | |
"Report:".Dump(heading); | |
var parser = new HtmlParser(); | |
bool isHeaderAdded = false; | |
StringBuilder html = new StringBuilder("<table>"); | |
foreach (var path in _reportPaths.ToString().Split('\n')) | |
{ | |
if (path.Length == 0) | |
continue; | |
var filepath = path.Trim(); | |
if (File.Exists(filepath)) | |
{ | |
var fileparts = System.IO.Path.GetFileNameWithoutExtension(filepath) | |
.Split('-'); | |
var type = fileparts[0]; | |
var size = fileparts[1]; | |
var doc = parser.Parse(File.ReadAllText(filepath)); | |
if (!isHeaderAdded) | |
{ | |
html.AppendLine($"<thead><tr><th>Type</th><th>Size</th>{doc.QuerySelector("thead > tr").InnerHtml}</tr></thead>"); | |
isHeaderAdded = true; | |
} | |
var rows = doc.QuerySelectorAll("tbody > tr"); | |
foreach (var r in rows) | |
html.Append($"<tbody><tr><td>{type}</td><td>{size}</td>{r.InnerHtml}</tr></tbody>"); | |
} | |
else | |
$"Unable to find report '{filepath}'".Dump(); | |
} | |
if (isHeaderAdded) | |
{ | |
html.AppendLine("</table>"); | |
Util.RawHtml(html.ToString()).Dump(); | |
File.WriteAllText($@"{_lpPath}AmalgamatedReport.html", html.ToString()); | |
} | |
} | |
///<summary> | |
///Dump the report directly from BenchmarkDotNet | |
///</summary> | |
public static void DumpReport() | |
{ | |
if (File.Exists(ReportFilePath)) | |
Util.RawHtml(File.ReadAllText(ReportFilePath)).Dump(); | |
else | |
$"Unable to find report '{ReportFilePath}'".Dump(); | |
} | |
public static void DumpWorkerReport(int sum, int count) | |
{ | |
$"Sum: {sum}, Count: {count}".Dump(); | |
} | |
} | |
void Main() | |
{ | |
//Set execution state | |
var sizes = new int[] { 10, 1000, 10000 }; | |
RunnerHelper.SupressBenchmarking = false; | |
if (RunnerHelper.SupressBenchmarking) | |
{ | |
"Running manual validation of test results".Dump("Execution Type"); | |
for (int i = 0; i < sizes.Length; i++) | |
{ | |
RunnerHelper.Size = sizes[i]; | |
var integerWorker = new IntegerWorker(); | |
integerWorker.NormalEnumerator(); | |
integerWorker.HandCrankedEnumerator(); | |
integerWorker.HandCrankedWithMoveNextState(); | |
integerWorker.ResultSetFullyCalculated(); | |
var itemWorker = new ItemWorker(); | |
itemWorker.NormalEnumerator(); | |
itemWorker.HandCrankedEnumerator(); | |
itemWorker.HandCrankedWithMoveNextState(); | |
itemWorker.ResultSetFullyCalculated(); | |
var genericWorkerInt = new GenericWorker(); | |
genericWorkerInt.NormalEnumerator(); | |
genericWorkerInt.HandCrankedEnumerator(); | |
genericWorkerInt.HandCrankedWithMoveNextState(); | |
genericWorkerInt.ResultSetFullyCalculated(); | |
} | |
} | |
else | |
{ | |
"Running tests with BenchmarkDotNet".Dump("Execution Type"); | |
for (int i = 0; i < sizes.Length; i++) | |
{ | |
RunnerHelper.Size = sizes[i]; | |
RunnerHelper.ReportType = "IntegerWorker"; | |
BenchmarkRunner.Run<IntegerWorker>(); | |
RunnerHelper.BackupReport(); | |
RunnerHelper.ReportType = "ItemWorker"; | |
BenchmarkRunner.Run<ItemWorker>(); | |
RunnerHelper.BackupReport(); | |
RunnerHelper.ReportType = "GenericWorker"; | |
BenchmarkRunner.Run<GenericWorker>(); | |
RunnerHelper.BackupReport(); | |
} | |
//Report | |
RunnerHelper.DumpReports(); | |
} | |
} | |
//class CustomConfig : ManualConfig | |
//{ | |
// public CustomConfig() | |
// { | |
// Add(MemoryDiagnoser.Default); | |
// | |
// Add(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS | |
// | |
// Add(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default | |
// Add(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default | |
// Add(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default | |
// } | |
//} | |
interface IHasValue | |
{ | |
int Value { get; set; } | |
} | |
class Item : IHasValue | |
{ | |
public int Value { get; set; } | |
} | |
class GenericDriver<T> : IEnumerable<T> where T : IHasValue, new() | |
{ | |
T[] _array; | |
public T Current { get { return _array[_index]; } } | |
public GenericDriver() | |
{ | |
_array = new T[RunnerHelper.Size]; | |
for (int i = 0; i < RunnerHelper.Size; i++) | |
_array[i] = new T() { Value = 1 }; | |
Results = new T[RunnerHelper.Size]; | |
} | |
public IEnumerator<T> GetEnumerator() | |
{ | |
for (int i = 0; i < RunnerHelper.Size; i++) | |
yield return _array[i]; | |
} | |
IEnumerator IEnumerable.GetEnumerator() | |
{ | |
return GetEnumerator(); | |
} | |
int _index = 0; | |
public void Reset() { _index = 0; } | |
public T MoveNext() { return _array[_index++]; } | |
public bool EOF { get { return _index >= RunnerHelper.Size; } } | |
public bool ResetWithState() { _index = 0; return _index < RunnerHelper.Size; } | |
public bool MoveNextWithState() | |
{ | |
_index++; | |
if (_index == RunnerHelper.Size) | |
return false; | |
return true; | |
} | |
public T[] Results { get; set; } | |
public int ResultCount { get; set; } | |
public void Calculate() | |
{ | |
ResultCount = 0; | |
for (int i = 0; i < RunnerHelper.Size; i++) | |
{ | |
Results[i] = _array[i]; | |
ResultCount++; | |
} | |
} | |
} | |
class IntegerDriver : IEnumerable<int> | |
{ | |
int[] _array; | |
public int Current { get { return _array[_index]; } } | |
public IntegerDriver() | |
{ | |
_array = new int[RunnerHelper.Size]; | |
for (int i = 0; i < RunnerHelper.Size; i++) | |
_array[i] = 1; | |
Results = new int[RunnerHelper.Size]; | |
} | |
public IEnumerator<int> GetEnumerator() | |
{ | |
for (int i = 0; i < RunnerHelper.Size; i++) | |
yield return _array[i]; | |
} | |
IEnumerator IEnumerable.GetEnumerator() | |
{ | |
return GetEnumerator(); | |
} | |
int _index = 0; | |
public void Reset() { _index = 0; } | |
public int MoveNext() { return _array[_index++]; } | |
public bool EOF { get { return _index >= RunnerHelper.Size; } } | |
public bool ResetWithState() { _index = 0; return _index < RunnerHelper.Size; } | |
public bool MoveNextWithState() | |
{ | |
_index++; | |
if (_index == RunnerHelper.Size) | |
return false; | |
return true; | |
} | |
public int[] Results { get; set; } | |
public int ResultCount { get; set; } | |
public void Calculate() | |
{ | |
ResultCount = 0; | |
for (int i = 0; i < RunnerHelper.Size; i++) | |
{ | |
Results[i] = _array[i]; | |
ResultCount++; | |
} | |
} | |
} | |
class SpecificItemDriver | |
{ | |
int _index = 0; | |
Item[] _array; | |
public Item Current { get { return _array[_index]; } } | |
public SpecificItemDriver() | |
{ | |
_array = new Item[RunnerHelper.Size]; | |
for (int i = 0; i < RunnerHelper.Size; i++) | |
_array[i] = new Item() { Value = 1 }; | |
} | |
public void Reset() { _index = 0; } | |
public Item MoveNext() { return _array[_index++]; } | |
public bool EOF { get { return _index >= RunnerHelper.Size; } } | |
} | |
class ItemDriver : IEnumerable<Item> | |
{ | |
Item[] _array; | |
public Item Current { get { return _array[_index]; } } | |
public ItemDriver() | |
{ | |
_array = new Item[RunnerHelper.Size]; | |
for (int i = 0; i < RunnerHelper.Size; i++) | |
_array[i] = new Item() { Value = 1 }; | |
Results = new Item[RunnerHelper.Size]; | |
} | |
public IEnumerator<Item> GetEnumerator() | |
{ | |
for (int i = 0; i < RunnerHelper.Size; i++) | |
yield return _array[i]; | |
} | |
IEnumerator IEnumerable.GetEnumerator() | |
{ | |
return GetEnumerator(); | |
} | |
int _index = 0; | |
public void Reset() { _index = 0; } | |
public Item MoveNext() { return _array[_index++]; } | |
public bool EOF { get { return _index >= RunnerHelper.Size; } } | |
public bool ResetWithState() { _index = 0; return _index < RunnerHelper.Size; } | |
public bool MoveNextWithState() | |
{ | |
_index++; | |
if (_index == RunnerHelper.Size) | |
return false; | |
return true; | |
} | |
public Item[] Results { get; set; } | |
public int ResultCount { get; set; } | |
public void Calculate() | |
{ | |
ResultCount = 0; | |
for (int i = 0; i < RunnerHelper.Size; i++) | |
{ | |
Results[i] = _array[i]; | |
ResultCount++; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment