using System; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; namespace Amphora.PerformanceTests { /// /// Times Amphora's sorting, random numbers and trigonometry against framework code, taking a control through every /// row. /// public static class Program { public const string QuickOption = "--quick"; public const string ValuesOption = "--values"; public const string ItemsOption = "--items"; public const string RandomOption = "--random"; public const string TrigOption = "--trig"; public const float BusyMachinePercent = 20; public const int BusySampleMilliseconds = 500; public const string Usage = "Usage: Amphora.PerformanceTests [--quick] [--values] [--items] [--random] [--trig] " + "[text in the row names to run]"; [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GetSystemTimes(out long idleTime, out long kernelTime, out long userTime); public static bool IsJitOptimizerDisabled(Assembly assembly) { var debuggable = assembly.GetCustomAttribute(); return debuggable != null && debuggable.IsJITOptimizerDisabled; } /// /// Why timings taken in this process wouldn't mean anything, or null when they would. /// public static string ReasonTimingsAreMeaningless() { if (Debugger.IsAttached) { return "a debugger is attached"; } foreach (Assembly assembly in new[] { typeof(Program).Assembly, typeof(Sort).Assembly }) { if (IsJitOptimizerDisabled(assembly)) { return assembly.GetName().Name + " is a Debug build"; } } return null; } /// /// One core at high priority, so the scheduler neither migrates the timing loop nor preempts it for background /// work. /// public static void ClaimOneCore() { Process self = Process.GetCurrentProcess(); self.PriorityClass = ProcessPriorityClass.High; self.ProcessorAffinity = (IntPtr)(1L << (Environment.ProcessorCount - 1)); } /// /// How busy every processor was over a short sample, or null where Windows won't say. /// public static float? SystemBusyPercent() { if (!GetSystemTimes(out long idleBefore, out long kernelBefore, out long userBefore)) { return null; } Thread.Sleep(BusySampleMilliseconds); if (!GetSystemTimes(out long idleAfter, out long kernelAfter, out long userAfter)) { return null; } // Kernel time includes idle time. long total = kernelAfter - kernelBefore + userAfter - userBefore; long idle = idleAfter - idleBefore; if (total <= 0) { return null; } return 100f * (total - idle) / total; } public static int Main(string[] args) { bool quick = false; bool values = false; bool items = false; bool random = false; bool trig = false; string filter = ""; foreach (string arg in args) { if (arg == QuickOption) { quick = true; } else if (arg == ValuesOption) { values = true; } else if (arg == ItemsOption) { items = true; } else if (arg == RandomOption) { random = true; } else if (arg == TrigOption) { trig = true; } else if (arg.StartsWith("--", StringComparison.Ordinal) || filter != "") { Console.WriteLine(Usage); return 2; } else { filter = arg; } } string meaningless = ReasonTimingsAreMeaningless(); if (meaningless != null) { Console.WriteLine("Timings here would mean nothing, because " + meaningless + "."); return 1; } float? busy = SystemBusyPercent(); ClaimOneCore(); Console.WriteLine($".NET {Environment.Version}, {IntPtr.Size * 8}-bit, {Environment.ProcessorCount} " + $"logical processors, timer at {Stopwatch.Frequency / 1e6:0.##} MHz, pinned to the last processor at " + "high priority."); if (busy > BusyMachinePercent) { Console.WriteLine($"WARNING: the machine was {busy:0}% busy before starting. Absolute times will be " + "inflated and noisy; every row has a control, so read the speedups."); } Console.WriteLine(); int rounds = quick ? Measurement.QuickRounds : Measurement.Rounds; bool everything = !values && !items && !random && !trig; if (everything || values) { ValueSorting.Run(filter, rounds); } if (everything || items) { ItemSorting.Run(filter, rounds); } if (everything || random) { RandomDrawing.Run(filter, rounds); } if (everything || trig) { TrigFunctions.Run(filter, rounds); } return 0; } } }