using System; using System.Collections.Generic; using System.Globalization; namespace Amphora.PerformanceTests { /// /// One way of putting an array of plain values in order. It gets a fresh copy of the unsorted values each time and /// returns the sorted array, which may be the same one. /// public sealed class ValueCandidate { public readonly string Name; public readonly Func Sort; public ValueCandidate(string name, Func sort) { Name = name; Sort = sort; } } public static class ValueSorting { public static readonly int[] Sizes = { 8, 32, 128, 512, 2048, 8192, 32768, 131072, 524288 }; /// /// The candidate every other is measured against, which is framework code that doesn't change. /// public const int ControlIndex = 0; private static string Ratio(double control, double candidate) { return (control / candidate).ToString("0.00", CultureInfo.InvariantCulture) + "x"; } public static List Candidates() { var keySorter = new RadixKeySorter(); return new List { new ValueCandidate("Array.Sort (control)", values => { Array.Sort(values); return values; }), new ValueCandidate("Sort.CopyOfKeys", values => Sort.CopyOfKeys(values)), new ValueCandidate("Sort.Keys", values => { Sort.Keys(values, values.Length, keySorter); return values; }), new ValueCandidate("copy only", values => values), }; } /// /// Values with no order, spread over the range an int can hold. /// public static uint[] Random(int count) { var returnMe = new uint[count]; uint state = 12345; for (int i = 0; i < count; ++i) { state = unchecked(state * 1664525u + 1013904223u); returnMe[i] = state >> 1; } return returnMe; } /// /// Values with no order that all fit in twelve bits, so most of a full sort's work is wasted. /// public static uint[] Narrow(int count) { uint[] returnMe = Random(count); for (int i = 0; i < count; ++i) { returnMe[i] &= 0xFFF; } return returnMe; } public static uint[] Ascending(int count) { var returnMe = new uint[count]; for (int i = 0; i < count; ++i) { returnMe[i] = (uint)(i * 3); } return returnMe; } public static void Run(string filter, int rounds) { Console.WriteLine("Sorting an array of plain values. Every candidate also copies the values in, so the " + "time of the copy-only candidate is taken off before comparing. Per candidate: the fastest " + "nanoseconds a sort, warm, net of the copy, and the speedup over the control, which is Array.Sort."); List candidates = Candidates(); int copyOnlyIndex = candidates.Count - 1; foreach (var input in new (string Name, Func Make)[] { ("random", Random), ("narrow", Narrow), ("ascending", Ascending), }) { Console.WriteLine(); string header = $"{"values (" + input.Name + ")",-20}"; for (int c = 0; c < copyOnlyIndex; ++c) { header += $" {candidates[c].Name,26}"; } header += $" {"copy only ns",13}"; Console.WriteLine(header); foreach (int size in Sizes) { string name = $"{size} values, {input.Name}"; if (name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) < 0) { continue; } uint[] template = input.Make(size); var actions = new List(); var results = new uint[candidates.Count][]; for (int c = 0; c < candidates.Count; ++c) { int index = c; actions.Add(() => { var working = new uint[size]; Array.Copy(template, working, size); results[index] = candidates[index].Sort(working); }); } // Every candidate has to leave the same order, or its timing says nothing. var expected = (uint[])template.Clone(); Array.Sort(expected); for (int c = 0; c < copyOnlyIndex; ++c) { actions[c](); for (int i = 0; i < size; ++i) { if (results[c][i] != expected[i]) { throw new InvalidOperationException(candidates[c].Name + " left " + name + " out of order."); } } } Timing[] timings = Measurement.TimeTakingTurns(actions, rounds); double copyNanoseconds = timings[copyOnlyIndex].MinimumNanoseconds; double controlNet = Math.Max(1, timings[ControlIndex].MinimumNanoseconds - copyNanoseconds); string row = $"{size,-20}"; for (int c = 0; c < copyOnlyIndex; ++c) { double net = Math.Max(1, timings[c].MinimumNanoseconds - copyNanoseconds); row += $" {net,15:0} {Ratio(controlNet, net),9}"; } row += $" {copyNanoseconds,13:0}"; Console.WriteLine(row); } } Console.WriteLine(); } } }