Skip to content

WIP: The great pairwise reduction refactor #58418

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

Draft
wants to merge 57 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
80f031f
counting and summing Bools with a small integer `init` promote to `[U…
mbauman May 10, 2025
3c3fdcc
Fix doctests
mbauman May 10, 2025
4baecd3
keep the add_sum method table small
mbauman May 11, 2025
93796ab
unify reduce/reducedim empty case behaviors
mbauman Aug 29, 2024
0c3d4b2
TEMPORARILY USE SPARSEARRAYS PR BRANCH
mbauman Apr 19, 2025
0a3f844
add two-arg axes and size for Broadcasted
mbauman May 12, 2025
19f0ebb
fully pairwise mapreduce with consistent use of init
mbauman May 12, 2025
0708c2b
fixup Broadcast size/axes
mbauman May 13, 2025
e719715
more deprecations
mbauman May 13, 2025
da3bdb1
NEWS update for empty dimensional reductions
mbauman May 13, 2025
8cef637
avoid calling length in functions that define length
mbauman May 13, 2025
89c3975
Add (offset) axes support for Enumerate
mbauman May 13, 2025
f88cfeb
make it a bit easier to extend `mapreduce_kernel`
mbauman May 13, 2025
bddc126
fixup PermutedDimsArray changes
mbauman May 13, 2025
7747f1e
avoid similar shenanigans; they are not needed
mbauman May 13, 2025
31de628
cartesian broadcasts are now pairwise!
mbauman May 13, 2025
2f5a886
TEMPORARILY USE LINEAR ALGEBRA BRANCH
mbauman May 13, 2025
aeb9a89
promote to Int
mbauman May 14, 2025
2a0b7b7
restore `reduce_first(+, true)` method
mbauman May 14, 2025
6cc2496
TEMPORARILY USE SPARSEARRAY AND STATISTICS BRANCHES
mbauman May 14, 2025
ec3def4
TEMPORARILY USE SPARSEARRAYS: mapreduce_kernel implementation!
mbauman May 19, 2025
7445ccd
fix offset IndexLinears
mbauman May 19, 2025
eed76db
support differently-offset output arrays
mbauman May 19, 2025
ed4cd1b
remove not-fully-fixed MWE from 52457
mbauman May 19, 2025
cfe80cd
implement the kernel for SCartesianIndices
mbauman May 19, 2025
aab7aa3
remove the mapreduce patches from juliac buildscript
mbauman May 19, 2025
cf551b1
fix error test that was accidentally tripping over depwarns
mbauman May 20, 2025
1748ff4
TEMPORARILY USE LINEARALGEBRA -- bump to merge master
mbauman May 20, 2025
febc5ee
fix doctests
mbauman May 20, 2025
84e3711
skip deprecations in doc checks
mbauman May 20, 2025
8b8fd7c
make deprecations a bit gentler
mbauman May 20, 2025
133e960
specialize constructors in transformer for find*
mbauman May 20, 2025
5fdb54c
add tests from 45822
N5N3 May 20, 2025
e013f9f
add tests for 39385
mbauman May 20, 2025
6fb8ef1
restore promote_union and _realtype
mbauman May 22, 2025
167888d
fixup! restore promote_union and _realtype
mbauman May 22, 2025
a093f9d
restore has_fast_linear_indexing
mbauman May 27, 2025
be75442
simplify inner indices computation
mbauman May 27, 2025
a6e4e73
Specialize function args to avoid allocations
mbauman May 25, 2025
744e3fd
fixup! simplify inner indices computation
mbauman May 27, 2025
647d5ed
Revert "fixup! simplify inner indices computation"
mbauman May 27, 2025
6fb57cc
Revert "simplify inner indices computation"
mbauman May 27, 2025
07e11a2
transducers for SkipMissing
mbauman May 28, 2025
144c253
fewer transducers
mbauman May 28, 2025
ddc4502
fixup! Specialize function args to avoid allocations
mbauman May 28, 2025
30c8afb
be more careful about which dimensions are considered "inner" reductions
mbauman May 28, 2025
b66af55
only use mapreduce_first without initial values
mbauman Jun 3, 2025
a729ea7
skip skipmissing transducers entirely
mbauman May 29, 2025
13ee1b5
use distinct skipmissing functions
mbauman May 29, 2025
e6948ec
improve inference for unstable HasLength iters
mbauman May 30, 2025
8ec7b41
add tuned quick outs
mbauman May 30, 2025
427bbb7
simplify bootstrap; avoid vcat early in bootstrap order
mbauman Jun 3, 2025
5f8c6a3
skip at-simd in favor of explicit re-commutating
mbauman Jun 3, 2025
cdb3cb4
Merge remote-tracking branch 'origin/master' into mb/ReduceReuse♻
mbauman Jun 26, 2025
264b8db
fixup! Specialize function args to avoid allocations
mbauman Jun 26, 2025
3d5cfec
move to 8x bins for commutative ops
mbauman Jul 3, 2025
4807a12
Merge remote-tracking branch 'origin/master' into mb/ReduceReuse♻
mbauman Jul 3, 2025
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
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ New library features
Standard library changes
------------------------

* Empty dimensional reductions (`reduce`, `mapreduce`, `sum`, `minimum`, etc., with the `dims` keyword
selecting one or more dimensions) now behave like their whole-array (`dims=:`) counterparts,
only returning values in unambiguous cases and erroring otherwise. For example,
`minimum([], dims=2)` is now an error; it would previously return an empty array.
This can be addressed by specifying an `init` keyword argument as the error message directs.

#### JuliaSyntaxHighlighting

#### LinearAlgebra
Expand Down
5 changes: 2 additions & 3 deletions base/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -236,13 +236,11 @@ Base.similar(::Broadcasted{ArrayConflict}, ::Type{Bool}, dims) =
similar(BitArray, dims)

@inline Base.axes(bc::Broadcasted) = _axes(bc, bc.axes)
@inline Base.axes(bc::Broadcasted, d::Integer) = get(axes(bc), d, OneTo(1))
_axes(::Broadcasted, axes::Tuple) = axes
@inline _axes(bc::Broadcasted, ::Nothing) = combine_axes(bc.args...)
_axes(bc::Broadcasted{<:AbstractArrayStyle{0}}, ::Nothing) = ()

@inline Base.axes(bc::Broadcasted{<:Any, <:NTuple{N}}, d::Integer) where N =
d <= N ? axes(bc)[d] : OneTo(1)

BroadcastStyle(::Type{<:Broadcasted{Style}}) where {Style} = Style()
BroadcastStyle(::Type{<:Broadcasted{S}}) where {S<:Union{Nothing,Unknown}} =
throw(ArgumentError("Broadcasted{Unknown} wrappers do not have a style assigned"))
Expand All @@ -266,6 +264,7 @@ Base.ndims(bc::Broadcasted) = ndims(typeof(bc))
Base.ndims(::Type{<:Broadcasted{<:Any,<:NTuple{N,Any}}}) where {N} = N

Base.size(bc::Broadcasted) = map(length, axes(bc))
Base.size(bc::Broadcasted, d::Integer) = length(axes(bc, d))
Base.length(bc::Broadcasted) = prod(size(bc))

function Base.iterate(bc::Broadcasted)
Expand Down
2 changes: 1 addition & 1 deletion base/cartesian.jl
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ function exprresolve_conditional(ex::Expr)
return true, exprresolve_cond_dict[callee](ex.args[2], ex.args[3])
end
end
elseif Meta.isexpr(ex, :block, 2) && ex.args[1] isa LineNumberNode
elseif ex.head === :block && length(ex.args) == 2 && ex.args[1] isa LineNumberNode
return exprresolve_conditional(ex.args[2])
end
false, false
Expand Down
27 changes: 27 additions & 0 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const __internal_changes_list = (
:codeinfonargs, # #54341
:ocnopartial,
:printcodeinfocalls,
:reducerefactor,
# Add new change names above this line
)

Expand Down Expand Up @@ -558,3 +559,29 @@ true
isbindingresolved

# END 1.12 deprecations

# BEGIN 1.13 deprecations

## These are all mapreduce internals that were not exported or public, but have deprecations to minimize disruptions
@deprecate reducedim_init(f, op, A::AbstractArray, region) (v = mapreduce_empty(f, op, eltype(A)); fill!(mapreduce_similar(A, typeof(v), reduced_indices(A,region)), v)) false
const _dep_message_reducedim_init = ", these internals have been removed. To customize the array returned by dimensional reductions, implement mapreduce_similar instead"
deprecate(Base, :reducedim_init)
deprecate(Base, Symbol("#reducedim_init"))

@deprecate (reducedim_initarray(A::Union{Base.AbstractBroadcasted, AbstractArray}, region, init, ::Type{T}) where {T}) fill!(mapreduce_similar(A,T,reduced_indices(A,region)), init) false
@deprecate reducedim_initarray(A::Union{Base.AbstractBroadcasted, AbstractArray}, region, init) fill!(mapreduce_similar(A,typeof(init),reduced_indices(A,region)), init) false
const _dep_message_reducedim_init = ", these internals have been removed. To customize the array returned by dimensional reductions, implement mapreduce_similar instead"
deprecate(Base, :reducedim_initarray)
deprecate(Base, Symbol("#reducedim_initarray"))

@deprecate _mapreduce_dim(f, op, nt, A::Union{Base.AbstractBroadcasted, AbstractArray}, dims) mapreducedim(f, op, A, nt, dims) false

@deprecate_binding mapreducedim! Base.mapreduce! false
@deprecate_binding _mapreducedim! Base.mapreduce! false
@deprecate_binding reducedim! Base.reduce! false

@deprecate mapreduce_impl(f, op, A::AbstractArrayOrBroadcasted, ifirst::Integer, ilast::Integer, blksize::Int=pairwise_blocksize(f, op)) Base.mapreduce_pairwise(f, op, A, _InitialValue(), ifirst:ilast) false
@deprecate _mapreduce(f, op, A) Base.mapreduce_pairwise(f, op, A, Base._InitialValue()) false
@deprecate _mapreduce(f, op, _, A) Base.mapreduce_pairwise(f, op, A, Base._InitialValue()) false

# END 1.13 deprecations
10 changes: 9 additions & 1 deletion base/dict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -846,7 +846,15 @@ function iterate(d::ImmutableDict{K,V}, t=d) where {K, V}
!isdefined(t, :parent) && return nothing
(Pair{K,V}(t.key, t.value), t.parent)
end
length(t::ImmutableDict) = count(Returns(true), t)
# length is defined in terms of iteration; using a higher-order function to do the iteration
# is likely to call `length` again, so this is a manual for loop:
function length(t::ImmutableDict)
Copy link
Member

Choose a reason for hiding this comment

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

won't this be horribly slow?

Copy link
Member Author

Choose a reason for hiding this comment

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

I mean, wasn't it already doing this? This shouldn't be any worse. It's definitely faster than infinite recursion :)

The trouble is that ImmutableDict has an IteratorSize of HasLength. And so the new reduction machinery happily calls length to determine the pairwise splits. So if length itself uses reductions (like count) to calculate length, we're in trouble.

On nightly:

julia> using BenchmarkTools

julia> d = Base.ImmutableDict((i => i+1 for i in 1:200)...);

julia> @benchmark length($d)
BenchmarkTools.Trial: 10000 samples with 641 evaluations per sample.
 Range (min  max):  193.512 ns  392.225 ns  ┊ GC (min  max): 0.00%  0.00%
 Time  (median):     193.772 ns               ┊ GC (median):    0.00%
 Time  (mean ± σ):   196.869 ns ±  11.655 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  █▁  ▁▃▂▄▂                                                     ▁
  ███████████▇██▇▇▆▇▆▆▆▅▅▇▆▆▅▆▅▅▆▅▅▅▅▅▅▅▄▄▅▅▅▅▂▄▃▃▄▄▄▄▃▂▂▂▃▄▂▅▄ █
  194 ns        Histogram: log(frequency) by time        241 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> @eval Base function length(t::ImmutableDict)
           r = 0
           for _ in t
               r+=1
           end
           return r
       end
length (generic function with 95 methods)

julia> @benchmark length($d)
BenchmarkTools.Trial: 10000 samples with 631 evaluations per sample.
 Range (min  max):  193.542 ns  366.482 ns  ┊ GC (min  max): 0.00%  0.00%
 Time  (median):     193.740 ns               ┊ GC (median):    0.00%
 Time  (mean ± σ):   196.829 ns ±  11.533 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  █   ▁ ▄ ▂                                                     ▁
  ███████▇███▇██▇▇▇▇▆▇▆▆▅▆▆▆▇▆▆▆▅▅▅▅▅▅▄▅▆▅▄▅▅▅▅▄▄▅▄▄▄▅▅▄▄▃▄▄▃▃▄ █
  194 ns        Histogram: log(frequency) by time        238 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

Copy link
Member

Choose a reason for hiding this comment

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

ah that's unfortunate...

r = 0
for _ in t
r+=1
end
return r
end
isempty(t::ImmutableDict) = !isdefined(t, :parent)
empty(::ImmutableDict, ::Type{K}, ::Type{V}) where {K, V} = ImmutableDict{K,V}()

Expand Down
3 changes: 2 additions & 1 deletion base/docs/Docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,8 @@ See also: [`names`](@ref), [`Docs.hasdoc`](@ref), [`Base.ispublic`](@ref).
"""
function undocumented_names(mod::Module; private::Bool=false)
filter!(names(mod; all=true)) do sym
!hasdoc(mod, sym) && !startswith(string(sym), '#') &&
!Base.isdeprecated(mod, sym) &&
!hasdoc(mod, sym) && !startswith(string(sym), '#') &&
(private || Base.ispublic(mod, sym))
end
end
Expand Down
9 changes: 2 additions & 7 deletions base/fastmath.jl
Original file line number Diff line number Diff line change
Expand Up @@ -401,19 +401,14 @@ minimum_fast(a; kw...) = Base.reduce(min_fast, a; kw...)
maximum_fast(f, a; kw...) = Base.mapreduce(f, max_fast, a; kw...)
minimum_fast(f, a; kw...) = Base.mapreduce(f, min_fast, a; kw...)

Base.reducedim_init(f, ::typeof(max_fast), A::AbstractArray, region) =
Base.reducedim_init(f, max, A::AbstractArray, region)
Base.reducedim_init(f, ::typeof(min_fast), A::AbstractArray, region) =
Base.reducedim_init(f, min, A::AbstractArray, region)

maximum!_fast(r::AbstractArray, A::AbstractArray; kw...) =
maximum!_fast(identity, r, A; kw...)
minimum!_fast(r::AbstractArray, A::AbstractArray; kw...) =
minimum!_fast(identity, r, A; kw...)

maximum!_fast(f::Function, r::AbstractArray, A::AbstractArray; init::Bool=true) =
Base.mapreducedim!(f, max_fast, Base.initarray!(r, f, max, init, A), A)
Base.mapreduce!(f, max_fast, r, A; update=!init)
minimum!_fast(f::Function, r::AbstractArray, A::AbstractArray; init::Bool=true) =
Base.mapreducedim!(f, min_fast, Base.initarray!(r, f, min, init, A), A)
Base.mapreduce!(f, min_fast, r, A; update=!init)

end
14 changes: 11 additions & 3 deletions base/intfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ function oct(x::Unsigned, pad::Int, neg::Bool)
end

# 2-digit decimal characters ("00":"99")
const _dec_d100 = UInt16[
const _dec_d100 = [
# generating expression: UInt16[(0x30 + i % 10) << 0x8 + (0x30 + i ÷ 10) for i = 0:99]
# 0 0, 0 1, 0 2, 0 3, and so on in little-endian
0x3030, 0x3130, 0x3230, 0x3330, 0x3430, 0x3530, 0x3630, 0x3730, 0x3830, 0x3930,
Expand Down Expand Up @@ -928,8 +928,16 @@ function hex(x::Unsigned, pad::Int, neg::Bool)
unsafe_takestring(a)
end

const base36digits = UInt8['0':'9';'a':'z']
const base62digits = UInt8['0':'9';'A':'Z';'a':'z']
# UInt8['0':'9';'a':'z']
const base36digits = [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a]
# UInt8['0':'9';'A':'Z';'a':'z']
const base62digits = [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a]

function _base(base::Integer, x::Integer, pad::Int, neg::Bool)
(x >= 0) | (base < 0) || throw(DomainError(x, "For negative `x`, `base` must be negative."))
Expand Down
1 change: 1 addition & 0 deletions base/iterators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ enumerate(iter) = Enumerate(iter)

length(e::Enumerate) = length(e.itr)
size(e::Enumerate) = size(e.itr)
axes(e::Enumerate) = axes(e.itr)
@propagate_inbounds function iterate(e::Enumerate, state=(1,))
i, rest = state[1], tail(state)
n = iterate(e.itr, rest...)
Expand Down
4 changes: 2 additions & 2 deletions base/mathconstants.jl
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ julia> Base.MathConstants.eulergamma
julia> dx = 10^-6;

julia> sum(-exp(-x) * log(x) for x in dx:dx:100) * dx
0.5772078382499133
0.5772078382090373
```
"""
γ, const eulergamma = γ
Expand Down Expand Up @@ -129,7 +129,7 @@ julia> Base.MathConstants.catalan
catalan = 0.9159655941772...

julia> sum(log(x)/(1+x^2) for x in 1:0.01:10^6) * 0.01
0.9159466120554123
0.9159466120556183
```
"""
catalan
Expand Down
139 changes: 55 additions & 84 deletions base/missing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -269,100 +269,71 @@ function show(io::IO, s::SkipMissing)
print(io, ')')
end

# Optimized mapreduce implementation
# The generic method is faster when !(eltype(A) >: Missing) since it does not need
# additional loops to identify the two first non-missing values of each block
mapreduce(f, op, itr::SkipMissing{<:AbstractArray}) =
_mapreduce(f, op, IndexStyle(itr.x), eltype(itr.x) >: Missing ? itr : itr.x)

function _mapreduce(f, op, ::IndexLinear, itr::SkipMissing{<:AbstractArray})
A = itr.x
ai = missing
inds = LinearIndices(A)
i = first(inds)
ilast = last(inds)
for outer i in i:ilast
@inbounds ai = A[i]
# Simple optimization: catch skipmissings that wrap iterators that don't include Missing
function mapreduce(f::F, op::G, itr::SkipMissing; init=Base._InitialValue()) where {F,G}
(IteratorEltype(itr.x) === HasEltype() && !(eltype(itr.x) >: Missing)) && return mapreduce(f, op, itr.x; init)
return mapreduce_pairwise(f, op, itr, init)
end

function mapreduce_pairwise(f::F, op::G, itr::SkipMissing{<:AbstractArray}, init) where {F,G}
v = mapreduce_skipmissing_pairwise(f, op, itr.x, init, eachindex(itr.x))
return ismissing(v) ? _mapreduce_start(f, op, itr, init) : v
end

# This is based on mapreduce_pairwise, but with special sauce that allows one or both of the pairwise splits
# to not process any elements; returning `missing` as the sentinel in such a situation
function mapreduce_skipmissing_pairwise(f::F, op::G, A, init, inds) where {F,G}
if length(inds) <= max(10, pairwise_blocksize(f, op))
return mapreduce_skipmissing_kernel(f, op, A, init, inds)
else
p1, p2 = halves(inds)
v1 = mapreduce_skipmissing_pairwise(f, op, A, init, p1)
v2 = mapreduce_skipmissing_pairwise(f, op, A, init, p2)
return ismissing(v1) ? v2 : ismissing(v2) ? v1 : op(v1, v2)
end
end

function mapreduce_skipmissing_kernel(f, op, A, init, inds::AbstractUnitRange)
i1, iN = first(inds), last(inds)
i = i1; ai = missing
for outer i in i1:iN
ai = @inbounds A[i]
!ismissing(ai) && break
end
ismissing(ai) && return mapreduce_empty(f, op, eltype(itr))
a1::eltype(itr) = ai
i == typemax(typeof(i)) && return mapreduce_first(f, op, a1)
i += 1
ai = missing
for outer i in i:ilast
ismissing(ai) && return missing
v = _mapreduce_start(f, op, A, init, ai)
i == typemax(typeof(i)) && return v
for i in i+1:iN
@inbounds ai = A[i]
!ismissing(ai) && break
if !ismissing(ai)
v = op(v, f(ai))
end
end
ismissing(ai) && return mapreduce_first(f, op, a1)
# We know A contains at least two non-missing entries: the result cannot be nothing
something(mapreduce_impl(f, op, itr, first(inds), last(inds)))
return v
end

_mapreduce(f, op, ::IndexCartesian, itr::SkipMissing) = mapfoldl(f, op, itr)

mapreduce_impl(f, op, A::SkipMissing, ifirst::Integer, ilast::Integer) =
mapreduce_impl(f, op, A, ifirst, ilast, pairwise_blocksize(f, op))

# Returns nothing when the input contains only missing values, and Some(x) otherwise
@noinline function mapreduce_impl(f, op, itr::SkipMissing{<:AbstractArray},
ifirst::Integer, ilast::Integer, blksize::Int)
A = itr.x
if ifirst > ilast
return nothing
elseif ifirst == ilast
@inbounds a1 = A[ifirst]
if ismissing(a1)
return nothing
else
return Some(mapreduce_first(f, op, a1))
end
elseif ilast - ifirst < blksize
# sequential portion
ai = missing
i = ifirst
for outer i in i:ilast
@inbounds ai = A[i]
!ismissing(ai) && break
end
ismissing(ai) && return nothing
a1 = ai::eltype(itr)
i == typemax(typeof(i)) && return Some(mapreduce_first(f, op, a1))
i += 1
ai = missing
for outer i in i:ilast
@inbounds ai = A[i]
!ismissing(ai) && break
end
ismissing(ai) && return Some(mapreduce_first(f, op, a1))
a2 = ai::eltype(itr)
i == typemax(typeof(i)) && return Some(op(f(a1), f(a2)))
i += 1
v = op(f(a1), f(a2))
@simd for i = i:ilast
@inbounds ai = A[i]
if !ismissing(ai)
v = op(v, f(ai))
end
end
return Some(v)
else
# pairwise portion
imid = ifirst + (ilast - ifirst) >> 1
v1 = mapreduce_impl(f, op, itr, ifirst, imid, blksize)
v2 = mapreduce_impl(f, op, itr, imid+1, ilast, blksize)
if v1 === nothing && v2 === nothing
return nothing
elseif v1 === nothing
return v2
elseif v2 === nothing
return v1
else
return Some(op(something(v1), something(v2)))
function mapreduce_skipmissing_kernel(f, op, A, init, inds)
it = iterate(inds)
local s
ai = missing
while it !== nothing
i, s = it
ai = @inbounds A[i]
!ismissing(ai) && break
it = iterate(inds, s)
end
ismissing(ai) && return missing
v = _mapreduce_start(f, op, A, init, ai)
for i in Iterators.rest(inds, s)
@inbounds ai = A[i]
if !ismissing(ai)
v = op(v, f(ai))
end
end
return v
end


"""
filter(f, itr::SkipMissing{<:AbstractArray})

Expand Down
Loading
Loading