mirror of
https://github.com/dlang/phobos.git
synced 2025-04-29 22:50:38 +03:00
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:
parent
5356dc2353
commit
169c82c938
2 changed files with 188 additions and 21 deletions
22
changelog/std-algorithm-searching-extremum.dd
Normal file
22
changelog/std-algorithm-searching-extremum.dd
Normal 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).
|
|
@ -1242,38 +1242,40 @@ Params:
|
|||
Returns:
|
||||
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)
|
||||
if (isInputRange!Range && !isInfinite!Range)
|
||||
private auto extremum(alias map, alias selector = "a < b", Range)(Range r)
|
||||
if (isInputRange!Range && !isInfinite!Range &&
|
||||
is(typeof(unaryFun!map(ElementType!(Range).init))))
|
||||
in
|
||||
{
|
||||
assert(!r.empty, "r is an empty range");
|
||||
}
|
||||
body
|
||||
{
|
||||
alias mapFun = unaryFun!map;
|
||||
alias Element = ElementType!Range;
|
||||
Unqual!Element seed = r.front;
|
||||
r.popFront();
|
||||
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)
|
||||
(Range r, RangeElementType seedElement)
|
||||
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 selectorFun = binaryFun!selector;
|
||||
|
||||
alias Element = ElementType!Range;
|
||||
alias CommonElement = CommonType!(Element, RangeElementType);
|
||||
alias MapType = Unqual!(typeof(mapFun(CommonElement.init)));
|
||||
|
||||
Unqual!CommonElement extremeElement = seedElement;
|
||||
|
||||
alias MapType = Unqual!(typeof(mapFun(CommonElement.init)));
|
||||
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)
|
||||
{
|
||||
|
@ -1301,7 +1303,55 @@ if (isInputRange!Range && !isInfinite!Range &&
|
|||
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
|
||||
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
|
||||
assert([[0, 4], [1, 2]].extremum!("a[0]", "a > b") == [1, 2]);
|
||||
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
|
||||
{
|
||||
// allow seeds
|
||||
int[] arr;
|
||||
assert(arr.extremum(1) == 1);
|
||||
|
||||
// 2d seeds
|
||||
int[][] arr2d;
|
||||
assert(arr2d.extremum([1]) == [1]);
|
||||
|
||||
|
@ -1325,6 +1386,46 @@ if (isInputRange!Range && !isInfinite!Range &&
|
|||
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
|
||||
/**
|
||||
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:
|
||||
$(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)
|
||||
{
|
||||
return extremum!map(r);
|
||||
}
|
||||
|
||||
/// 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)
|
||||
if (isInputRange!Range && !isInfinite!Range &&
|
||||
!is(CommonType!(ElementType!Range, RangeElementType) == void))
|
||||
|
@ -3252,6 +3360,15 @@ if (isInputRange!Range && !isInfinite!Range &&
|
|||
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
|
||||
{
|
||||
|
@ -3299,7 +3416,13 @@ if (isInputRange!Range && !isInfinite!Range &&
|
|||
{
|
||||
DummyType d;
|
||||
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
|
||||
|
@ -3329,21 +3452,37 @@ Returns: The maximal element of the passed-in range.
|
|||
See_Also:
|
||||
$(REF max, std,algorithm,comparison)
|
||||
*/
|
||||
auto maxElement(alias map = "a", Range)(Range r)
|
||||
if (isInputRange!Range && !isInfinite!Range &&
|
||||
!is(CommonType!(ElementType!Range, RangeElementType) == void))
|
||||
auto maxElement(alias map, Range)(Range r)
|
||||
if (isInputRange!Range && !isInfinite!Range)
|
||||
{
|
||||
return extremum!(map, "a > b")(r);
|
||||
}
|
||||
|
||||
/// ditto
|
||||
auto maxElement(alias map = "a", Range, RangeElementType = ElementType!Range)
|
||||
(Range r, RangeElementType seed)
|
||||
auto maxElement(Range)(Range r)
|
||||
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);
|
||||
}
|
||||
|
||||
/// 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
|
||||
{
|
||||
|
@ -3392,7 +3531,14 @@ if (isInputRange!Range && !isInfinite!Range)
|
|||
{
|
||||
DummyType d;
|
||||
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
|
||||
|
@ -3404,7 +3550,6 @@ if (isInputRange!Range && !isInfinite!Range)
|
|||
assert(arr2d.maxElement!"a[1]" == arr2d[1]);
|
||||
}
|
||||
|
||||
|
||||
// minPos
|
||||
/**
|
||||
Computes a subrange of `range` starting at the first occurrence of `range`'s
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue