// The MIT License // // Copyright (c) 2008 Jon Skeet // // 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.Reflection; using System.Collections; /// /// The attribute to use to mark methods as being /// the targets of benchmarking. /// [AttributeUsage(AttributeTargets.Method)] public class BenchmarkAttribute : Attribute { } /// /// Very simple benchmarking framework. Looks for all types /// in the current assembly which have public static parameterless /// methods marked with the Benchmark attribute. In addition, if /// there are public static Init, Reset and Check methods with /// appropriate parameters (a string array for Init, nothing for /// Reset or Check) these are called at appropriate times. /// public class Benchmark { /// /// Number of times to run the methods in each type. /// static int runIterations=1; 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]; } // We're only ever interested in public static methods. This variable // just makes it easier to read the code... 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); // Find all parameterless methods with the [Benchmark] attribute ArrayList benchmarkMethods=new ArrayList(); foreach (MethodInfo method in type.GetMethods(publicStatic)) { ParameterInfo[] parameters = method.GetParameters(); if (parameters!=null && parameters.Length != 0) { continue; } if (method.GetCustomAttributes (typeof(BenchmarkAttribute), false).Length != 0) { benchmarkMethods.Add (method); } } // Ignore types with no appropriate methods to benchmark if (benchmarkMethods.Count==0) { continue; } Console.WriteLine ("Benchmarking type {0}", type.Name); // 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 } for (int i=0; i < runIterations; i++) { if (runIterations != 1) { Console.WriteLine ("Run #{0}", i+1); } 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); } // If everything's worked, report the time taken, // nicely lined up (assuming no very long method names!) Console.WriteLine (" {0,-20} {1}", method.Name, 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); } } } } } /// /// Parses the command line, returning an array of strings /// which are the arguments the tasks should receive. This /// array will definitely be non-null, even if null is /// passed in originally. /// 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); } }