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