std.algorithm.searching: no mapping-specialization for extremum (#5001)

std.algorithm.searching: no mapping-specialization for extremum
merged-on-behalf-of: Sebastian Wilzbach <sebi.wilzbach@gmail.com>
This commit is contained in:
Sebastian Wilzbach 2017-03-03 05:00:49 +01:00 committed by The Dlang Bot
parent 5356dc2353
commit 169c82c938
2 changed files with 188 additions and 21 deletions

View file

@ -0,0 +1,22 @@
Performance improvements for `std.algorithm.searching.{min,max}Element`
$(REF minElement, std, algorithm, searching) and $(REF maxElement, std, algorithm, searching)
are now considerably faster (almost 2x in microbenchmarks)
as a special path for the identity case is provided. This allows the compiler
to make use of SSE instructions.
The exact improvements are:
$(CONSOLE
% dmd -release -O test.d && ./test
extremum.before = 54 secs, 12 ms, 347 μs, and 9 hnsecs
extremum.after = 29 secs, 521 ms, 896 μs, and 5 hnsecs
)
$(CONSOLE
% ldc -release -O3 test.d && ./test
extremum.before = 13 secs, 186 ms, 176 μs, and 4 hnsecs
extremum.after = 2 secs, 241 ms, 454 μs, and 9 hnsecs
)
$(LINK2 https://gist.github.com/wilzbach/9f757fa76200956aadb97059d614df34, See the benchmark code).

View file

@ -1242,38 +1242,40 @@ Params:
Returns: Returns:
The extreme value according to `map` and `selector` of the passed-in values. The extreme value according to `map` and `selector` of the passed-in values.
*/ */
private auto extremum(alias map = "a", alias selector = "a < b", Range)(Range r) private auto extremum(alias map, alias selector = "a < b", Range)(Range r)
if (isInputRange!Range && !isInfinite!Range) if (isInputRange!Range && !isInfinite!Range &&
is(typeof(unaryFun!map(ElementType!(Range).init))))
in in
{ {
assert(!r.empty, "r is an empty range"); assert(!r.empty, "r is an empty range");
} }
body body
{ {
alias mapFun = unaryFun!map;
alias Element = ElementType!Range; alias Element = ElementType!Range;
Unqual!Element seed = r.front; Unqual!Element seed = r.front;
r.popFront(); r.popFront();
return extremum!(map, selector)(r, seed); return extremum!(map, selector)(r, seed);
} }
private auto extremum(alias map = "a", alias selector = "a < b", Range, private auto extremum(alias map, alias selector = "a < b", Range,
RangeElementType = ElementType!Range) RangeElementType = ElementType!Range)
(Range r, RangeElementType seedElement) (Range r, RangeElementType seedElement)
if (isInputRange!Range && !isInfinite!Range && if (isInputRange!Range && !isInfinite!Range &&
!is(CommonType!(ElementType!Range, RangeElementType) == void)) !is(CommonType!(ElementType!Range, RangeElementType) == void) &&
is(typeof(unaryFun!map(ElementType!(Range).init))))
{ {
alias mapFun = unaryFun!map; alias mapFun = unaryFun!map;
alias selectorFun = binaryFun!selector; alias selectorFun = binaryFun!selector;
alias Element = ElementType!Range; alias Element = ElementType!Range;
alias CommonElement = CommonType!(Element, RangeElementType); alias CommonElement = CommonType!(Element, RangeElementType);
alias MapType = Unqual!(typeof(mapFun(CommonElement.init)));
Unqual!CommonElement extremeElement = seedElement; Unqual!CommonElement extremeElement = seedElement;
alias MapType = Unqual!(typeof(mapFun(CommonElement.init)));
MapType extremeElementMapped = mapFun(extremeElement); MapType extremeElementMapped = mapFun(extremeElement);
static if (isRandomAccessRange!Range && hasLength!Range) // direct access via a random access range is faster
static if (isRandomAccessRange!Range)
{ {
foreach (const i; 0 .. r.length) foreach (const i; 0 .. r.length)
{ {
@ -1301,7 +1303,55 @@ if (isInputRange!Range && !isInfinite!Range &&
return extremeElement; return extremeElement;
} }
@safe pure nothrow unittest private auto extremum(alias selector = "a < b", Range)(Range r)
if (isInputRange!Range && !isInfinite!Range &&
!is(typeof(unaryFun!selector(ElementType!(Range).init))))
{
alias Element = ElementType!Range;
Unqual!Element seed = r.front;
r.popFront();
return extremum!selector(r, seed);
}
// if we only have one statement in the loop it can be optimized a lot better
private auto extremum(alias selector = "a < b", Range,
RangeElementType = ElementType!Range)
(Range r, RangeElementType seedElement)
if (isInputRange!Range && !isInfinite!Range &&
!is(CommonType!(ElementType!Range, RangeElementType) == void) &&
!is(typeof(unaryFun!selector(ElementType!(Range).init))))
{
alias Element = ElementType!Range;
alias CommonElement = CommonType!(Element, RangeElementType);
Unqual!CommonElement extremeElement = seedElement;
alias selectorFun = binaryFun!selector;
// direct access via a random access range is faster
static if (isRandomAccessRange!Range)
{
foreach (const i; 0 .. r.length)
{
if (selectorFun(r[i], extremeElement))
{
extremeElement = r[i];
}
}
}
else
{
while (!r.empty)
{
if (selectorFun(r.front, extremeElement))
{
extremeElement = r.front;
}
r.popFront();
}
}
return extremeElement;
}
@safe pure unittest
{ {
// allows a custom map to select the extremum // allows a custom map to select the extremum
assert([[0, 4], [1, 2]].extremum!"a[0]" == [0, 4]); assert([[0, 4], [1, 2]].extremum!"a[0]" == [0, 4]);
@ -1310,14 +1360,25 @@ if (isInputRange!Range && !isInfinite!Range &&
// allows a custom selector for comparison // allows a custom selector for comparison
assert([[0, 4], [1, 2]].extremum!("a[0]", "a > b") == [1, 2]); assert([[0, 4], [1, 2]].extremum!("a[0]", "a > b") == [1, 2]);
assert([[0, 4], [1, 2]].extremum!("a[1]", "a > b") == [0, 4]); assert([[0, 4], [1, 2]].extremum!("a[1]", "a > b") == [0, 4]);
// use a custom comparator
import std.math : cmp;
assert([-2., 0, 5].extremum!cmp == 5.0);
assert([-2., 0, 2].extremum!`cmp(a, b) < 0` == -2.0);
// combine with map
import std.range : enumerate;
assert([-3., 0, 5].enumerate.extremum!(`a.value`, cmp) == tuple(2, 5.0));
assert([-2., 0, 2].enumerate.extremum!(`a.value`, `cmp(a, b) < 0`) == tuple(0, -2.0));
// seed with a custom value
int[] arr;
assert(arr.extremum(1) == 1);
} }
@safe pure nothrow unittest @safe pure nothrow unittest
{ {
// allow seeds // 2d seeds
int[] arr;
assert(arr.extremum(1) == 1);
int[][] arr2d; int[][] arr2d;
assert(arr2d.extremum([1]) == [1]); assert(arr2d.extremum([1]) == [1]);
@ -1325,6 +1386,46 @@ if (isInputRange!Range && !isInfinite!Range &&
assert(extremum([2, 3, 4], 1.5) == 1.5); assert(extremum([2, 3, 4], 1.5) == 1.5);
} }
@safe pure unittest
{
import std.range : enumerate, iota;
// forward ranges
assert(iota(1, 5).extremum() == 1);
assert(iota(2, 5).enumerate.extremum!"a.value" == tuple(0, 2));
// should work with const
const(int)[] immArr = [2, 1, 3];
assert(immArr.extremum == 1);
// should work with immutable
immutable(int)[] immArr2 = [2, 1, 3];
assert(immArr2.extremum == 1);
// with strings
assert(["b", "a", "c"].extremum == "a");
// with all dummy ranges
import std.internal.test.dummyrange;
foreach (DummyType; AllDummyRanges)
{
DummyType d;
assert(d.extremum == 1);
assert(d.extremum!(a => a) == 1);
assert(d.extremum!`a > b` == 10);
assert(d.extremum!(a => a, `a > b`) == 10);
}
}
@nogc @safe nothrow pure unittest
{
static immutable arr = [7, 3, 4, 2, 1, 8];
assert(arr.extremum == 1);
static immutable arr2d = [[1, 9], [3, 1], [4, 2]];
assert(arr2d.extremum!"a[1]" == arr2d[1]);
}
// find // find
/** /**
Finds an individual element in an input range. Elements of $(D Finds an individual element in an input range. Elements of $(D
@ -3237,14 +3338,21 @@ Returns: The minimal element of the passed-in range.
See_Also: See_Also:
$(REF min, std,algorithm,comparison) $(REF min, std,algorithm,comparison)
*/ */
auto minElement(alias map = "a", Range)(Range r) auto minElement(alias map, Range)(Range r)
if (isInputRange!Range && !isInfinite!Range) if (isInputRange!Range && !isInfinite!Range)
{ {
return extremum!map(r); return extremum!map(r);
} }
/// ditto /// ditto
auto minElement(alias map = "a", Range, RangeElementType = ElementType!Range) auto minElement(Range)(Range r)
if (isInputRange!Range && !isInfinite!Range)
{
return extremum(r);
}
/// ditto
auto minElement(alias map, Range, RangeElementType = ElementType!Range)
(Range r, RangeElementType seed) (Range r, RangeElementType seed)
if (isInputRange!Range && !isInfinite!Range && if (isInputRange!Range && !isInfinite!Range &&
!is(CommonType!(ElementType!Range, RangeElementType) == void)) !is(CommonType!(ElementType!Range, RangeElementType) == void))
@ -3252,6 +3360,15 @@ if (isInputRange!Range && !isInfinite!Range &&
return extremum!map(r, seed); return extremum!map(r, seed);
} }
/// ditto
auto minElement(Range, RangeElementType = ElementType!Range)
(Range r, RangeElementType seed)
if (isInputRange!Range && !isInfinite!Range &&
!is(CommonType!(ElementType!Range, RangeElementType) == void))
{
return extremum(r, seed);
}
/// ///
@safe pure unittest @safe pure unittest
{ {
@ -3299,7 +3416,13 @@ if (isInputRange!Range && !isInfinite!Range &&
{ {
DummyType d; DummyType d;
assert(d.minElement == 1); assert(d.minElement == 1);
assert(d.minElement!(a => a) == 1);
} }
// with empty, but seeded ranges
int[] arr;
assert(arr.minElement(42) == 42);
assert(arr.minElement!(a => a)(42) == 42);
} }
@nogc @safe nothrow pure unittest @nogc @safe nothrow pure unittest
@ -3329,21 +3452,37 @@ Returns: The maximal element of the passed-in range.
See_Also: See_Also:
$(REF max, std,algorithm,comparison) $(REF max, std,algorithm,comparison)
*/ */
auto maxElement(alias map = "a", Range)(Range r) auto maxElement(alias map, Range)(Range r)
if (isInputRange!Range && !isInfinite!Range && if (isInputRange!Range && !isInfinite!Range)
!is(CommonType!(ElementType!Range, RangeElementType) == void))
{ {
return extremum!(map, "a > b")(r); return extremum!(map, "a > b")(r);
} }
/// ditto /// ditto
auto maxElement(alias map = "a", Range, RangeElementType = ElementType!Range) auto maxElement(Range)(Range r)
(Range r, RangeElementType seed)
if (isInputRange!Range && !isInfinite!Range) if (isInputRange!Range && !isInfinite!Range)
{
return extremum!`a > b`(r);
}
/// ditto
auto maxElement(alias map, Range, RangeElementType = ElementType!Range)
(Range r, RangeElementType seed)
if (isInputRange!Range && !isInfinite!Range &&
!is(CommonType!(ElementType!Range, RangeElementType) == void))
{ {
return extremum!(map, "a > b")(r, seed); return extremum!(map, "a > b")(r, seed);
} }
/// ditto
auto maxElement(Range, RangeElementType = ElementType!Range)
(Range r, RangeElementType seed)
if (isInputRange!Range && !isInfinite!Range &&
!is(CommonType!(ElementType!Range, RangeElementType) == void))
{
return extremum!`a > b`(r, seed);
}
/// ///
@safe pure unittest @safe pure unittest
{ {
@ -3392,7 +3531,14 @@ if (isInputRange!Range && !isInfinite!Range)
{ {
DummyType d; DummyType d;
assert(d.maxElement == 10); assert(d.maxElement == 10);
assert(d.maxElement!(a => a) == 10);
} }
// with empty, but seeded ranges
int[] arr;
assert(arr.maxElement(42) == 42);
assert(arr.maxElement!(a => a)(42) == 42);
} }
@nogc @safe nothrow pure unittest @nogc @safe nothrow pure unittest
@ -3404,7 +3550,6 @@ if (isInputRange!Range && !isInfinite!Range)
assert(arr2d.maxElement!"a[1]" == arr2d[1]); assert(arr2d.maxElement!"a[1]" == arr2d[1]);
} }
// minPos // minPos
/** /**
Computes a subrange of `range` starting at the first occurrence of `range`'s Computes a subrange of `range` starting at the first occurrence of `range`'s