Skip to content

Commit 49f2cb8

Browse files
committed
Decouple sum/prod promotion from reduce
1 parent 9fe391d commit 49f2cb8

File tree

8 files changed

+155
-86
lines changed

8 files changed

+155
-86
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ Language changes
115115
* The keyword `importall` is deprecated. Use `using` and/or individual `import` statements
116116
instead ([#22789]).
117117

118+
* `reduce(+, [...])` and `reduce(*, [...])` no longer widen the iterated over arguments to
119+
system word size. `sum` and `prod` still preserve this behavior. ([#22825])
120+
118121
Breaking changes
119122
----------------
120123

base/process.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,6 @@ setenv(cmd::Cmd; dir="") = Cmd(cmd; dir=dir)
230230
(&)(left::AbstractCmd, right::AbstractCmd) = AndCmds(left, right)
231231
redir_out(src::AbstractCmd, dest::AbstractCmd) = OrCmds(src, dest)
232232
redir_err(src::AbstractCmd, dest::AbstractCmd) = ErrOrCmds(src, dest)
233-
Base.mr_empty(f, op::typeof(&), T::Type{<:Base.AbstractCmd}) =
234-
throw(ArgumentError("reducing over an empty collection of type $T with operator & is not allowed"))
235233

236234
# Stream Redirects
237235
redir_out(dest::Redirectable, src::AbstractCmd) = CmdRedirect(src, dest, STDIN_NO)

base/reduce.jl

Lines changed: 83 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,29 @@
55
###### Generic (map)reduce functions ######
66

77
if Int === Int32
8-
const SmallSigned = Union{Int8,Int16}
9-
const SmallUnsigned = Union{UInt8,UInt16}
8+
const SmallSigned = Union{Int8,Int16}
9+
const SmallUnsigned = Union{UInt8,UInt16}
1010
else
11-
const SmallSigned = Union{Int8,Int16,Int32}
12-
const SmallUnsigned = Union{UInt8,UInt16,UInt32}
11+
const SmallSigned = Union{Int8,Int16,Int32}
12+
const SmallUnsigned = Union{UInt8,UInt16,UInt32}
1313
end
1414

15-
const CommonReduceResult = Union{UInt64,UInt128,Int64,Int128,Float16,Float32,Float64}
16-
const WidenReduceResult = Union{SmallSigned, SmallUnsigned}
17-
18-
promote_sys_size{T}(::Type{T}) = T
19-
promote_sys_size{T<:SmallSigned}(::Type{T}) = Int
20-
promote_sys_size{T<:SmallUnsigned}(::Type{T}) = UInt
21-
# r_promote_type: promote T to the type of reduce(op, ::Array{T})
22-
# (some "extra" methods are required here to avoid ambiguity warnings)
23-
r_promote_type(op, ::Type{T}) where {T} = T
24-
r_promote_type(op, ::Type{T}) where {T<:WidenReduceResult} = promote_sys_size(T)
25-
r_promote_type(::typeof(+), ::Type{T}) where {T<:WidenReduceResult} = promote_sys_size(T)
26-
r_promote_type(::typeof(*), ::Type{T}) where {T<:WidenReduceResult} = promote_sys_size(T)
27-
r_promote_type(::typeof(+), ::Type{T}) where {T<:Number} = typeof(zero(T)+zero(T))
28-
r_promote_type(::typeof(*), ::Type{T}) where {T<:Number} = typeof(one(T)*one(T))
29-
r_promote_type(::typeof(scalarmax), ::Type{T}) where {T<:WidenReduceResult} = T
30-
r_promote_type(::typeof(scalarmin), ::Type{T}) where {T<:WidenReduceResult} = T
31-
r_promote_type(::typeof(max), ::Type{T}) where {T<:WidenReduceResult} = T
32-
r_promote_type(::typeof(min), ::Type{T}) where {T<:WidenReduceResult} = T
33-
34-
# r_promote: promote x to the type of reduce(op, [x])
35-
r_promote(op, x::T) where {T} = convert(r_promote_type(op, T), x)
15+
# Certain reductions like sum and prod may wish to promote the items being
16+
# reduced over to an appropriate size.
17+
promote_sys_size(x) = x
18+
promote_sys_size(x::Union{Bool, SmallSigned}) = Int(x)
19+
promote_sys_size(x::SmallUnsigned) = UInt(x)
3620

3721
## foldl && mapfoldl
3822

3923
@noinline function mapfoldl_impl(f, op, v0, itr, i)
4024
# Unroll the while loop once; if v0 is known, the call to op may
4125
# be evaluated at compile time
4226
if done(itr, i)
43-
return r_promote(op, v0)
27+
return v0
4428
else
4529
(x, i) = next(itr, i)
46-
v = op(r_promote(op, v0), f(x))
30+
v = op(v0, f(x))
4731
while !done(itr, i)
4832
@inbounds (x, i) = next(itr, i)
4933
v = op(v, f(x))
@@ -71,7 +55,7 @@ In general, this cannot be used with empty collections (see [`reduce(op, itr)`](
7155
function mapfoldl(f, op, itr)
7256
i = start(itr)
7357
if done(itr, i)
74-
return Base.mr_empty_iter(f, op, itr, iteratoreltype(itr))
58+
return Base.mapreduce_empty_iter(f, op, itr, iteratoreltype(itr))
7559
end
7660
(x, i) = next(itr, i)
7761
v0 = f(x)
@@ -110,10 +94,10 @@ function mapfoldr_impl(f, op, v0, itr, i::Integer)
11094
# Unroll the while loop once; if v0 is known, the call to op may
11195
# be evaluated at compile time
11296
if isempty(itr) || i == 0
113-
return r_promote(op, v0)
97+
return v0
11498
else
11599
x = itr[i]
116-
v = op(f(x), r_promote(op, v0))
100+
v = op(f(x), v0)
117101
while i > 1
118102
x = itr[i -= 1]
119103
v = op(f(x), v)
@@ -141,7 +125,7 @@ In general, this cannot be used with empty collections (see [`reduce(op, itr)`](
141125
function mapfoldr(f, op, itr)
142126
i = endof(itr)
143127
if isempty(itr)
144-
return Base.mr_empty_iter(f, op, itr, iteratoreltype(itr))
128+
return Base.mapreduce_empty_iter(f, op, itr, iteratoreltype(itr))
145129
end
146130
return mapfoldr_impl(f, op, f(itr[i]), itr, i-1)
147131
end
@@ -184,12 +168,12 @@ foldr(op, itr) = mapfoldr(identity, op, itr)
184168
@noinline function mapreduce_impl(f, op, A::AbstractArray, ifirst::Integer, ilast::Integer, blksize::Int)
185169
if ifirst == ilast
186170
@inbounds a1 = A[ifirst]
187-
return r_promote(op, f(a1))
171+
return f(a1)
188172
elseif ifirst + blksize > ilast
189173
# sequential portion
190174
@inbounds a1 = A[ifirst]
191175
@inbounds a2 = A[ifirst+1]
192-
v = op(r_promote(op, f(a1)), r_promote(op, f(a2)))
176+
v = op(f(a1), f(a2))
193177
@simd for i = ifirst + 2 : ilast
194178
@inbounds ai = A[i]
195179
v = op(v, f(ai))
@@ -248,39 +232,49 @@ pairwise_blocksize(::typeof(abs2), ::typeof(+)) = 4096
248232

249233
# handling empty arrays
250234
_empty_reduce_error() = throw(ArgumentError("reducing over an empty collection is not allowed"))
251-
mr_empty(f, op, T) = _empty_reduce_error()
252-
# use zero(T)::T to improve type information when zero(T) is not defined
253-
mr_empty(::typeof(identity), op::typeof(+), T) = r_promote(op, zero(T)::T)
254-
mr_empty(::typeof(abs), op::typeof(+), T) = r_promote(op, abs(zero(T)::T))
255-
mr_empty(::typeof(abs2), op::typeof(+), T) = r_promote(op, abs2(zero(T)::T))
256-
mr_empty(::typeof(identity), op::typeof(*), T) = r_promote(op, one(T)::T)
257-
mr_empty(::typeof(abs), op::typeof(scalarmax), T) = abs(zero(T)::T)
258-
mr_empty(::typeof(abs2), op::typeof(scalarmax), T) = abs2(zero(T)::T)
259-
mr_empty(::typeof(abs), op::typeof(max), T) = mr_empty(abs, scalarmax, T)
260-
mr_empty(::typeof(abs2), op::typeof(max), T) = mr_empty(abs2, scalarmax, T)
261-
mr_empty(f, op::typeof(&), T) = true
262-
mr_empty(f, op::typeof(|), T) = false
263-
264-
mr_empty_iter(f, op, itr, ::HasEltype) = mr_empty(f, op, eltype(itr))
265-
mr_empty_iter(f, op::typeof(&), itr, ::EltypeUnknown) = true
266-
mr_empty_iter(f, op::typeof(|), itr, ::EltypeUnknown) = false
267-
mr_empty_iter(f, op, itr, ::EltypeUnknown) = _empty_reduce_error()
235+
reduce_empty(op, T) = _empty_reduce_error()
236+
reduce_empty(::typeof(+), T) = zero(T)
237+
reduce_empty(::typeof(*), T) = one(T)
238+
reduce_empty(::typeof(&), ::Type{Bool}) = true
239+
reduce_empty(::typeof(|), ::Type{Bool}) = false
240+
241+
mapreduce_empty(f, op, T) = _empty_reduce_error()
242+
mapreduce_empty(::typeof(identity), op, T) = reduce_empty(op, T)
243+
mapreduce_empty(::typeof(promote_sys_size), op, T) =
244+
promote_sys_size(mapreduce_empty(identity, op, T))
245+
mapreduce_empty(::typeof(abs), ::typeof(+), T) = abs(zero(T))
246+
mapreduce_empty(::typeof(abs2), ::typeof(+), T) = abs2(zero(T))
247+
mapreduce_empty(::typeof(abs), ::Union{typeof(scalarmax), typeof(max)}, T) =
248+
abs(zero(T))
249+
mapreduce_empty(::typeof(abs2), ::Union{typeof(scalarmax), typeof(max)}, T) =
250+
abs2(zero(T))
251+
252+
# Allow mapreduce_empty to “see through” promote_sys_size
253+
let ComposedFunction = typename(typeof(identity identity)).wrapper
254+
global mapreduce_empty(f::ComposedFunction{typeof(promote_sys_size)}, op, T) =
255+
promote_sys_size(mapreduce_empty(f.g, op, T))
256+
end
257+
258+
mapreduce_empty_iter(f, op, itr, ::HasEltype) = mapreduce_empty(f, op, eltype(itr))
259+
mapreduce_empty_iter(f, op::typeof(&), itr, ::EltypeUnknown) = true
260+
mapreduce_empty_iter(f, op::typeof(|), itr, ::EltypeUnknown) = false
261+
mapreduce_empty_iter(f, op, itr, ::EltypeUnknown) = _empty_reduce_error()
268262

269263
_mapreduce(f, op, A::AbstractArray) = _mapreduce(f, op, IndexStyle(A), A)
270264

271265
function _mapreduce(f, op, ::IndexLinear, A::AbstractArray{T}) where T
272266
inds = linearindices(A)
273267
n = length(inds)
274268
if n == 0
275-
return mr_empty(f, op, T)
269+
return mapreduce_empty(f, op, T)
276270
elseif n == 1
277271
@inbounds a1 = A[inds[1]]
278-
return r_promote(op, f(a1))
272+
return f(a1)
279273
elseif n < 16 # process short array here, avoid mapreduce_impl() compilation
280274
@inbounds i = inds[1]
281275
@inbounds a1 = A[i]
282276
@inbounds a2 = A[i+=1]
283-
s = op(r_promote(op, f(a1)), r_promote(op, f(a2)))
277+
s = op(f(a1), f(a2))
284278
while i < last(inds)
285279
@inbounds Ai = A[i+=1]
286280
s = op(s, f(Ai))
@@ -303,9 +297,6 @@ Reduce the given collection `itr` with the given binary operator `op`. `v0` must
303297
neutral element for `op` that will be returned for empty collections. It is unspecified
304298
whether `v0` is used for non-empty collections.
305299
306-
The return type is `Int` (`UInt`) for (un)signed integers of less than system word size.
307-
For all other arguments, a common return type is found to which all arguments are promoted.
308-
309300
Reductions for certain commonly-used operators may have special implementations, and
310301
should be used instead: `maximum(itr)`, `minimum(itr)`, `sum(itr)`, `prod(itr)`,
311302
`any(itr)`, `all(itr)`.
@@ -351,24 +342,47 @@ reduce(op, a::Number) = a
351342
352343
Sum the results of calling function `f` on each element of `itr`.
353344
345+
The return type is `Int` for signed integers of less than system word size, and
346+
`UInt` for unsigned integers of less than system word size. For all other
347+
arguments, a common return type is found to which all arguments are promoted.
348+
354349
```jldoctest
355350
julia> sum(abs2, [2; 3; 4])
356351
29
357352
```
353+
354+
Note the important difference between `sum(A)` and `reduce(+, A)` for arrays
355+
with small integer eltype:
356+
357+
```jldoctest
358+
julia> sum(Int8[100, 28])
359+
128
360+
361+
julia> reduce(+, Int8[100, 28])
362+
-128
363+
```
364+
365+
In the former case, the integers are widened to system word size and therefore
366+
the result is 128. In the latter case, no such widening happens and integer
367+
overflow results in -128.
358368
"""
359-
sum(f::Callable, a) = mapreduce(f, +, a)
369+
sum(f::Callable, a) = mapreduce(promote_sys_size f, +, a)
360370

361371
"""
362372
sum(itr)
363373
364374
Returns the sum of all elements in a collection.
365375
376+
The return type is `Int` for signed integers of less than system word size, and
377+
`UInt` for unsigned integers of less than system word size. For all other
378+
arguments, a common return type is found to which all arguments are promoted.
379+
366380
```jldoctest
367381
julia> sum(1:20)
368382
210
369383
```
370384
"""
371-
sum(a) = mapreduce(identity, +, a)
385+
sum(a) = mapreduce(promote_sys_size, +, a)
372386
sum(a::AbstractArray{Bool}) = count(a)
373387

374388

@@ -383,7 +397,7 @@ summation algorithm for additional accuracy.
383397
"""
384398
function sum_kbn(A)
385399
T = _default_eltype(typeof(A))
386-
c = r_promote(+, zero(T)::T)
400+
c = promote_sys_size(zero(T)::T)
387401
i = start(A)
388402
if done(A, i)
389403
return c
@@ -409,24 +423,32 @@ end
409423
410424
Returns the product of `f` applied to each element of `itr`.
411425
426+
The return type is `Int` for signed integers of less than system word size, and
427+
`UInt` for unsigned integers of less than system word size. For all other
428+
arguments, a common return type is found to which all arguments are promoted.
429+
412430
```jldoctest
413431
julia> prod(abs2, [2; 3; 4])
414432
576
415433
```
416434
"""
417-
prod(f::Callable, a) = mapreduce(f, *, a)
435+
prod(f::Callable, a) = mapreduce(promote_sys_size f, *, a)
418436

419437
"""
420438
prod(itr)
421439
422440
Returns the product of all elements of a collection.
423441
442+
The return type is `Int` for signed integers of less than system word size, and
443+
`UInt` for unsigned integers of less than system word size. For all other
444+
arguments, a common return type is found to which all arguments are promoted.
445+
424446
```jldoctest
425447
julia> prod(1:20)
426448
2432902008176640000
427449
```
428450
"""
429-
prod(a) = mapreduce(identity, *, a)
451+
prod(a) = mapreduce(promote_sys_size, *, a)
430452

431453
## maximum & minimum
432454

base/reducedim.jl

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -137,19 +137,22 @@ reducedim_init(f, op::typeof(|), A::AbstractArray, region) = reducedim_initarray
137137

138138
# specialize to make initialization more efficient for common cases
139139

140-
for (IT, RT) in ((CommonReduceResult, :(eltype(A))), (SmallSigned, :Int), (SmallUnsigned, :UInt))
141-
T = Union{[AbstractArray{t} for t in uniontypes(IT)]..., [AbstractArray{Complex{t}} for t in uniontypes(IT)]...}
142-
@eval begin
143-
reducedim_init(f::typeof(identity), op::typeof(+), A::$T, region) =
144-
reducedim_initarray(A, region, zero($RT))
145-
reducedim_init(f::typeof(identity), op::typeof(*), A::$T, region) =
146-
reducedim_initarray(A, region, one($RT))
147-
reducedim_init(f::Union{typeof(abs),typeof(abs2)}, op::typeof(+), A::$T, region) =
148-
reducedim_initarray(A, region, real(zero($RT)))
149-
reducedim_init(f::Union{typeof(abs),typeof(abs2)}, op::typeof(*), A::$T, region) =
150-
reducedim_initarray(A, region, real(one($RT)))
151-
end
140+
let
141+
BitIntFloat = Union{BitInteger, Math.IEEEFloat}
142+
T = Union{
143+
[AbstractArray{t} for t in uniontypes(BitIntFloat)]...,
144+
[AbstractArray{Complex{t}} for t in uniontypes(BitIntFloat)]...}
145+
146+
global reducedim_init(f::typeof(identity), op::typeof(+), A::T, region) =
147+
reducedim_initarray(A, region, zero(eltype(A)))
148+
global reducedim_init(f::typeof(identity), op::typeof(*), A::T, region) =
149+
reducedim_initarray(A, region, one(eltype(A)))
150+
global reducedim_init(f::Union{typeof(abs),typeof(abs2)}, op::typeof(+), A::T, region) =
151+
reducedim_initarray(A, region, real(zero(eltype(A))))
152+
global reducedim_init(f::Union{typeof(abs),typeof(abs2)}, op::typeof(*), A::T, region) =
153+
reducedim_initarray(A, region, real(one(eltype(A))))
152154
end
155+
153156
reducedim_init(f::Union{typeof(identity),typeof(abs),typeof(abs2)}, op::typeof(+), A::AbstractArray{Bool}, region) =
154157
reducedim_initarray(A, region, 0)
155158

@@ -610,14 +613,21 @@ any!(r, A)
610613
for (fname, op) in [(:sum, :+), (:prod, :*),
611614
(:maximum, :scalarmax), (:minimum, :scalarmin),
612615
(:all, :&), (:any, :|)]
616+
function compose_promote_sys_size(x)
617+
if fname in [:sum, :prod]
618+
:(promote_sys_size $x)
619+
else
620+
x
621+
end
622+
end
613623
fname! = Symbol(fname, '!')
614624
@eval begin
615625
$(fname!)(f::Function, r::AbstractArray, A::AbstractArray; init::Bool=true) =
616626
mapreducedim!(f, $(op), initarray!(r, $(op), init, A), A)
617627
$(fname!)(r::AbstractArray, A::AbstractArray; init::Bool=true) = $(fname!)(identity, r, A; init=init)
618628

619629
$(fname)(f::Function, A::AbstractArray, region) =
620-
mapreducedim(f, $(op), A, region)
630+
mapreducedim($(compose_promote_sys_size(:f)), $(op), A, region)
621631
$(fname)(A::AbstractArray, region) = $(fname)(identity, A, region)
622632
end
623633
end

base/sparse/sparsematrix.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1674,7 +1674,7 @@ function Base._mapreduce(f, op, ::Base.IndexCartesian, A::SparseMatrixCSC{T}) wh
16741674
n = length(A)
16751675
if z == 0
16761676
if n == 0
1677-
Base.mr_empty(f, op, T)
1677+
Base.mapreduce_empty(f, op, T)
16781678
else
16791679
_mapreducezeros(f, op, T, n-z-1, f(zero(T)))
16801680
end

base/tuple.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,13 @@ reverse(t::Tuple) = revargs(t...)
308308

309309
# TODO: these definitions cannot yet be combined, since +(x...)
310310
# where x might be any tuple matches too many methods.
311+
# TODO: this is inconsistent with the regular sum in cases where the arguments
312+
# require size promotion to system size.
311313
sum(x::Tuple{Any, Vararg{Any}}) = +(x...)
312314

313315
# NOTE: should remove, but often used on array sizes
316+
# TODO: this is inconsistent with the regular prod in cases where the arguments
317+
# require size promotion to system size.
314318
prod(x::Tuple{}) = 1
315319
prod(x::Tuple{Any, Vararg{Any}}) = *(x...)
316320

0 commit comments

Comments
 (0)