Created
September 26, 2016 22:29
-
-
Save nbxx/d8a2ba6eb22cbd3b651361056c7e059a to your computer and use it in GitHub Desktop.
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
//----------------------------------------------------------------------- | |
// <copyright file="Benchmark.cs" company="YuGuan Corporation"> | |
// Copyright (c) YuGuan Corporation. All rights reserved. | |
// </copyright> | |
//----------------------------------------------------------------------- | |
namespace DevLib.Diagnostics | |
{ | |
using System; | |
using System.Diagnostics; | |
using System.Diagnostics.CodeAnalysis; | |
using System.Runtime.InteropServices; | |
using System.Security.Permissions; | |
using System.Threading; | |
/// <summary> | |
/// Measure code snippets performance. | |
/// </summary> | |
[EnvironmentPermissionAttribute(SecurityAction.Demand, Unrestricted = true)] | |
public static class Benchmark | |
{ | |
/// <summary> | |
/// Field ColorRandom. | |
/// </summary> | |
private static readonly Random ColorRandom = new Random(); | |
/// <summary> | |
/// Initialize Benchmark. | |
/// </summary> | |
public static void Initialize() | |
{ | |
DevLib.Diagnostics.Benchmark.Run(delegate { }, string.Empty, 1, delegate { }); | |
} | |
/// <summary> | |
/// Run code snippets and give a performance test result. | |
/// </summary> | |
/// <param name="action">Code snippets to run. | |
/// <example>E.g. <code>delegate { Console.WriteLine("Hello"); }</code></example> | |
/// </param> | |
/// <param name="name">The name of current benchmark.</param> | |
/// <param name="iteration">Repeat times.</param> | |
/// <param name="outputAction">The action to handle the performance test result string. | |
/// <example>Default: <code>Console.WriteLine</code></example> | |
/// </param> | |
/// <returns>Benchmark result.</returns> | |
public static BenchmarkResult Run(Action<int> action, string name = null, int iteration = 1, Action<string> outputAction = null) | |
{ | |
if ((action == null) || (iteration < 1)) | |
{ | |
return null; | |
} | |
if (name == null) | |
{ | |
name = action.Method.ToString(); | |
} | |
if (outputAction == null) | |
{ | |
outputAction = Console.WriteLine; | |
} | |
string titleName = string.Format("{0} x {1}", name, iteration.ToString()).PadRight(53, '-'); | |
// Backup current thread priority | |
ProcessPriorityClass originalPriorityClass = Process.GetCurrentProcess().PriorityClass; | |
ThreadPriority originalThreadPriority = Thread.CurrentThread.Priority; | |
// Backup current console color | |
ConsoleColor originalForegroundColor = Console.ForegroundColor; | |
ConsoleColor originalBackgroundColor = Console.BackgroundColor; | |
ConsoleColor consoleRandomColor = (ConsoleColor)ColorRandom.Next(9, 15); | |
Console.ResetColor(); | |
Console.ForegroundColor = consoleRandomColor; | |
Console.BackgroundColor = ConsoleColor.Black; | |
string beginTitle = string.Format(@"/--Benchmark Begin-->{0}--\", titleName); | |
outputAction(beginTitle); | |
Debug.WriteLine(beginTitle); | |
Console.ResetColor(); | |
Console.ForegroundColor = originalForegroundColor; | |
Console.BackgroundColor = originalBackgroundColor; | |
// Record the latest GC counts | |
int gcArrayLength = GC.MaxGeneration + 1; | |
int[] gcCountArray = new int[gcArrayLength]; | |
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); | |
for (int i = 0; i < gcArrayLength; i++) | |
{ | |
gcCountArray[i] = GC.CollectionCount(i); | |
} | |
// Run action, record timespan | |
Stopwatch stopwatch = new Stopwatch(); | |
ulong cycleCount = GetCycleCount(); | |
long threadTimeCount = GetCurrentThreadTime(); | |
stopwatch.Start(); | |
for (int i = 1; i <= iteration; i++) | |
{ | |
action(i); | |
} | |
stopwatch.Stop(); | |
long threadTime = GetCurrentThreadTime() - threadTimeCount; | |
ulong cpuCycles = GetCycleCount() - cycleCount; | |
for (int i = 0; i < gcArrayLength; i++) | |
{ | |
gcCountArray[i] = GC.CollectionCount(i) - gcCountArray[i]; | |
} | |
string[] gcTitleArray = new string[gcArrayLength]; | |
string[] gcResultArray = new string[gcArrayLength]; | |
for (int i = 0; i < gcArrayLength; i++) | |
{ | |
gcTitleArray[gcArrayLength - 1 - i] = string.Format("G{0}", i.ToString()); | |
gcResultArray[gcArrayLength - 1 - i] = gcCountArray[i].ToString(); | |
} | |
string gcTitle = string.Join("/", gcTitleArray); | |
string gcResult = string.Join("/", gcResultArray); | |
Console.WriteLine(); | |
// Console output recorded times | |
Console.ResetColor(); | |
Console.ForegroundColor = ConsoleColor.White; | |
Console.BackgroundColor = ConsoleColor.Black; | |
string resultTitle = string.Format("{0,18}{1,18}{2,18}{3,18}", "[Stopwatch]", "[ThreadTime]", "[CPUCycles]", string.Format("[{0}]", gcTitle)); | |
outputAction(resultTitle); | |
Debug.WriteLine(resultTitle); | |
Console.ResetColor(); | |
Console.ForegroundColor = ConsoleColor.Green; | |
Console.BackgroundColor = ConsoleColor.Black; | |
string resultTime = string.Format("{0,16:N0}ms{1,16:N0}ms{2,18:N0}{3,18}", stopwatch.ElapsedMilliseconds.ToString(), (threadTime / 10000L).ToString(), cpuCycles.ToString(), gcResult); | |
outputAction(resultTime); | |
Debug.WriteLine(resultTime); | |
Console.ResetColor(); | |
Console.ForegroundColor = consoleRandomColor; | |
Console.BackgroundColor = ConsoleColor.Black; | |
string endTitle = string.Format(@"\--Benchmark End---->{0}--/", titleName); | |
outputAction(endTitle); | |
Debug.WriteLine(endTitle); | |
// Restore console color | |
Console.ResetColor(); | |
Console.ForegroundColor = originalForegroundColor; | |
Console.BackgroundColor = originalBackgroundColor; | |
// Restore thread priority | |
Process.GetCurrentProcess().PriorityClass = originalPriorityClass; | |
Thread.CurrentThread.Priority = originalThreadPriority; | |
Console.WriteLine(); | |
BenchmarkResult result = new BenchmarkResult(stopwatch.ElapsedMilliseconds, threadTime / 10000L, cpuCycles, gcCountArray, name, iteration); | |
return result; | |
} | |
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Reviewed.")] | |
[DllImport("kernel32.dll")] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
internal static extern bool QueryThreadCycleTime(IntPtr threadHandle, ref ulong cycleTime); | |
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Reviewed.")] | |
[DllImport("kernel32.dll")] | |
internal static extern IntPtr GetCurrentThread(); | |
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Reviewed.")] | |
[DllImport("kernel32.dll", SetLastError = true)] | |
internal static extern bool GetThreadTimes(IntPtr threadHandle, out long creationTime, out long exitTime, out long kernelTime, out long userTime); | |
/// <summary> | |
/// Static Method GetCycleCount. | |
/// </summary> | |
/// <returns>Cycle count.</returns> | |
private static ulong GetCycleCount() | |
{ | |
if (Environment.OSVersion.Platform > PlatformID.Win32NT) | |
{ | |
return 0; | |
} | |
ulong cycleCount = 0; | |
try | |
{ | |
QueryThreadCycleTime(GetCurrentThread(), ref cycleCount); | |
} | |
catch | |
{ | |
} | |
return cycleCount; | |
} | |
/// <summary> | |
/// Static Method GetCurrentThreadTime. | |
/// </summary> | |
/// <returns>Thread time.</returns> | |
private static long GetCurrentThreadTime() | |
{ | |
if (Environment.OSVersion.Platform > PlatformID.Win32NT) | |
{ | |
return 0; | |
} | |
long temp = 0; | |
long kernelTime = 0; | |
long userTimer = 0; | |
try | |
{ | |
GetThreadTimes(GetCurrentThread(), out temp, out temp, out kernelTime, out userTimer); | |
} | |
catch | |
{ | |
} | |
return kernelTime + userTimer; | |
} | |
} | |
/// <summary> | |
/// Code snippets performance test result. | |
/// </summary> | |
[Serializable] | |
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "Reviewed.")] | |
public class BenchmarkResult | |
{ | |
/// <summary> | |
/// Initializes a new instance of the <see cref="BenchmarkResult" /> class. | |
/// </summary> | |
public BenchmarkResult() | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="BenchmarkResult" /> class. | |
/// </summary> | |
/// <param name="stopwatchElapsedMilliseconds">Stopwatch timespan in milliseconds.</param> | |
/// <param name="threadTimeElapsedMilliseconds">ThreadTime timespan in milliseconds.</param> | |
/// <param name="cpuCycles">CPU cycles.</param> | |
/// <param name="gcCountArray">GC Count Array.</param> | |
/// <param name="name">The name of current benchmark.</param> | |
/// <param name="iteration">Repeat times.</param> | |
public BenchmarkResult(long stopwatchElapsedMilliseconds, long threadTimeElapsedMilliseconds, ulong cpuCycles, int[] gcCountArray, string name = null, int iteration = 1) | |
{ | |
this.StopwatchElapsedMilliseconds = stopwatchElapsedMilliseconds; | |
this.ThreadTimeElapsedMilliseconds = threadTimeElapsedMilliseconds; | |
this.CPUCycles = cpuCycles; | |
this.GCCountArray = gcCountArray; | |
this.Name = name; | |
this.Iteration = iteration; | |
} | |
/// <summary> | |
/// Gets or sets stopwatch timespan in milliseconds. | |
/// </summary> | |
public long StopwatchElapsedMilliseconds | |
{ | |
get; | |
set; | |
} | |
/// <summary> | |
/// Gets or sets threadTime timespan in milliseconds. | |
/// </summary> | |
public long ThreadTimeElapsedMilliseconds | |
{ | |
get; | |
set; | |
} | |
/// <summary> | |
/// Gets or sets CPU cycles. | |
/// </summary> | |
public ulong CPUCycles | |
{ | |
get; | |
set; | |
} | |
/// <summary> | |
/// Gets or sets GC Count Array. | |
/// </summary> | |
public int[] GCCountArray | |
{ | |
get; | |
set; | |
} | |
/// <summary> | |
/// Gets or sets the name of current benchmark. | |
/// </summary> | |
public string Name | |
{ | |
get; | |
set; | |
} | |
/// <summary> | |
/// Gets or sets repeat times of current benchmark. | |
/// </summary> | |
public int Iteration | |
{ | |
get; | |
set; | |
} | |
/// <summary> | |
/// Returns a <see cref="System.String" /> that represents this instance. | |
/// </summary> | |
/// <returns>A <see cref="System.String" /> that represents this instance.</returns> | |
public override string ToString() | |
{ | |
string gcTitle = null; | |
string gcResult = null; | |
if (this.GCCountArray != null && this.GCCountArray.Length > 0) | |
{ | |
int gcArrayLength = this.GCCountArray.Length; | |
string[] gcTitleArray = new string[gcArrayLength]; | |
string[] gcResultArray = new string[gcArrayLength]; | |
for (int i = 0; i < gcArrayLength; i++) | |
{ | |
gcTitleArray[gcArrayLength - 1 - i] = string.Format("G{0}", i.ToString()); | |
gcResultArray[gcArrayLength - 1 - i] = this.GCCountArray[i].ToString(); | |
} | |
gcTitle = string.Join("/", gcTitleArray); | |
gcResult = string.Join("/", gcResultArray); | |
} | |
else | |
{ | |
gcTitle = "GC"; | |
gcResult = "NA"; | |
} | |
return string.Format("[Stopwatch]={0}ms [ThreadTime]={1}ms [CPUCycles]={2} [{3}]={4}", this.StopwatchElapsedMilliseconds.ToString(), this.ThreadTimeElapsedMilliseconds.ToString(), this.CPUCycles.ToString(), gcTitle, gcResult); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment