using System; using System.Collections.Generic; using System.Globalization; using System.IO; namespace Glass.PerformanceTests { /// /// Runs a benchmark once over a small text with its output thrown away, so the compiling and first-use setup of the /// code it times is done before the run that counts. Without it the first slice or frame of every run carries tens /// of milliseconds that the later ones don't. /// public static class WarmStart { public const int Chars = 200000; /// /// Gets whether a warm-up is running, when a row needs only enough calls to compile what it times. /// public static bool IsWarming { get; private set; } /// /// Returns the arguments with every text size, which starts with a digit, set to the warm-up's, adding one /// after the benchmark's name when there is none. /// public static string[] Shrunk(string[] args) { string size = Chars.ToString(CultureInfo.InvariantCulture); var returnMe = new List(); bool sized = false; foreach (string argument in args) { if (argument.Length > 0 && char.IsDigit(argument[0])) { returnMe.Add(size); sized = true; } else { returnMe.Add(argument); } } if (!sized) { returnMe.Insert(1, size); } return returnMe.ToArray(); } /// /// Warms up, and then runs a benchmark for real. A warm-up that fails ends the run, since it has already said /// why. /// public static int Run(Func benchmark, string[] args) { TextWriter output = Console.Out; int warmExit; IsWarming = true; Console.SetOut(TextWriter.Null); try { warmExit = benchmark(Shrunk(args)); } finally { Console.SetOut(output); IsWarming = false; } if (warmExit != 0) { return warmExit; } GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); return benchmark(args); } } }