-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Deprecate generic stride implementation (#17812), allow AbstractArrays to use BLAS/LAPACK routines (#25247) #25321
Deprecate generic stride implementation (#17812), allow AbstractArrays to use BLAS/LAPACK routines (#25247) #25321
Conversation
So you need a subtype of |
That’s exactly what this is doing: the strides matrix interface consists of overriding conversion to Down the line a trait could be added as well but this is the first step. |
base/abstractarray.jl
Outdated
strides(A::AbstractArray) = size_to_strides(1, size(A)...) | ||
function strides end | ||
|
||
# the definition of strides for Array is the cumsum of the product of sizes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cumprod of the size? But it's not quite that, it's cumprod([1;collect(size(A))[1:end-1]])
doc/src/manual/interfaces.md
Outdated
Provided the element type of the array is compatible with BLAS, a strided array can utilize BLAS and LAPACK routines | ||
for more efficient linear algebra routines. | ||
|
||
A typical example of a user defined strided array is one that wraps a standard `Array` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hyphenate "user-defined" (hyphenate compound adjectives). Don't hyphenate "subtype".
base/abstractarray.jl
Outdated
@@ -251,8 +251,8 @@ julia> strides(A) | |||
""" | |||
function strides end | |||
|
|||
# the definition of strides for Array is the cumsum of the product of sizes | |||
function _cumsumprodsizes(a::AbstractArray, i) | |||
# the definition of strides for Array is cumprod([1;collect(size(A))[1:end-1]]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well, technically cumprod
would return an array, whereas strides returns a tuple
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, that's not quite right since:
julia> A = Array{Float64,0}()
0-dimensional Array{Float64,0}:
0.0
julia> strides(A)
()
How about saying:
the defition of strides for
Array{T,N}
istuple()
ifN = 0
, otherwise it is a tuple containing 1 and a cumulative product of the first N-1 sizes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking good, thanks for tackling this.
base/deprecated.jl
Outdated
@@ -3489,6 +3489,20 @@ end | |||
@deprecate getq(F::Factorization) F.Q | |||
end | |||
|
|||
# Issues #17812 Remove default stride implementation | |||
function stride(a::AbstractArray, i::Integer) | |||
depwarn("`stride(a::AbstractArray, i)` is deprecated for general arrays. Override `stride` for custom array types that implement the strided array interface.", :stride) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this is insufficiently cautionary. Maybe
Specialize `stride` for custom array types that have the appropriate representation in memory. Warning: inappropriately implementing this method for an array type that does not use strided storage can lead to incorrect results or segfaults.
@@ -216,9 +216,40 @@ end | |||
# Check that stride of matrix/vector is 1 | |||
# Writing like this to avoid splatting penalty when called with multiple arguments, | |||
# see PR 16416 | |||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the new IPO constant propagation do we need stride1
? It seems that stride(x, dim)
could check for dim == 1
, and the branch may be elided now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This happens automatically at compile time? Cool!
I'm not sure how to test for performance regression or that it's doing the right behaviour, so I might hold off on that change (since it's independent of this PR).
base/subarray.jl
Outdated
@@ -255,11 +255,11 @@ IndexStyle(::Type{<:SubArray}) = IndexCartesian() | |||
# so they are well-defined even for non-linear memory layouts | |||
strides(V::SubArray) = substrides(V.parent, V.indices) | |||
|
|||
substrides(parent, I::Tuple) = substrides(1, parent, 1, I) | |||
substrides(parent, I::Tuple) = substrides(stride(parent, 1), parent, 1, I) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine that many implementations of stride(x, dim)
will involve a loop over d = 1:dim
; since you call stride
once per dimension, this is now O(N^2)
where N = ndims(parent)
. It might be more efficient to write this in terms of strides(parent)
which can be implemented with a single loop and thus be O(N)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Maybe something like:
substrides(parent, I) = substrides(parent, strides(parent), I)
substrides(parent, strds, ::Tuple{}) = ()
substrides(parent, strds, I::Tuple{ScalarIndex, Vararg{Any}}) = (substrides(parent, tail(strds), tail(I))...,)
substrides(parent, strds, I::Tuple{Slice, Vararg{Any}}) = (first(strds), substrides(parent, tail(strds), tail(I))...)
substrides(parent, strds, I::Tuple{AbstractRange, Vararg{Any}}) = (first(strds)*step(I[1]), substrides(parent, tail(strds), tail(I))...)
substrides(parent, strds, I::Tuple{Any, Vararg{Any}}) = throw(ArgumentError("strides is invalid for SubArrays with indices of type $(typeof(I[1]))"))
Does substrides
have to go through a deprecation process? It's not exported.
doc/src/manual/interfaces.md
Outdated
A typical example of a user-defined strided array is one that wraps a standard `Array` | ||
with additional structure. | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd add a warning here too about how dangerous it is to implement these methods if the underlying storage is not actually strided. Otherwise the following might happen:
- user calls a function. Gets a MethodError due to missing implementation of
strides
- "helpfully" implements
strides
to fix the method error
It might be useful to give some concrete examples of arrays and categorize them as to whether they have strided memory representation. Examples:
1:5 # not strided (there is no storage associated with this array, could segfault if implemented `strides`)
collect(1:5) # is strided
A = [1 5; 2 6; 3 7; 4 8] # is strided
V = view(A, 1:2, :) # is strided
V = view(A, 1:2:4, :) # is strided
V = view(A, [1,2,4], :) # is not strided
I've changed this a bit so that the default override is I've also removed the restriction to |
There is an issue with type-inferrence with the new julia> versioninfo()
Julia Version 0.7.0-DEV.3204
Commit d1086e0091* (2017-12-30 21:09 UTC)
Platform Info:
OS: macOS (x86_64-apple-darwin17.3.0)
CPU: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
WORD_SIZE: 64
BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Haswell)
LAPACK: libopenblas64_
LIBM: libopenlibm
LLVM: libLLVM-3.9.1 (ORCJIT, skylake)
Environment:
JULIA_VERSION = 0.6
julia> A = rand(5,5,5,5);
julia> V = view(A, 1:1 ,:, 1:3, :);
julia> using Test
julia> @inferred strides(A)
(1, 5, 25, 125)
julia> @inferred strides(V)
ERROR: return type NTuple{4,Int64} does not match inferred return type Tuple{Int64,Int64,Int64,Any}
Stacktrace:
[1] error(::String) at ./error.jl:33
[2] top-level scope This seems to be a limitation on type inference and I'm not sure how to resolve it. The relevant function is substrides(parent, I::Tuple) = substrides(parent, strides(parent), I)
substrides(parent, strds, ::Tuple{}) = ()
substrides(parent, strds, I::Tuple{ScalarIndex, Vararg{Any}}) = (substrides(parent, tail(strds), tail(I))...,)
substrides(parent, strds, I::Tuple{Slice, Vararg{Any}}) = (first(strds), substrides(parent, tail(strds), tail(I))...)
substrides(parent, strds, I::Tuple{AbstractRange, Vararg{Any}}) = (first(strds)*step(I[1]), substrides(parent, tail(strds), tail(I))...)
substrides(parent, strds, I::Tuple{Any, Vararg{Any}}) = throw(ArgumentError("strides is invalid for SubArrays with indices of type $(typeof(I[1]))")) |
I've worked around the issue with I think this PR is ready to be merged, though I'm happy to make any other changes. There is one test failure but it appears to be a download failure, which shouldn't be caused by this PR. It doesn't look like I have the ability to restart the test. |
An |
Good point. I'm not sure how to proceed as it appears to be a breakdown of type inference. Perhaps putting the type assertion in the arguments is better? That is, for example: substrides(parent, strds::NTuple{N,Int}, I::Tuple{Slice, Vararg{Any}}) where N = (first(strds), substrides(parent, tail(strds), tail(I))...) Edit: "assertion" isn't the right word here. |
Yeah, this does seem to be an inference bug. I get different answers depending on sequence of operations. For example: @code_warntype substrides(A, (1, 5, 25, 125), (1:1, Base.Slice(Base.OneTo(5)), 1:3, Base.Slice(Base.OneTo(5)))) # "fails"
@code_warntype substrides(A, (5, 25, 125), (Base.Slice(Base.OneTo(5)), 1:3, Base.Slice(Base.OneTo(5)))) # "succeeds"
@code_warntype substrides(A, (1, 5, 25, 125), (1:1, Base.Slice(Base.OneTo(5)), 1:3, Base.Slice(Base.OneTo(5)))) # "succeeds"
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presuming tests pass, one tiny tweak and I think this is done. Many, many thanks for seeing this through!
test/linalg/blas.jl
Outdated
Base.setindex!(A::WrappedArray, v, i::Int) = setindex!(A.A, v, i) | ||
Base.setindex!(A::WrappedArray{T, N}, v, I::Vararg{Int, N}) where {T, N} = setindex!(A.A, v, I...) | ||
Base.unsafe_convert(::Type{Ptr{T}}, A::WrappedArray{T}) where T = Base.unsafe_convert(Ptr{T}, A.A) | ||
Base.stride(A::WrappedArray, i::Int) = stride(A.A, i) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps before this line you can test that strides(A::WrappedArray)
and stride(A::WrappedArray, i)
both throw an error, and that afterward it does not? In a sense, the main contribution of this PR is removing a fallback method, so we should test that the fallback doesn't accidentally get reintroduced.
Or you could define WrappedArray2
and test for the error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume by "throws error" you mean @test_deprecated
since the fallback is still there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right indeed. 👍
I think it’s ready to be merged now (again). The only failed test was a download error, which I don’t think this PR could cause. |
This is really nice! 👍 |
I belatedly realized that I should have asked you to add the deprecation to NEWS.md. Any chance you could do that now? |
Yes I’ll try to do this soon. |
Just like the deprecation of strides (#25321), the default implementation for `elsize` is generally not true. This deprecates the fallback, restructures it to be defined on the type (and not the value), and implements it where possible for the builtin array types and wrappers.
Just like the deprecation of strides (#25321), the default implementation for `elsize` is generally not true. This deprecates the fallback, restructures it to be defined on the type (and not the value), and implements it where possible for the builtin array types and wrappers.
The deprecation method for this ain't very helpful on 0.7, given this message what should the appropriate method to update code be?
|
The alternative is to use cartesian indexing, i.e., |
If your custom array is strided in memory, then it should implement the interface and override If it's not strided in memory, you probably actually mean to use |
If you're not using strides as a computation for pointer-loading (or passing to a C-API), then ideally you'd try to use |
The xref above (added after my response) is from PyPlot, which probably means it is a pointer operation. Tread carefully in terms of following the advice above; I see there's an The best advice might be to read up on strided arrays; once you understand the concept you will know if your array is strided and how to best implement the fix. |
It may be that #29135 will solve the issue. A second thumbs up there would be good and then it can be merged. |
This pull request resolves Issues #17812 and #25247:
stride(::AbstractArray, i::Int)
andstrides(::AbstractArray)
.StridedArray
.StridedArray
in BLAS routines, so that user types that are memory-equivalent to strided arrays can access these routines.StridedArray
insparse/sparsevector.jl
. I'm not sure why these were there to begin.I still need to weaken the types for ther LAPack routines. This is straightforward but will take some time to get the tests implemented.