Skip to content

Commit

Permalink
Merge pull request #4816 from andralex/sort
Browse files Browse the repository at this point in the history
Optimized sort, 4%-8% speed improvements
  • Loading branch information
andralex committed Sep 25, 2016
2 parents 644fa82 + a439324 commit dd5ebbf
Showing 1 changed file with 149 additions and 24 deletions.
173 changes: 149 additions & 24 deletions std/algorithm/sorting.d
Expand Up @@ -1260,39 +1260,96 @@ private size_t getPivot(alias less, Range)(Range r)
return mid;
}

private void optimisticInsertionSort(alias less, Range)(Range r)
/*
Sorting routine that is optimized for short ranges. Note: uses insertion sort
going downward. Benchmarked a similar routine that goes upward, for some reason
it's slower.
*/
private void shortSort(alias less, Range)(Range r)
{
import std.algorithm.mutation : swapAt;

alias pred = binaryFun!(less);
if (r.length < 2)

switch (r.length)
{
return;
case 0: case 1:
return;
case 2:
if (pred(r[1], r[0])) r.swapAt(0, 1);
return;
case 3:
if (pred(r[2], r[0]))
{
if (pred(r[0], r[1]))
{
r.swapAt(0, 1);
r.swapAt(0, 2);
}
else
{
r.swapAt(0, 2);
if (pred(r[1], r[0])) r.swapAt(0, 1);
}
}
else
{
if (pred(r[1], r[0]))
{
r.swapAt(0, 1);
}
else
{
if (pred(r[2], r[1])) r.swapAt(1, 2);
}
}
return;
case 4:
if (pred(r[1], r[0])) r.swapAt(0, 1);
if (pred(r[3], r[2])) r.swapAt(2, 3);
if (pred(r[2], r[0])) r.swapAt(0, 2);
if (pred(r[3], r[1])) r.swapAt(1, 3);
if (pred(r[2], r[1])) r.swapAt(1, 2);
return;
default:
sort5!pred(r[r.length - 5 .. r.length]);
if (r.length == 5) return;
break;
}

immutable maxJ = r.length - 1;
for (size_t i = r.length - 2; i != size_t.max; --i)
assert(r.length >= 6);
/* The last 5 elements of the range are sorted. Proceed with expanding the
sorted portion downward. */
immutable maxJ = r.length - 2;
for (size_t i = r.length - 6; ; --i)
{
size_t j = i;

static if (hasAssignableElements!Range)
static if (is(typeof(() nothrow {
auto t = r[0]; if (pred(t, r[0])) r[0] = r[0];
}))) // Can we afford to temporarily invalidate the array?
{
size_t j = i + 1;
auto temp = r[i];

for (; j < maxJ && pred(r[j + 1], temp); ++j)
if (pred(r[j], temp))
{
r[j] = r[j + 1];
do
{
r[j - 1] = r[j];
++j;
}
while (j < r.length && pred(r[j], temp));
r[j - 1] = temp;
}

r[j] = temp;
}
else
{
for (; j < maxJ && pred(r[j + 1], r[j]); ++j)
size_t j = i;
while (pred(r[j + 1], r[j]))
{
r.swapAt(j, j + 1);
if (j == maxJ) break;
++j;
}
}
if (i == 0) break;
}
}

Expand All @@ -1310,10 +1367,78 @@ private void optimisticInsertionSort(alias less, Range)(Range r)
e = uniform(-100, 100, rnd);
}

optimisticInsertionSort!(binaryFun!("a < b"), int[])(a);
shortSort!(binaryFun!("a < b"), int[])(a);
assert(isSorted(a));
}

/*
Sorts the first 5 elements exactly of range r.
*/
private void sort5(alias lt, Range)(Range r)
{
assert(r.length >= 5);

import std.algorithm : swapAt;

// 1. Sort first two pairs
if (lt(r[1], r[0])) r.swapAt(0, 1);
if (lt(r[3], r[2])) r.swapAt(2, 3);

// 2. Arrange first two pairs by the largest element
if (lt(r[3], r[1]))
{
r.swapAt(0, 2);
r.swapAt(1, 3);
}
assert(!lt(r[1], r[0]) && !lt(r[3], r[1]) && !lt(r[3], r[2]));

// 3. Insert 4 into [0, 1, 3]
if (lt(r[4], r[1]))
{
r.swapAt(3, 4);
r.swapAt(1, 3);
if (lt(r[1], r[0]))
{
r.swapAt(0, 1);
}
}
else if (lt(r[4], r[3]))
{
r.swapAt(3, 4);
}
assert(!lt(r[1], r[0]) && !lt(r[3], r[1]) && !lt(r[4], r[3]));

// 4. Insert 2 into [0, 1, 3, 4] (note: we already know the last is greater)
assert(!lt(r[4], r[2]));
if (lt(r[2], r[1]))
{
r.swapAt(1, 2);
if (lt(r[1], r[0]))
{
r.swapAt(0, 1);
}
}
else if (lt(r[3], r[2]))
{
r.swapAt(2, 3);
}
// 7 comparisons, 0-9 swaps
}

@safe unittest
{
import std.algorithm.iteration : permutations;
import std.algorithm.mutation : copy;

int[5] buf;
foreach (per; iota(5).permutations)
{
per.copy(buf[]);
sort5!((a, b) => a < b)(buf[]);
assert(buf[].isSorted);
}
}

// sort
/**
Sorts a random-access range according to the predicate $(D less). Performs
Expand Down Expand Up @@ -1548,14 +1673,14 @@ sort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable,
private void quickSortImpl(alias less, Range)(Range r, size_t depth)
{
import std.algorithm.mutation : swap, swapAt;
import std.algorithm.comparison : min;
import std.algorithm.comparison : min, max;

alias Elem = ElementType!(Range);
enum size_t optimisticInsertionSortGetsBetter = 25;
static assert(optimisticInsertionSortGetsBetter >= 1);
enum size_t shortSortGetsBetter = max(32, 1024 / Elem.sizeof);
static assert(shortSortGetsBetter >= 1);

// partition
while (r.length > optimisticInsertionSortGetsBetter)
while (r.length > shortSortGetsBetter)
{
if (depth == 0)
{
Expand Down Expand Up @@ -1596,9 +1721,9 @@ private void quickSortImpl(alias less, Range)(Range r, size_t depth)
r = left;
}
// residual sort
static if (optimisticInsertionSortGetsBetter > 1)
static if (shortSortGetsBetter > 1)
{
optimisticInsertionSort!(less, Range)(r);
shortSort!(less, Range)(r);
}
}

Expand Down Expand Up @@ -2475,7 +2600,7 @@ schwartzSort(alias transform, alias less = "a < b",

static double entropy(double[] probs) {
double result = 0;
foreach (p; probs)
foreach (ref p; probs)
{
if (!p) continue;
//enforce(p > 0 && p <= 1, "Wrong probability passed to entropy");
Expand Down Expand Up @@ -2509,7 +2634,7 @@ schwartzSort(alias transform, alias less = "a < b",

static double entropy(double[] probs) {
double result = 0;
foreach (p; probs)
foreach (ref p; probs)
{
if (!p) continue;
//enforce(p > 0 && p <= 1, "Wrong probability passed to entropy");
Expand Down

0 comments on commit dd5ebbf

Please sign in to comment.