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;
}
}
}