NextStride
Documentation for FedericoStra/NextStride.jl.
This is an experimental project to work around the issue https://github.com/JuliaLang/julia/issues/58403.
It redefines methods in the Base module and may not be safe for space travel.
By default, the stride(A, k::Integer) function comes with three methods, which differ in behavior when k > ndims(A), specialized for arrays A of type:
AbstractArray: this version returns the valuesum(strides(A) .* size(A));SubArray: this version returns the valuestrides(A)[end] * size(A)[end];Union{DenseArray,Base.StridedReshapedArray,Base.StridedReinterpretArray}: this version returns the valuelength(A).
Loading this package removes specializations (2)−(3) and leaves only the following:
julia> methods(stride)
# 1 method for generic function "stride" from Base:
[1] stride(A::AbstractArray{T, N}, k::Integer) where {T, N}
@ NextStride <source file>This new method is standardized in the following way:
- if
k < 1, then it returns an error; - if
1 ≤ k ≤ ndims(A), then it returnsstrides(A)[k]; - if
k > ndims(A), then it returns the length (in elements, not bytes) of the shortest contiguous memory region accessed byA.
The integer returned in the third case is called next stride and can also be computed directly via the function next_stride(A).
For example, we have
julia> using NextStride
julia> a = zeros(3,5,7);
julia> p = PermutedDimsArray(a, (2,3,1));
julia> v = view(p, :, 7:-2:1, 3:-1:1);
julia> stride(a, 4), stride(p, 4), stride(v, 4)
(105, 105, 105)whereas this code would return (105, 123, -3) without loading NextStride.
Configuring the behavior
The behavior of stride(A::AbstractArray, k::Integer) for k > ndims(A) can be configured by calling the function set_virtual_strides_behavior(B::VirtualStridesBehavior) with one of the following instances of the enumeration VirtualStridesBehavior:
ReturnError: returns an error;ReturnZero: returns0(use with caution!);ReturnNextStride: returns the "next stride" ofA;Call_next_stride: callsnext_stride(A).
Notice that with option (3) the functions stride and next_stride remain independent of one another (and can therefore be specialized separately), whereas with option (4) they become coupled (adding methods to next_stride will affect the value returned by stride).
Option (1) is always "safe" to use, because the worse it can do is throw an error if some code tries to call stride(A::AbstractArray, k::Integer) with k > ndims(A).
On the other hand, option (2) might not be safe because there is code in Julia's base libraries that accidentally relies on stride(A::AbstractArray, k::Integer) returning a non-zero value for k > ndims(A). One such example is LinearAlgebra.mul!(y, A, x) when called with a vector as second argument instead of a matrix. It treats it as a 1-column matrix and calls stride(A, 2) expecting a non-zero stride which is valid to pass to the underlying LinearAlgebra.BLAS.gemv! function (BLAS gemv functions explicitly require that lda must be positive). Setting the behavior of stride(A, 2) to return 0 instead may result in code that appears to be working, but it is definitely not correct.
julia> using LinearAlgebra
julia> mul!(zeros(3), [1., 2., 3.], [10.])
3-element Vector{Float64}:
10.0
20.0
30.0
julia> using NextStride
julia> mul!(zeros(3), [1., 2., 3.], [10.]) # no problems here
3-element Vector{Float64}:
10.0
20.0
30.0
julia> NextStride.set_virtual_strides_behavior(NextStride.ReturnZero)
julia> mul!(zeros(3), [1., 2., 3.], [10.]) # still *appears* to work correctly...
3-element Vector{Float64}:
10.0
20.0
30.0
julia> NextStride.set_virtual_strides_behavior(NextStride.ReturnError)
julia> mul!(zeros(3), [1., 2., 3.], [10.])
ERROR: array strides: dimension out of range
Stacktrace:API
Index
NextStride.VirtualStridesBehaviorNextStride.next_strideNextStride.set_virtual_strides_behaviorNextStride.virtual_strides_call_next_strideNextStride.virtual_strides_return_errorNextStride.virtual_strides_return_next_strideNextStride.virtual_strides_return_zero
Public
Exported
NextStride.next_stride — Functionnext_stride(A::AbstractArray) :: IntegerReturn the length (in elements, not bytes) of the shortest contiguous memory region accessed by A.
This is the smallest positive stride which, if assigned to the first virtual axis beyond ndims(A), would prevent overlap between successive arrays of the same type of A.
For example, if ndims(A) == 2, we could have an array B with ndims(B) == 3 such that A == B[:,:,1]; next_stride(A) is the smallest positive stride(B, 3) that guarantees that B[:,:,1] and B[:,:,2] do not overlap in memory.
Examples
julia> a = zeros(3,5,7);
julia> p = PermutedDimsArray(a, (2,3,1));
julia> v = view(p, :, 7:-2:1, 3:-1:1);
julia> next_stride(a), next_stride(p), next_stride(v)
(105, 105, 105)Non exported
NextStride.VirtualStridesBehavior — TypeVirtualStridesBehavior <: EnumInstances
ReturnErrorReturnZeroReturnNextStrideCall_next_stride
Usage
Instances of this enumeration are passed to the function set_virtual_strides_behavior to configure the behavior of stride(A::AbstractArray, k::Integer) for k > ndims(A) according to this specification:
ReturnError: returns an error;ReturnZero: returns0(use with caution!);ReturnNextStride: returns the "next stride" ofA;Call_next_stride: callsnext_stride(A).
Notice that with option (3) the functions stride and next_stride remain independent of one another (and can therefore be specialized separately), whereas with option (4) they become coupled (adding methods to next_stride will affect the value returned by stride).
NextStride.set_virtual_strides_behavior — Functionset_virtual_strides_behavior(B::VirtualStridesBehavior)Set the behavior of stride(A::AbstractArray, k::Integer) for k > ndims(A) according to the specification B, which must be an instance of the enumeration VirtualStridesBehavior.
NextStride.virtual_strides_call_next_stride — Functionvirtual_strides_call_next_stride()Set the behavior of stride(A::AbstractArray, k::Integer) to call next_stride(A) if k > ndims(A).
NextStride.virtual_strides_return_error — Functionvirtual_strides_return_error()Set the behavior of stride(A::AbstractArray, k::Integer) to return an error if k > ndims(A).
NextStride.virtual_strides_return_next_stride — Functionvirtual_strides_return_next_stride()Set the behavior of stride(A::AbstractArray, k::Integer) to return the "next stride" if k > ndims(A).
NextStride.virtual_strides_return_zero — Functionvirtual_strides_return_zero()Set the behavior of stride(A::AbstractArray, k::Integer) to return 0 if k > ndims(A).