Skip to content

faster maximum, minimum (#28849) #30320

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 52 additions & 8 deletions base/reduce.jl
Original file line number Diff line number Diff line change
Expand Up @@ -452,19 +452,63 @@ julia> prod(1:20)
prod(a) = mapreduce(identity, mul_prod, a)

## maximum & minimum
function _fast(::typeof(max),x,y)
ifelse(isnan(x),
x,
ifelse(x > y, x, y))
end

function _fast(::typeof(min),x,y)
ifelse(isnan(x),
x,
ifelse(x < y, x, y))
end

isbadzero(::typeof(max), x::AbstractFloat) = (x == zero(x)) & signbit(x)
isbadzero(::typeof(min), x::AbstractFloat) = (x == zero(x)) & !signbit(x)
isbadzero(op, x) = false
isgoodzero(::typeof(max), x) = isbadzero(min, x)
isgoodzero(::typeof(min), x) = isbadzero(max, x)

function mapreduce_impl(f, op::Union{typeof(max), typeof(min)},
A::AbstractArray, first::Int, last::Int)
# locate the first non NaN number
@inbounds a1 = A[first]
v = mapreduce_first(f, op, a1)
i = first + 1
while (v == v) && (i <= last)
a1 = @inbounds A[first]
v1 = mapreduce_first(f, op, a1)
v2 = v3 = v4 = v1
chunk_len = 256
start = first
stop = start + chunk_len - 4
while stop <= last
isnan(v1) && return v1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This broke maximum on non-numeric values. This is visible only with long arrays:

julia> maximum(rand(Char, 500))
ERROR: MethodError: no method matching isnan(::Char)
Closest candidates are:
  isnan(::BigFloat) at mpfr.jl:879
  isnan(::Missing) at missing.jl:83
  isnan(::Float16) at float.jl:530
  ...
Stacktrace:
 [1] mapreduce_impl(::typeof(identity), ::typeof(max), ::Array{Char,1}, ::Int64, ::Int64) at ./reduce.jl:482
 [2] _mapreduce at ./reduce.jl:320 [inlined]
 [3] _mapreduce_dim at ./reducedim.jl:308 [inlined]
 [4] #mapreduce#549 at ./reducedim.jl:304 [inlined]
 [5] mapreduce at ./reducedim.jl:304 [inlined]
 [6] _maximum at ./reducedim.jl:653 [inlined]
 [7] _maximum at ./reducedim.jl:652 [inlined]
 [8] #maximum#555 at ./reducedim.jl:648 [inlined]
 [9] maximum(::Array{Char,1}) at ./reducedim.jl:648
 [10] top-level scope at none:0

isnan(v2) && return v2
isnan(v3) && return v3
isnan(v4) && return v4
@inbounds for i in start:4:stop
v1 = _fast(op, v1, f(A[i+1]))
v2 = _fast(op, v2, f(A[i+2]))
v3 = _fast(op, v3, f(A[i+3]))
v4 = _fast(op, v4, f(A[i+4]))
end
start = stop
stop = start + chunk_len - 4
end
v = op(op(v1,v2),op(v3,v4))
start += 1
for i in start:last
@inbounds ai = A[i]
v = op(v, f(ai))
i += 1
v = op(v, f(A[i]))
end
v

# enforce correct order of 0.0 and -0.0
# e.g. maximum([0.0, -0.0]) === 0.0
# should hold
if isbadzero(op, v)
for i in first:last
x = @inbounds A[i]
isgoodzero(op,x) && return x
end
end
return v
end

maximum(f, a) = mapreduce(f, max, a)
Expand Down
43 changes: 43 additions & 0 deletions test/reduce.jl
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,55 @@ prod2(itr) = invoke(prod, Tuple{Any}, itr)
@test minimum([4, 3, 5, 2]) == 2
@test extrema([4, 3, 5, 2]) == (2, 5)

@test maximum([-0.,0.]) === 0.0
@test maximum([0.,-0.]) === 0.0
@test maximum([0.,-0.,0.]) === 0.0
@test minimum([-0.,0.]) === -0.0
@test minimum([0.,-0.]) === -0.0
@test minimum([0.,-0.,0.]) === -0.0

@testset "minimum/maximum checks all elements" begin
for N in [2:20;150;300]
for i in 1:N
arr = fill(0., N)
truth = rand()
arr[i] = truth
@test maximum(arr) == truth

truth = -rand()
arr[i] = truth
@test minimum(arr) == truth

arr[i] = NaN
@test isnan(maximum(arr))
@test isnan(minimum(arr))

arr = zeros(N)
@test minimum(arr) === 0.0
@test maximum(arr) === 0.0

arr[i] = -0.0
@test minimum(arr) === -0.0
@test maximum(arr) === 0.0

arr = -zeros(N)
@test minimum(arr) === -0.0
@test maximum(arr) === -0.0
arr[i] = 0.0
@test minimum(arr) === -0.0
@test maximum(arr) === 0.0
end
end
end

@test isnan(maximum([NaN]))
@test isnan(minimum([NaN]))
@test isequal(extrema([NaN]), (NaN, NaN))

@test isnan(maximum([NaN, 2.]))
@test isnan(maximum([2., NaN]))
@test isnan(minimum([NaN, 2.]))
@test isnan(minimum([2., NaN]))
@test isequal(extrema([NaN, 2.]), (NaN,NaN))

@test isnan(maximum([NaN, 2., 3.]))
Expand Down