Last active
March 19, 2020 10:16
-
-
Save Sergio0694/84c3ae8e7844adf1da723a4a720b0922 to your computer and use it in GitHub Desktop.
A simple class that can run a benchmark and display its results with a markdown table
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
using System; | |
using System.Diagnostics; | |
using System.Diagnostics.Contracts; | |
using System.Runtime.CompilerServices; | |
using System.Text; | |
#nullable enable | |
namespace Profiling | |
{ | |
/// <summary> | |
/// A simple <see langword="class"/> that runs a benchmark and returns a markdown table with the results. | |
/// </summary> | |
/// <remarks> | |
/// This is a very simplified version of what BenchmarkDotNet does, but with no additional | |
/// dependencies, so that it can also be used from UWP projects (on .NET Native too). | |
/// </remarks> | |
public static class BenchmarkRunner | |
{ | |
/// <summary> | |
/// The number of benchmarking runs to run for each benchmark | |
/// </summary> | |
private const int NumberOfRuns = 10; | |
/// <summary> | |
/// Runs a given benchmark. | |
/// </summary> | |
/// <typeparam name="T">The type of benchmark to run.</typeparam> | |
[Pure] | |
[MethodImpl(MethodImplOptions.NoInlining)] | |
public static string Run<T>() | |
where T : struct, IBenchmark | |
{ | |
StringBuilder builder = new StringBuilder(); | |
builder.AppendLine("| Test | Mean | Min | Max |"); | |
builder.AppendLine("|-------------:|-------------:|-------------:|-------------:|"); | |
string | |
name = typeof(T).Name.PadLeft(14), | |
result = RunCore<T>(); | |
builder.AppendLine($"|{name}|{result}|"); | |
return builder.ToString(); | |
} | |
/// <summary> | |
/// Runs a given benchmark. | |
/// </summary> | |
/// <typeparam name="T">The type of benchmark to run.</typeparam> | |
[Pure] | |
[MethodImpl(MethodImplOptions.NoInlining)] | |
private static string RunCore<T>() | |
where T : struct, IBenchmark | |
{ | |
T runner = default; | |
GC.Collect(); | |
// Warmup | |
runner.Run(); | |
GC.Collect(); | |
Span<TimeSpan> times = stackalloc TimeSpan[NumberOfRuns]; | |
Stopwatch timer = new Stopwatch(); | |
foreach (ref TimeSpan time in times) | |
{ | |
timer.Restart(); | |
runner.Run(); | |
timer.Stop(); | |
time = timer.Elapsed; | |
} | |
GC.Collect(); | |
return GetStatisticsForRun(times); | |
} | |
/// <summary> | |
/// Creates a formatted representation of times from a given run result. | |
/// </summary> | |
/// <param name="times">The times for a benchmark run.</param> | |
[Pure] | |
[MethodImpl(MethodImplOptions.NoInlining)] | |
private static string GetStatisticsForRun(ReadOnlySpan<TimeSpan> times) | |
{ | |
long | |
avg = 0, | |
min = long.MaxValue, | |
max = long.MinValue; | |
foreach (TimeSpan time in times) | |
{ | |
avg += time.Ticks; | |
if (time.Ticks < min) min = time.Ticks; | |
if (time.Ticks > max) max = time.Ticks; | |
} | |
avg -= min; | |
avg -= max; | |
avg /= times.Length - 2; | |
string | |
first = TimeSpan.FromTicks(avg).ToString("m':'s'.'ffffff").PadLeft(14), | |
second = TimeSpan.FromTicks(min).ToString("m':'s'.'ffffff").PadLeft(14), | |
third = TimeSpan.FromTicks(max).ToString("m':'s'.'ffffff").PadLeft(14); | |
return string.Join('|', first, second, third); | |
} | |
} | |
/// <summary> | |
/// An <see langword="interface"/> for a script runner in a given configuration. | |
/// </summary> | |
public interface IBenchmark | |
{ | |
/// <summary> | |
/// Runs a given benchmark. | |
/// </summary> | |
/// <remarks>You should mark this method as not inlined, for better results.</remarks> | |
void Run(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment