Skip to content

Commit 2c16e98

Browse files
committed
Merge pull request #16251 from JuliaLang/mb/varargs
RFC: Refactor indexing to use Varargs and ReshapedArrays
2 parents 817d6c2 + e5fec99 commit 2c16e98

9 files changed

+200
-234
lines changed

base/abstractarray.jl

+62-63
Original file line numberDiff line numberDiff line change
@@ -414,14 +414,11 @@ pointer{T}(x::AbstractArray{T}, i::Integer) = (@_inline_meta; unsafe_convert(Ptr
414414
# We only define one fallback method on getindex for all argument types.
415415
# That dispatches to an (inlined) internal _getindex function, where the goal is
416416
# to transform the indices such that we can call the only getindex method that
417-
# we require AbstractArray subtypes must define, either:
418-
# getindex(::T, ::Int) # if linearindexing(T) == LinearFast()
419-
# getindex(::T, ::Int, ::Int, #=...ndims(A) indices...=#) if LinearSlow()
420-
# Unfortunately, it is currently impossible to express the latter method for
421-
# arbitrary dimensionalities. We could get around that with ::CartesianIndex{N},
422-
# but that isn't as obvious and would require that the function be inlined to
423-
# avoid allocations. If the subtype hasn't defined those methods, it goes back
424-
# to the _getindex function where an error is thrown to prevent stack overflows.
417+
# we require the type A{T,N} <: AbstractArray{T,N} to define; either:
418+
# getindex(::A, ::Int) # if linearindexing(A) == LinearFast() OR
419+
# getindex{T,N}(::A{T,N}, ::Vararg{Int, N}) # if LinearSlow()
420+
# If the subtype hasn't defined the required method, it falls back to the
421+
# _getindex function again where an error is thrown to prevent stack overflows.
425422

426423
function getindex(A::AbstractArray, I...)
427424
@_propagate_inbounds_meta
@@ -432,62 +429,62 @@ function unsafe_getindex(A::AbstractArray, I...)
432429
@inbounds r = getindex(A, I...)
433430
r
434431
end
435-
## Internal defitions
436-
# 0-dimensional indexing is defined to prevent ambiguities. LinearFast is easy:
437-
_getindex(::LinearFast, A::AbstractArray) = (@_propagate_inbounds_meta; getindex(A, 1))
438-
# But LinearSlow must take into account the dimensionality of the array:
439-
_getindex{T}(::LinearSlow, A::AbstractArray{T,0}) = error("indexing not defined for ", typeof(A))
440-
_getindex(::LinearSlow, A::AbstractVector) = (@_propagate_inbounds_meta; getindex(A, 1))
441-
_getindex(l::LinearSlow, A::AbstractArray) = (@_propagate_inbounds_meta; _getindex(l, A, 1))
442-
432+
## Internal definitions
443433
_getindex(::LinearIndexing, A::AbstractArray, I...) = error("indexing $(typeof(A)) with types $(typeof(I)) is not supported")
444434

445-
## LinearFast Scalar indexing
446-
_getindex(::LinearFast, A::AbstractArray, I::Int) = error("indexing not defined for ", typeof(A))
447-
function _getindex(::LinearFast, A::AbstractArray, I::Real...)
448-
@_inline_meta
435+
## LinearFast Scalar indexing: canonical method is one Int
436+
_getindex(::LinearFast, A::AbstractArray, ::Int) = error("indexing not defined for ", typeof(A))
437+
_getindex(::LinearFast, A::AbstractArray, i::Real) = (@_propagate_inbounds_meta; getindex(A, to_index(i)))
438+
function _getindex{T,N}(::LinearFast, A::AbstractArray{T,N}, I::Vararg{Real,N})
449439
# We must check bounds for sub2ind; so we can then use @inbounds
440+
@_inline_meta
441+
J = to_indexes(I...)
442+
@boundscheck checkbounds(A, J...)
443+
@inbounds r = getindex(A, sub2ind(size(A), J...))
444+
r
445+
end
446+
function _getindex(::LinearFast, A::AbstractArray, I::Real...) # TODO: DEPRECATE FOR #14770
447+
@_inline_meta
450448
J = to_indexes(I...)
451449
@boundscheck checkbounds(A, J...)
452450
@inbounds r = getindex(A, sub2ind(size(A), J...))
453451
r
454452
end
455453

456-
# LinearSlow Scalar indexing
457-
@generated function _getindex{T,AN}(::LinearSlow, A::AbstractArray{T,AN}, I::Real...)
454+
455+
## LinearSlow Scalar indexing: Canonical method is full dimensionality of Ints
456+
_getindex{T,N}(::LinearSlow, A::AbstractArray{T,N}, ::Vararg{Int, N}) = error("indexing not defined for ", typeof(A))
457+
_getindex{T,N}(::LinearSlow, A::AbstractArray{T,N}, I::Vararg{Real, N}) = (@_propagate_inbounds_meta; getindex(A, to_indexes(I...)...))
458+
function _getindex(::LinearSlow, A::AbstractArray, i::Real)
459+
# ind2sub requires all dimensions to be > 0; may as well just check bounds
460+
@_inline_meta
461+
@boundscheck checkbounds(A, i)
462+
@inbounds r = getindex(A, ind2sub(size(A), to_index(i))...)
463+
r
464+
end
465+
@generated function _getindex{T,AN}(::LinearSlow, A::AbstractArray{T,AN}, I::Real...) # TODO: DEPRECATE FOR #14770
458466
N = length(I)
459-
if N == AN
460-
if all(x->x===Int, I)
461-
:(error("indexing not defined for ", typeof(A)))
462-
else
463-
:(@_propagate_inbounds_meta; getindex(A, to_indexes(I...)...))
464-
end
465-
elseif N > AN
467+
if N > AN
466468
# Drop trailing ones
467469
Isplat = Expr[:(I[$d]) for d = 1:AN]
468470
Osplat = Expr[:(to_index(I[$d]) == 1) for d = AN+1:N]
469471
quote
470-
# We only check the trailing ones, so just propagate @inbounds state
471472
@_propagate_inbounds_meta
472473
@boundscheck (&)($(Osplat...)) || throw_boundserror(A, I)
473474
getindex(A, $(Isplat...))
474475
end
475476
else
476477
# Expand the last index into the appropriate number of indices
477478
Isplat = Expr[:(I[$d]) for d = 1:N-1]
478-
i = 0
479-
for d=N:AN
480-
push!(Isplat, :(s[$(i+=1)]))
481-
end
482479
sz = Expr(:tuple)
483-
sz.args = Expr[:(size(A, $d)) for d=N:AN]
484-
szcheck = Expr[:(size(A, $d) > 0) for d=N:AN]
480+
sz.args = Expr[:(size(A, $d)) for d=max(N,1):AN]
481+
szcheck = Expr[:(size(A, $d) > 0) for d=max(N,1):AN]
482+
last_idx = N > 0 ? :(to_index(I[$N])) : 1
485483
quote
486484
# ind2sub requires all dimensions to be > 0:
487485
@_propagate_inbounds_meta
488486
@boundscheck (&)($(szcheck...)) || throw_boundserror(A, I)
489-
s = ind2sub($sz, to_index(I[$N]))
490-
getindex(A, $(Isplat...))
487+
getindex(A, $(Isplat...), ind2sub($sz, $last_idx)...)
491488
end
492489
end
493490
end
@@ -504,34 +501,40 @@ function unsafe_setindex!(A::AbstractArray, v, I...)
504501
r
505502
end
506503
## Internal defitions
507-
_setindex!(::LinearFast, A::AbstractArray, v) = (@_propagate_inbounds_meta; setindex!(A, v, 1))
508-
_setindex!{T}(::LinearSlow, A::AbstractArray{T,0}, v) = error("indexing not defined for ", typeof(A))
509-
_setindex!(::LinearSlow, A::AbstractVector, v) = (@_propagate_inbounds_meta; setindex!(A, v, 1))
510-
_setindex!(l::LinearSlow, A::AbstractArray, v) = (@_propagate_inbounds_meta; _setindex!(l, A, v, 1))
511-
512504
_setindex!(::LinearIndexing, A::AbstractArray, v, I...) = error("indexing $(typeof(A)) with types $(typeof(I)) is not supported")
513505

514506
## LinearFast Scalar indexing
515-
_setindex!(::LinearFast, A::AbstractArray, v, I::Int) = error("indexed assignment not defined for ", typeof(A))
516-
function _setindex!(::LinearFast, A::AbstractArray, v, I::Real...)
517-
@_inline_meta
507+
_setindex!(::LinearFast, A::AbstractArray, v, ::Int) = error("indexed assignment not defined for ", typeof(A))
508+
_setindex!(::LinearFast, A::AbstractArray, v, i::Real) = (@_propagate_inbounds_meta; setindex!(A, v, to_index(i)))
509+
function _setindex!{T,N}(::LinearFast, A::AbstractArray{T,N}, v, I::Vararg{Real,N})
518510
# We must check bounds for sub2ind; so we can then use @inbounds
511+
@_inline_meta
512+
J = to_indexes(I...)
513+
@boundscheck checkbounds(A, J...)
514+
@inbounds r = setindex!(A, v, sub2ind(size(A), J...))
515+
r
516+
end
517+
function _setindex!(::LinearFast, A::AbstractArray, v, I::Real...) # TODO: DEPRECATE FOR #14770
518+
@_inline_meta
519519
J = to_indexes(I...)
520520
@boundscheck checkbounds(A, J...)
521521
@inbounds r = setindex!(A, v, sub2ind(size(A), J...))
522522
r
523523
end
524524

525525
# LinearSlow Scalar indexing
526-
@generated function _setindex!{T,AN}(::LinearSlow, A::AbstractArray{T,AN}, v, I::Real...)
526+
_setindex!{T,N}(::LinearSlow, A::AbstractArray{T,N}, v, ::Vararg{Int, N}) = error("indexed assignment not defined for ", typeof(A))
527+
_setindex!{T,N}(::LinearSlow, A::AbstractArray{T,N}, v, I::Vararg{Real, N}) = (@_propagate_inbounds_meta; setindex!(A, v, to_indexes(I...)...))
528+
function _setindex!(::LinearSlow, A::AbstractArray, v, i::Real)
529+
# ind2sub requires all dimensions to be > 0; may as well just check bounds
530+
@_inline_meta
531+
@boundscheck checkbounds(A, i)
532+
@inbounds r = setindex!(A, v, ind2sub(size(A), to_index(i))...)
533+
r
534+
end
535+
@generated function _setindex!{T,AN}(::LinearSlow, A::AbstractArray{T,AN}, v, I::Real...) # TODO: DEPRECATE FOR #14770
527536
N = length(I)
528-
if N == AN
529-
if all(x->x===Int, I)
530-
:(error("indexing not defined for ", typeof(A)))
531-
else
532-
:(@_propagate_inbounds_meta; setindex!(A, v, to_indexes(I...)...))
533-
end
534-
elseif N > AN
537+
if N > AN
535538
# Drop trailing ones
536539
Isplat = Expr[:(I[$d]) for d = 1:AN]
537540
Osplat = Expr[:(to_index(I[$d]) == 1) for d = AN+1:N]
@@ -544,19 +547,15 @@ end
544547
else
545548
# Expand the last index into the appropriate number of indices
546549
Isplat = Expr[:(I[$d]) for d = 1:N-1]
547-
i = 0
548-
for d=N:AN
549-
push!(Isplat, :(s[$(i+=1)]))
550-
end
551550
sz = Expr(:tuple)
552-
sz.args = Expr[:(size(A, $d)) for d=N:AN]
553-
szcheck = Expr[:(size(A, $d) > 0) for d=N:AN]
551+
sz.args = Expr[:(size(A, $d)) for d=max(N,1):AN]
552+
szcheck = Expr[:(size(A, $d) > 0) for d=max(N,1):AN]
553+
last_idx = N > 0 ? :(to_index(I[$N])) : 1
554554
quote
555-
@_propagate_inbounds_meta
556555
# ind2sub requires all dimensions to be > 0:
556+
@_propagate_inbounds_meta
557557
@boundscheck (&)($(szcheck...)) || throw_boundserror(A, I)
558-
s = ind2sub($sz, to_index(I[$N]))
559-
setindex!(A, v, $(Isplat...))
558+
setindex!(A, v, $(Isplat...), ind2sub($sz, $last_idx)...)
560559
end
561560
end
562561
end

base/array.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ done(a::Array,i) = i == length(a)+1
308308

309309
# This is more complicated than it needs to be in order to get Win64 through bootstrap
310310
getindex(A::Array, i1::Real) = arrayref(A, to_index(i1))
311-
getindex(A::Array, i1::Real, i2::Real, I::Real...) = arrayref(A, to_index(i1), to_index(i2), to_indexes(I...)...)
311+
getindex(A::Array, i1::Real, i2::Real, I::Real...) = arrayref(A, to_index(i1), to_index(i2), to_indexes(I...)...) # TODO: REMOVE FOR #14770
312312

313313
# Faster contiguous indexing using copy! for UnitRange and Colon
314314
function getindex(A::Array, I::UnitRange{Int})
@@ -337,7 +337,7 @@ end
337337

338338
## Indexing: setindex! ##
339339
setindex!{T}(A::Array{T}, x, i1::Real) = arrayset(A, convert(T,x)::T, to_index(i1))
340-
setindex!{T}(A::Array{T}, x, i1::Real, i2::Real, I::Real...) = arrayset(A, convert(T,x)::T, to_index(i1), to_index(i2), to_indexes(I...)...)
340+
setindex!{T}(A::Array{T}, x, i1::Real, i2::Real, I::Real...) = arrayset(A, convert(T,x)::T, to_index(i1), to_index(i2), to_indexes(I...)...) # TODO: REMOVE FOR #14770
341341

342342
# These are redundant with the abstract fallbacks but needed for bootstrap
343343
function setindex!(A::Array, x, I::AbstractVector{Int})

base/multidimensional.jl

+29-94
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,25 @@ index_shape_dim(A, dim, ::Colon) = (trailingsize(A, dim),)
262262
# ambiguities for AbstractArray subtypes. See the note in abstractarray.jl
263263

264264
# Note that it's most efficient to call checkbounds first, and then to_index
265-
@inline function _getindex(l::LinearIndexing, A::AbstractArray, I::Union{Real, AbstractArray, Colon}...)
265+
@inline function _getindex{T,N}(l::LinearIndexing, A::AbstractArray{T,N}, I::Vararg{Union{Real, AbstractArray, Colon},N})
266266
@boundscheck checkbounds(A, I...)
267267
_unsafe_getindex(l, A, I...)
268268
end
269+
# Explicitly allow linear indexing with one non-scalar index
270+
@inline function _getindex(l::LinearIndexing, A::AbstractArray, i::Union{Real, AbstractArray, Colon})
271+
@boundscheck checkbounds(A, i)
272+
_unsafe_getindex(l, _maybe_linearize(l, A), i)
273+
end
274+
# But we can speed up LinearSlow arrays by reshaping them to vectors:
275+
_maybe_linearize(::LinearFast, A::AbstractArray) = A
276+
_maybe_linearize(::LinearSlow, A::AbstractVector) = A
277+
_maybe_linearize(::LinearSlow, A::AbstractArray) = reshape(A, length(A))
278+
279+
@inline function _getindex{N}(l::LinearIndexing, A::AbstractArray, I::Vararg{Union{Real, AbstractArray, Colon},N}) # TODO: DEPRECATE FOR #14770
280+
@boundscheck checkbounds(A, I...)
281+
_unsafe_getindex(l, reshape(A, Val{N}), I...)
282+
end
283+
269284
@generated function _unsafe_getindex(::LinearIndexing, A::AbstractArray, I::Union{Real, AbstractArray, Colon}...)
270285
N = length(I)
271286
quote
@@ -279,8 +294,6 @@ end
279294
end
280295

281296
# logical indexing optimization - don't use find (within to_index)
282-
# This is inherently a linear operation in the source, but we could potentially
283-
# use fast dividing integers to speed it up.
284297
function _unsafe_getindex(::LinearIndexing, src::AbstractArray, I::AbstractArray{Bool})
285298
shape = index_shape(src, I)
286299
dest = similar(src, shape)
@@ -305,7 +318,7 @@ end
305318
$(Expr(:meta, :inline))
306319
D = eachindex(dest)
307320
Ds = start(D)
308-
idxlens = index_lengths(src, I...) # TODO: unsplat?
321+
idxlens = index_lengths(src, I...)
309322
@nloops $N i d->(1:idxlens[d]) d->(@inbounds j_d = getindex(I[d], i_d)) begin
310323
d, Ds = next(D, Ds)
311324
@inbounds dest[d] = @ncall $N getindex src j
@@ -322,10 +335,21 @@ end
322335
# before redispatching to the _unsafe_batchsetindex!
323336
_iterable(v::AbstractArray) = v
324337
_iterable(v) = repeated(v)
325-
@inline function _setindex!(l::LinearIndexing, A::AbstractArray, x, J::Union{Real,AbstractArray,Colon}...)
338+
@inline function _setindex!{T,N}(l::LinearIndexing, A::AbstractArray{T,N}, x, J::Vararg{Union{Real,AbstractArray,Colon},N})
326339
@boundscheck checkbounds(A, J...)
327340
_unsafe_setindex!(l, A, x, J...)
328341
end
342+
@inline function _setindex!(l::LinearIndexing, A::AbstractArray, x, j::Union{Real,AbstractArray,Colon})
343+
@boundscheck checkbounds(A, j)
344+
_unsafe_setindex!(l, _maybe_linearize(l, A), x, j)
345+
A
346+
end
347+
@inline function _setindex!{N}(l::LinearIndexing, A::AbstractArray, x, J::Vararg{Union{Real, AbstractArray, Colon},N}) # TODO: DEPRECATE FOR #14770
348+
@boundscheck checkbounds(A, J...)
349+
_unsafe_setindex!(l, reshape(A, Val{N}), x, J...)
350+
A
351+
end
352+
329353
@inline function _unsafe_setindex!(::LinearIndexing, A::AbstractArray, x, J::Union{Real,AbstractArray,Colon}...)
330354
_unsafe_batchsetindex!(A, _iterable(x), to_indexes(J...)...)
331355
end
@@ -445,95 +469,6 @@ for (f, fmod, op) = ((:cummin, :_cummin!, :min), (:cummax, :_cummax!, :max))
445469
@eval ($f)(A::AbstractArray) = ($f)(A, 1)
446470
end
447471

448-
## SubArray index merging
449-
# A view created like V = A[2:3:8, 5:2:17] can later be indexed as V[2:7],
450-
# creating a new 1d view.
451-
# In such cases we have to collapse the 2d space spanned by the ranges.
452-
#
453-
# API:
454-
# merge_indexes(V, indexes::NTuple, index)
455-
# indexes encodes the view's trailing indexes into the parent array,
456-
# and index encodes the subset of these elements that we'll select.
457-
#
458-
# It returns a CartesianIndex or array of CartesianIndexes.
459-
460-
# Checking 'in' a range is fast -- so check all possibilities and keep the good ones
461-
@generated function merge_indexes{N}(V, indexes::NTuple{N}, index::Union{Colon, Range})
462-
# There may be a vector of cartesian indices in the passed indexes... which
463-
# makes the number of indices more than N. Since we pre-allocate the array
464-
# of CartesianIndexes, we need to figure out how big to make it
465-
M = 0
466-
for T in indexes.parameters
467-
T <: CartesianIndex ? (M += length(T)) : (M += 1)
468-
end
469-
index_length_expr = index <: Colon ? Symbol("Istride_", N+1) : :(length(index))
470-
quote
471-
Cartesian.@nexprs $N d->(I_d = indexes[d])
472-
dimlengths = Cartesian.@ncall $N index_lengths_dim V.parent length(V.indexes)-N+1 I
473-
Istride_1 = 1 # strides of the indexes to merge
474-
Cartesian.@nexprs $N d->(Istride_{d+1} = Istride_d*dimlengths[d])
475-
idx_len = $(index_length_expr)
476-
if idx_len < 0.1*$(Symbol("Istride_", N+1)) # this has not been carefully tuned
477-
return merge_indexes_div(V, indexes, index, dimlengths)
478-
end
479-
Cartesian.@nexprs $N d->(counter_d = 1) # counter_0 is the linear index
480-
k = 0
481-
merged = Array(CartesianIndex{$M}, idx_len)
482-
Cartesian.@nloops $N i d->(1:dimlengths[d]) d->(counter_{d-1} = counter_d + (i_d-1)*Istride_d; @inbounds idx_d = I_d[i_d]) begin
483-
if counter_0 in index # this branch is elided for ::Colon
484-
@inbounds merged[k+=1] = Cartesian.@ncall $N CartesianIndex{$M} idx
485-
end
486-
end
487-
merged
488-
end
489-
end
490-
491-
# mapping getindex across the parent and subindices rapidly gets too big to
492-
# automatically inline, but it is crucial that it does so to avoid allocations
493-
# Unlike SubArray's reindex, merge_indexes doesn't drop any indices.
494-
@inline inlinemap(f, t::Tuple, s::Tuple) = (f(t[1], s[1]), inlinemap(f, tail(t), tail(s))...)
495-
inlinemap(f, t::Tuple{}, s::Tuple{}) = ()
496-
inlinemap(f, t::Tuple{}, s::Tuple) = ()
497-
inlinemap(f, t::Tuple, s::Tuple{}) = ()
498-
499-
# Otherwise, we fall back to the slow div/rem method, using ind2sub.
500-
@inline merge_indexes{N}(V, indexes::NTuple{N}, index) =
501-
merge_indexes_div(V, indexes, index, index_lengths_dim(V.parent, length(V.indexes)-N+1, indexes...))
502-
503-
@inline merge_indexes_div{N}(V, indexes::NTuple{N}, index::Real, dimlengths) =
504-
CartesianIndex(inlinemap(getindex, indexes, ind2sub(dimlengths, index)))
505-
merge_indexes_div{N}(V, indexes::NTuple{N}, index::AbstractArray, dimlengths) =
506-
reshape([CartesianIndex(inlinemap(getindex, indexes, ind2sub(dimlengths, i))) for i in index], size(index))
507-
merge_indexes_div{N}(V, indexes::NTuple{N}, index::Colon, dimlengths) =
508-
[CartesianIndex(inlinemap(getindex, indexes, ind2sub(dimlengths, i))) for i in 1:prod(dimlengths)]
509-
510-
# Merging indices is particularly difficult in the case where we partially linearly
511-
# index through a multidimensional array. It's easiest if we can simply reduce the
512-
# partial indices to a single linear index into the parent index array.
513-
function merge_indexes{N}(V, indexes::NTuple{N}, index::Tuple{Colon, Vararg{Colon}})
514-
shape = index_shape(indexes[1], index...)
515-
reshape(merge_indexes(V, indexes, :), (shape[1:end-1]..., shape[end]*prod(index_lengths_dim(V.parent, length(V.indexes)-length(indexes)+2, tail(indexes)...))))
516-
end
517-
@inline merge_indexes{N}(V, indexes::NTuple{N}, index::Tuple{Real, Vararg{Real}}) = merge_indexes(V, indexes, sub2ind(size(indexes[1]), index...))
518-
# In general, it's a little trickier, but we can use the product iterator
519-
# if we replace colons with ranges. This can be optimized further.
520-
function merge_indexes{N}(V, indexes::NTuple{N}, index::Tuple)
521-
I = replace_colons(V, indexes, index)
522-
shp = index_shape(indexes[1], I...) # index_shape does no bounds checking
523-
dimlengths = index_lengths_dim(V.parent, length(V.indexes)-N+1, indexes...)
524-
sz = size(indexes[1])
525-
reshape([CartesianIndex(inlinemap(getindex, indexes, ind2sub(dimlengths, sub2ind(sz, i...)))) for i in product(I...)], shp)
526-
end
527-
@inline replace_colons(V, indexes, I) = replace_colons_dim(V, indexes, 1, I)
528-
@inline replace_colons_dim(V, indexes, dim, I::Tuple{}) = ()
529-
@inline replace_colons_dim(V, indexes, dim, I::Tuple{Colon}) =
530-
(1:trailingsize(indexes[1], dim)*prod(index_lengths_dim(V.parent, length(V.indexes)-length(indexes)+2, tail(indexes)...)),)
531-
@inline replace_colons_dim(V, indexes, dim, I::Tuple{Colon, Vararg{Any}}) =
532-
(1:size(indexes[1], dim), replace_colons_dim(V, indexes, dim+1, tail(I))...)
533-
@inline replace_colons_dim(V, indexes, dim, I::Tuple{Any, Vararg{Any}}) =
534-
(I[1], replace_colons_dim(V, indexes, dim+1, tail(I))...)
535-
536-
537472
cumsum(A::AbstractArray, axis::Integer=1) = cumsum!(similar(A, Base._cumsum_type(A)), A, axis)
538473
cumsum!(B, A::AbstractArray) = cumsum!(B, A, 1)
539474
cumprod(A::AbstractArray, axis::Integer=1) = cumprod!(similar(A), A, axis)

0 commit comments

Comments
 (0)