// The MIT License // // Copyright (c) 2008 Jon Skeet // Copyright (c) 2009 Antonio Cuni // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; using System.Diagnostics; using System.Reflection; using System.Collections; using System.Collections.Generic; [AttributeUsage(AttributeTargets.Method)] public class BenchmarkAttribute : Attribute { public bool reference = false; public string expr = ""; private string[] parts; public BenchmarkAttribute() {} public BenchmarkAttribute(string expr) { this.expr = expr; if (expr == "reference") this.reference = true; else this.parts = expr.Split(new char[]{' '}); } public bool Check(double ratio) { if (reference) return true; if (parts == null || parts.Length == 0) return true; double d; switch(parts[0]) { case "~=": d = Convert.ToDouble(parts[1]); return (0.95*ratio < d) && (d < 1.05*ratio); case "~>": d = Convert.ToDouble(parts[1]); return d < 1.05*ratio; case "~<": d = Convert.ToDouble(parts[1]); return (0.95*ratio < d); } throw new Exception("Invalid expression: " + expr); } } public class Benchmark { static int runIterations=10; private static BenchmarkAttribute GetAttr(MethodInfo method) { object[] attrs = method.GetCustomAttributes(typeof(BenchmarkAttribute), false); if (attrs.Length > 0) return (BenchmarkAttribute)attrs[0]; else return null; } // Find all parameterless methods with the [Benchmark] attribute private static List GetMethods(Type type) { List benchmarkMethods = new List(); BindingFlags publicStatic = BindingFlags.Public | BindingFlags.Static; foreach (MethodInfo method in type.GetMethods(publicStatic)) { ParameterInfo[] parameters = method.GetParameters(); if (parameters!=null && parameters.Length != 0) continue; BenchmarkAttribute attr = GetAttr(method); if (attr != null) { if (attr.reference) benchmarkMethods.Insert(0, method); else benchmarkMethods.Add(method); } } return benchmarkMethods; } public static void Main(string[] args) { args = ParseCommandLine (args); // Save all the benchmark classes from doing a nullity test if (args==null) args = new string[0]; BindingFlags publicStatic = BindingFlags.Public | BindingFlags.Static; foreach (Type type in Assembly.GetCallingAssembly().GetTypes()) { // Find an Init method taking string[], if any MethodInfo initMethod=type.GetMethod ("Init", publicStatic, null, new Type[]{typeof(string[])}, null); // Find a parameterless Reset method, if any MethodInfo resetMethod=type.GetMethod ("Reset", publicStatic, null, new Type[0], null); // Find a parameterless Check method, if any MethodInfo checkMethod=type.GetMethod("Check", publicStatic, null, new Type[0], null); List benchmarkMethods = GetMethods(type); // Ignore types with no appropriate methods to benchmark if (benchmarkMethods.Count==0) continue; Console.WriteLine ("Benchmarking type {0}", type.Name); Console.WriteLine (" {0,-20} {1,-20} {2:F2}", "Name", "Time", "Ratio"); Console.WriteLine (" {0,-20} {1,-20} {2:F2}", "----", "----", "-----"); // If we've got an Init method, call it once try { if (initMethod!=null) initMethod.Invoke (null, new object[]{args}); } catch (TargetInvocationException e) { Exception inner = e.InnerException; string message = (inner==null ? null : inner.Message); if (message==null) message = "(No message)"; Console.WriteLine ("Init failed ({0})", message); continue; // Next type } Dictionary> results = new Dictionary>(); foreach (MethodInfo method in benchmarkMethods) results[method] = new List(); for (int i=0; i < runIterations; i++) { foreach (MethodInfo method in benchmarkMethods) { try { // Reset (if appropriate) if (resetMethod!=null) resetMethod.Invoke(null, null); // Give the test as good a chance as possible // of avoiding garbage collection GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); // Now run the test itself DateTime start = DateTime.Now; method.Invoke (null, null); DateTime end = DateTime.Now; // Check the results (if appropriate) // Note that this doesn't affect the timing if (checkMethod!=null) checkMethod.Invoke(null, null); results[method].Add(end-start); } catch (TargetInvocationException e) { Exception inner = e.InnerException; string message = (inner==null ? null : inner.Message); if (message==null) message = "(No message)"; Console.WriteLine (" {0}: Failed ({1})", method.Name, message); } } } PrintResults(results); } } static void PrintResults(Dictionary> results) { TimeSpan reference = TimeSpan.MinValue; foreach(MethodInfo method in results.Keys) { string name = method.Name; BenchmarkAttribute attr = GetAttr(method); TimeSpan time = Min(results[method]); if (reference == TimeSpan.MinValue) { Debug.Assert(attr.reference); reference = time; Console.WriteLine (" {0,-20} {1}", name, time); } else { double ratio = time.TotalMilliseconds / reference.TotalMilliseconds; string msg = ""; if (! attr.Check(ratio)) msg = string.Format(" Expected: {0:F2} {1}", ratio, attr.expr); Console.WriteLine (" {0,-20} {1,-20} {2:F2}x {3}", name, time, ratio, msg); } } } static TimeSpan Min(List lst) { TimeSpan result = lst[0]; foreach(TimeSpan item in lst) if (item < result) result = item; return result; } static string[] ParseCommandLine (string[] args) { if (args==null) return new string[0]; for (int i=0; i < args.Length; i++) { switch (args[i]) { case "-runtwice": runIterations=2; break; case "-version": PrintEnvironment(); break; case "-endoptions": // All following options are for the benchmarked // types. { string[] ret = new string [args.Length-i-1]; Array.Copy (args, i+1, ret, 0, ret.Length); return ret; } default: // Don't understand option; copy this and // all remaining options and return them. { string[] ret = new string [args.Length-i]; Array.Copy (args, i, ret, 0, ret.Length); return ret; } } } // Understood all arguments return new string[0]; } /// /// Prints out information about the operating environment. /// static void PrintEnvironment() { Console.WriteLine ("Operating System: {0}", Environment.OSVersion); Console.WriteLine ("Runtime version: {0}", Environment.Version); } }