280 lines
7.5 KiB
D
280 lines
7.5 KiB
D
import util;
|
|
|
|
import std.stdio : stderr, writeln;
|
|
import std.datetime : Duration;
|
|
|
|
import std.meta : AliasSeq, ApplyLeft, staticMap, Instantiate;
|
|
|
|
alias multiPhaseTests = AliasSeq!(testInsert, testDelete, testConcat);
|
|
alias multiPhaseSetupFuns = AliasSeq!(makeAndInsert, makeAndInsert, makeAndInsert);
|
|
|
|
enum phasesPerTest = 5;
|
|
|
|
enum phaseSizes = [
|
|
//Inserts
|
|
(10 - 0), (50 - 10), (200 - 50), (1000 - 200), (10000-1000),
|
|
|
|
//Deletes
|
|
(10 - 0), (50 - 10), (200 - 50), (1000 - 200), (10000-1000),
|
|
|
|
//Concats
|
|
10, 50, 200, 1000, 10000,
|
|
];
|
|
|
|
enum phaseSetupSizes = [
|
|
//Inserts
|
|
0, 10, 50, 200, 1000,
|
|
|
|
//Deletes
|
|
10, 50, 200, 1000, 10000,
|
|
|
|
//Concats
|
|
10, 50, 200, 1000, 10000,
|
|
];
|
|
|
|
enum multiPhaseTestNames = ["Inserts", "Deletes", "Concat"];
|
|
|
|
enum phaseNames = [
|
|
//Inserts
|
|
"0-10", "10-50", "50-200", "200-1000", "1000-10000",
|
|
|
|
//Deletes
|
|
"10-0", "50-10", "200-50", "1000-200", "10000-1000",
|
|
|
|
//Concats
|
|
"10+10", "50+50", "200+20", "1000+1000", "10000+10000",
|
|
];
|
|
|
|
enum runsPerMultiPhaseTest = 10000;
|
|
|
|
alias singlePhaseTests = AliasSeq!(testMixed);
|
|
alias singlePhaseSetupFuns = AliasSeq!(noSetup);
|
|
|
|
enum singlePhaseTestNames = ["Mixed"];
|
|
|
|
alias containers = AliasSeq!(rcarray!int, int[], StdArray!int, EMSIArray!int, StdxArray!int);
|
|
enum containerNames = ["rcarray", "[]", "Phobos Array", "EMSI Array", "stdx array"];
|
|
|
|
enum runsPerSinglePhaseTest = 1000000;
|
|
|
|
void testInsert(size_t size, Container)(ref Container container)
|
|
{
|
|
static foreach (i; 0 .. size)
|
|
{
|
|
container ~= 42;
|
|
}
|
|
|
|
assert(container.length >= size && container[$ - 1] == 42);
|
|
}
|
|
|
|
void testDelete(size_t size, Container)(ref Container container)
|
|
{
|
|
assert(container.length >= size);
|
|
|
|
foreach (i; 0 .. size)
|
|
{
|
|
static if (is(Container : StdxArray!U, U))
|
|
{
|
|
container.forceLength(container.length - 1);
|
|
}
|
|
else static if (is(Container : EMSIArray!U, U))
|
|
{
|
|
container.removeBack();
|
|
}
|
|
else
|
|
{
|
|
container.length = container.length - 1;
|
|
}
|
|
}
|
|
|
|
assert(container.length >= 0);
|
|
}
|
|
|
|
void testConcat(size_t size, Container)(ref Container container)
|
|
{
|
|
auto c = container ~ container;
|
|
|
|
assert(c.length == size * 2);
|
|
}
|
|
|
|
void testMixed(Container)()
|
|
{
|
|
auto container = make!Container();
|
|
|
|
static foreach (i; 0 .. 50)
|
|
{
|
|
container ~= 42;
|
|
}
|
|
|
|
static foreach (i; 0 .. 25)
|
|
{{
|
|
static if (is(Container : StdxArray!U, U))
|
|
{
|
|
container.forceLength(container.length - 1);
|
|
}
|
|
else static if (is(Container : EMSIArray!U, U))
|
|
{
|
|
container.removeBack();
|
|
}
|
|
else
|
|
{
|
|
container.length = container.length - 1;
|
|
}
|
|
}}
|
|
|
|
static foreach (i; 0 .. 25)
|
|
{{
|
|
container ~= 42;
|
|
container ~= 42;
|
|
|
|
static if (is(Container : StdxArray!U, U))
|
|
{
|
|
container.forceLength(container.length - 2);
|
|
}
|
|
else static if (is(Container : EMSIArray!U, U))
|
|
{
|
|
container.removeBack();
|
|
container.removeBack();
|
|
}
|
|
else
|
|
{
|
|
container.length = container.length - 2;
|
|
}
|
|
}}
|
|
|
|
assert(container.length == 25 && container[24 - 1] == 42);
|
|
}
|
|
|
|
auto testContainers(Containers...)()
|
|
{
|
|
import std.datetime.stopwatch : benchmark;
|
|
import std.meta : staticMap;
|
|
import std.array : array;
|
|
|
|
Duration[][] results;
|
|
|
|
static foreach (i, test; multiPhaseTests)
|
|
{
|
|
static foreach (j; i * phasesPerTest .. (i + 1) * phasesPerTest)
|
|
{{
|
|
enum phaseSize = phaseSizes[j];
|
|
enum phaseSetupSize = phaseSetupSizes[j];
|
|
|
|
alias ts = staticMap!(ApplyLeft!(test, phaseSize), Containers);
|
|
alias ss = staticMap!(ApplyLeft!(multiPhaseSetupFuns[i], phaseSetupSize), Containers);
|
|
results ~= benchmarkWithSetup!(Transpose!(2, ts, ss))(runsPerMultiPhaseTest).array;
|
|
}}
|
|
}
|
|
|
|
static foreach (i, test; singlePhaseTests)
|
|
{{
|
|
alias ts = staticMap!(test, Containers);
|
|
alias ss = staticMap!(singlePhaseSetupFuns[i], Containers);
|
|
results ~= benchmarkWithSetup!(Transpose!(2, ts, ss))(runsPerSinglePhaseTest).array;
|
|
}}
|
|
|
|
return results;
|
|
}
|
|
|
|
void printResults(Containers...)(Duration[][] results)
|
|
{
|
|
import std.stdio : writeln;
|
|
|
|
static foreach (i, test; multiPhaseTests)
|
|
{
|
|
writeln(test.stringof, ":");
|
|
|
|
static foreach (j; 0 .. phasesPerTest)
|
|
{
|
|
writeln("\t", "Phase ", j + 1, " (", phaseNames[i * phasesPerTest + j], ")");
|
|
|
|
static foreach (k, Container; Containers)
|
|
{
|
|
writeln("\t\t", Container.stringof, ": ", results[i * phasesPerTest + j][k]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static foreach (i, test; singlePhaseTests)
|
|
{
|
|
writeln(test.stringof, ":");
|
|
|
|
static foreach (j, Container; Containers)
|
|
{
|
|
writeln("\t", Container.stringof, ": ", results[multiPhaseTests.length * phasesPerTest + i][j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void plotResults(Containers...)(Duration[][] results)
|
|
{
|
|
import plt = matplotlibd.pyplot;
|
|
|
|
import std.range : iota;
|
|
import std.algorithm : map;
|
|
import std.array : array;
|
|
import std.conv : to;
|
|
|
|
static foreach (i, test; multiPhaseTests)
|
|
{{
|
|
enum colours = ["C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9"];
|
|
static assert(Containers.length <= colours.length);
|
|
|
|
auto testResults = results[i * phasesPerTest .. (i + 1) * phasesPerTest];
|
|
auto testPhaseNames = phaseNames[i * phasesPerTest .. (i + 1) * phasesPerTest];
|
|
|
|
plt.clf();
|
|
|
|
auto title = multiPhaseTestNames[i] ~ " (" ~ runsPerMultiPhaseTest.to!string ~ " runs per phase)";
|
|
|
|
double[] x = iota(phasesPerTest).map!(to!double).array;
|
|
|
|
enum barWidth = 0.8 / Containers.length;
|
|
static foreach (j; 0 .. Containers.length)
|
|
{{
|
|
auto height = testResults.map!(t => t.map!(r => r.total!"hnsecs")).map!(t => t[j].to!double / t[0]).array;
|
|
|
|
auto xs = x.dup;
|
|
xs[] = xs[] + j * barWidth;
|
|
|
|
plt.bar(xs, height, barWidth, ["color": colours[j], "label": containerNames[j]]);
|
|
}}
|
|
|
|
auto ticks = x.map!(t => t + barWidth + barWidth / 2).array;
|
|
plt.xticks(ticks, testPhaseNames);
|
|
plt.ylabel("Relative time (lower is better)");
|
|
plt.ylim(0, 2);
|
|
plt.axhline(1, ["color": "black"]);
|
|
plt.title(title);
|
|
plt.legend();
|
|
plt.savefig("out/" ~ i.to!string ~ ".png", ["dpi": 500]);
|
|
}}
|
|
|
|
static foreach (i, test; singlePhaseTests)
|
|
{{
|
|
auto testResults = results[multiPhaseTests.length * phasesPerTest + i];
|
|
|
|
auto title = singlePhaseTestNames[i] ~ " (" ~ runsPerSinglePhaseTest.to!string ~ " runs)";
|
|
auto x = iota(testResults.length);
|
|
auto height = testResults.map!(d => d.total!"msecs").map!(to!double).array;
|
|
height = height.map!(h => h / height[0]).array;
|
|
|
|
plt.clf();
|
|
plt.bar(x, height);
|
|
plt.xticks(x, containerNames);
|
|
plt.ylabel("Relative time (lower is better)");
|
|
plt.ylim(0, 2);
|
|
plt.axhline(1, ["color": "black"]);
|
|
plt.title(title);
|
|
plt.savefig("out/" ~ (multiPhaseTests.length + i).to!string ~ ".png", ["dpi": 500]);
|
|
}}
|
|
}
|
|
|
|
void main()
|
|
{
|
|
auto results = testContainers!containers;
|
|
|
|
results.printResults!containers;
|
|
results.plotResults!containers;
|
|
}
|