The minimum and maximum values of an iterable can be found either with minimum
and maximum
, and more conveniently(*) with extrema
.
Also, the positions of those extrema can be found with findmin
and findmax
(or argmin
, argmax
) separately. But I don’t know of any function in Base to obtain both at once. Did I miss it, or is there any reason why findextrema
or argextrema
are not desirable?
(*) I also expected that extrema
would be more performant than minimum
and maximum
separately, but it isn’t .
2 Likes
DNF
October 4, 2024, 5:30pm
2
Are you sure about that?
julia> @btime (minimum(x), maximum(x)) setup=(x=randn(10_000))
62.222 μs (0 allocations: 0 bytes)
(-3.6004358584957203, 4.050586645172776)
julia> @btime extrema(x) setup=(x=randn(10_000))
30.402 μs (0 allocations: 0 bytes)
(-4.16019176790883, 3.907089251771992)
DNF
October 4, 2024, 5:34pm
3
Surprisingly, though, findmin/max
is faster than minimum/maximum
:
julia> @btime (findmin(x), findmax(x)) setup=(x=randn(10_000))
33.522 μs (0 allocations: 0 bytes)
((-3.451315161397838, 4075), (3.534236845843399, 8767))
DNF:
Are you sure about that?
See the linked issue — extrema
fails to SIMD-vectorize on some systems, apparently. So it depends on your CPU.
Yep, exactly. I still see that issue on my Mac’s ARM processor. The 4x difference is quite telling.
julia> @btime (minimum(x), maximum(x)) setup=(x=randn(10_000))
12.416 μs (0 allocations: 0 bytes)
(-3.989876350573738, 3.444422298891971)
julia> @btime (extrema(x)) setup=(x=randn(10_000))
49.042 μs (0 allocations: 0 bytes)
(-3.639776550439404, 3.7251211876149286)
julia> @btime (minimum(x), maximum(x)) setup=(x=rand(-100:100, 10_000))
6.125 μs (0 allocations: 0 bytes)
(-100, 100)
julia> @btime (extrema(x)) setup=(x=rand(-100:100, 10_000))
3.073 μs (0 allocations: 0 bytes)
(-100, 100)
DNF
October 4, 2024, 5:48pm
6
Here’s a quick-and-dirty implementation of findextrema
which is much faster on my laptop at least:
function findextrema(x)
isempty(x) && throw(ArgumentError("reducing over an empty collection is not allowed."))
minind = maxind = firstindex(x)
minval = maxval = first(x)
for (i, val) in pairs(x)
(minind, minval) = ifelse(val < minval, (i, val), (minind, minval))
(maxind, maxval) = ifelse(val > maxval, (i, val), (maxind, maxval))
end
return (minind, minval), (maxind, maxval)
end
Benchmark:
julia> @btime extrema(x) setup=(x=randn(10_000))
30.468 μs (0 allocations: 0 bytes)
(-4.460544193815779, 3.614705321217675)
julia> @btime (findmin(x), findmax(x)) setup=(x=randn(10_000))
33.541 μs (0 allocations: 0 bytes)
((-3.9324850320327984, 4062), (3.7604910904523896, 502))
julia> @btime findextrema(x) setup=(x=randn(10_000))
8.370 μs (0 allocations: 0 bytes)
((9567, -3.7857378107436417), (4247, 3.8199475029280956))
This is an Intel processor, though.
2 Likes