-
-
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
Revise checkbounds again #17355
Revise checkbounds again #17355
Conversation
Might as well let @nanosoldier |
|
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels |
9fd70f5
to
f13bd54
Compare
@nanosoldier |
Your benchmark job has completed - no performance regressions were detected. A full report can be found here. cc @jrevels |
Tests pass and benchmarks look great, so it's just a question of whether we want this. |
checkindex(Bool, inds1, I[1]) & checkbounds_indices(indsrest, tail(I)) | ||
end | ||
|
||
@inline split{N}(inds, V::Type{Val{N}}) = _split((), inds, V) |
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.
should this be inside the IteratorsMD
module?
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.
If we don't think this is broadly useful, moving it would indeed be a good idea.
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.
there is already a split
function for strings, defining these methods in Base
would be extending that same function but I think this is sufficiently different that it should be separate
Could you add a little documentation on the intended purpose of all these functions? As I understand it:
|
Yes, you have the idea. EDIT: In particular, you seem to understand the ambiguity-reducing cascade: extend
As far as not documented, I did provide this comment. Is this insufficient? Would converting that into a docstring address your concern? It definitely isn't exported, and I'm not yet convinced it should be. For almost all arrays it seems like an internal detail---as long as the array provides useful
I think I did it for consistency with |
f13bd54
to
9125a9c
Compare
@nanosoldier |
OK, I think I've figured out what was happening: it was a lack of call-site specialization for the splatted However, detailed investigation revealed something relatively extraordinary that I had not yet noticed, and while it's a little long (apologies) I think it's kind of fun: include(Pkg.dir("BaseBenchmarks","src","array","sumindex.jl"))
a = rand(1000,1000)
b = ArrayLS(a)
s = sub(a, 1:999, 1:1000) # LinearSlow
perf_sumlinear(a)
perf_sumlinear(b)
perf_sumlinear(s)
@time 1
@time perf_sumlinear(a)
@time perf_sumlinear(b)
@time perf_sumlinear(s) julia-0.4:
master:
If you look carefully, there's a >10x performance improvement in linear indexing for immutable ArrayLS1{T,N} <: MyArray{T,N} # LinearSlow
data::Array{T,N}
end
@inline Base.size{T}(A::ArrayLS1{T,2}) = (sz = size(A.data); (sz[1]-1,sz[2]-1)) # this line forces LLVM to compute the div
@inline Base.getindex(A::ArrayLS1, i::Int, i2::Int) = getindex(A.data, i, i2)
@inline Base.unsafe_getindex(A::ArrayLS1, i::Int, j::Int) = Base.unsafe_getindex(A.data, i, j)
c = ArrayLS1(a)
perf_sumlinear(c)
@time perf_sumlinear(c) which yields julia-0.4:
and master:
So all that fighting I did against the 10x regression seems a little bit pointless, since it now looks like a (new) artifact of the benchmark. If we take that viewpoint seriously, should I drop eff2593, since I'd rather not have it? Or is it worth having for those corner cases where something is declared Presumably it would make sense to revise the benchmarks to make them like ArrayLS1? Of course, we're still doing considerably better than julia-0.4, including a 4x improvement for |
Really, what I'm after is a higher level strategy. The call graphs are really deep… and the only reason is to simplify dispatch and allow for easier extensions by custom arrays/indices. Specifically calling out what you expect to be specialized at each step would be very helpful, as would be my bullet list above. I actually missed a step above. I don't think I don't feel strongly about |
This is one of those cases where I confess I'm struggling with "author blindness" to what are doubtlessly holes in the documentation. To see if I understand, is your description in #17355 (comment) basically a summary of what you'd like in terms of documentation? (I'm happy to copy/paste/edit that into the source code.) Doesn't that comment I linked to above (conveniently here again) basically say the same thing? Would it be clearer if I described it like
? Or are you looking for "strategic vision"? This can be a little hard to articulate (and I should probably try harder), but here are a couple:
julia> A = rand(3,4)
3×4 Array{Float64,2}:
0.546929 0.092763 0.158707 0.405476
0.0441324 0.536253 0.596574 0.79432
0.976893 0.760052 0.56792 0.396879
julia> I = [CartesianIndex((1,2)), CartesianIndex((3,4))]
2-element Array{CartesianIndex{2},1}:
CartesianIndex{2}((1,2))
CartesianIndex{2}((3,4))
julia> A[I]
2-element Array{Float64,1}:
0.092763
0.396879 whereas on julia-0.4 this yields julia> A[I]
ERROR: ArgumentError: unable to check bounds for indices of type CartesianIndex{2}
in getindex at abstractarray.jl:488
Yes, this is a big bummer. If you see a way to avoid that design pattern, I'm all ears. I'll point out that I don't think it's any worse than |
9125a9c
to
47f3710
Compare
I don't have objections to this PR — I definitely like these names much better than what I had done. And that comment is great. My documentation request was more for the global call-chain like I described than the local "this method does x". My comment above would be hopefully be sufficient for a naive reader… somewhere… but I'm not sure where. I really just think I got confused with the crazy history of changes from |
Thanks for taking the time to look it over! I agree it's been a somewhat chaotic process. I will copy/paste some version of your analysis at the top of the bounds-checking code in abstractarray.jl. |
47f3710
to
eb77e26
Compare
Now documented out the wazoo. I moved some code around and renamed some variables for consistency from one function to the next, so there's more apparent than real churn. @nanosoldier |
With nanosoldier feeling better, let's try again: @nanosoldier |
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels |
Seems like JuliaCI/BaseBenchmarks.jl#16 wasn't used on the nanosoldier run; I've checked locally and can't replicate any of the regressions. I think this is ready to go. |
@@ -326,12 +326,17 @@ step(r::AbstractUnitRange) = 1 | |||
step(r::FloatRange) = r.step/r.divisor | |||
step{T}(r::LinSpace{T}) = ifelse(r.len <= 0, convert(T,NaN), (r.stop-r.start)/r.divisor) | |||
|
|||
function length(r::StepRange) | |||
unsafe_length(r::Range) = length(r) # generic fallback |
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.
what is potentially unsafe about unsafe_length?
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.
See the checked_sub
variants a little farther down in the file. The checked versions are necessary if you want to avoid being fooled when you do this:
julia> r = typemin(Int):typemax(Int)
-9223372036854775808:9223372036854775807
julia> length(r)
ERROR: OverflowError()
in length(::UnitRange{Int64}) at ./range.jl:354
in eval(::Module, ::Any) at ./boot.jl:234
in macro expansion at ./REPL.jl:92 [inlined]
in (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at ./event.jl:46
The downside of checked arithmetic is it's god awful slow if you use it for something performance-critical like bounds checking.
Fortunately, since for arrays all sizes have to be an Int
>= 0, it's not a cost you need to pay. So it's worth having a fast path for carefully-chosen applications.
eb77e26
to
76c4f9d
Compare
Thanks for the review as always, @tkelman. |
76c4f9d
to
e797ae4
Compare
note that there are several (new) sphinx warnings from the formatting in your rst |
This also reorganizes code to make the order follow the hierarchy; makes more sense for the "big picture" documentation to be near the top node.
e797ae4
to
81f8d4f
Compare
Hmm, I already encountered an application where it was really sweet to be able to call For that reason, I decided I had better symmetrize everything: now all functions have both |
@@ -147,11 +147,13 @@ function showerror(io::IO, ex::BoundsError) | |||
print(io, ": attempt to access ") | |||
if isa(ex.a, AbstractArray) | |||
print(io, summary(ex.a)) | |||
elseif isa(ex.a, Tuple) | |||
print(io, "array with indices ", ex.a) |
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.
is a BoundsError
with a Tuple
field always going to mean the indices?
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.
Amazing we didn't have a test for that. Added.
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.
ah i see with your new version, you won't usually index with i
a tuple
bd812bd
to
d4d88f3
Compare
@@ -147,11 +147,13 @@ function showerror(io::IO, ex::BoundsError) | |||
print(io, ": attempt to access ") | |||
if isa(ex.a, AbstractArray) | |||
print(io, summary(ex.a)) | |||
elseif isa(ex.a, Tuple) && isdefined(ex, :i) && isa(ex.i, 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.
ex.a
is also a Tuple when you index a tuple out of bounds.
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
won't be though, see collapsed discussion right above
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.
Ah, I see. Technically unambiguous, but kind of obscure.
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 don't like the ambiguity either. Some options I see:
- create a new exception type (yuck)
- add another field to
BoundsError
- create an internal
ArrayIndices
type that wraps the tuple-of-indices and stick it in thea
slot
Of these, I like the third the best.
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.
Option 4: don't support the throwing version of checkbounds_indices
. Really I was most concerned about setting up an expectation that checkbounds_indices(IA, I)
would throw, and surprise the user when it doesn't. If that yields a MethodError
but checkbounds_indices(Bool, IA, I)
works, any user will expect that to return a Bool
and then could handle the throwing him/herself. Maybe this is the best choice?
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.
Yes, option 4 sounds pretty good.
I'm basically ok with this (especially since @mbauman is :) ). Overloading the meaning of the fields in BoundsError is kind of unfortunate --- won't that lead to unpredictable messages for bounds errors, where sometimes the array type is given and sometimes it isn't? |
d4d88f3
to
577d05f
Compare
577d05f
to
f16a3fe
Compare
Test failure is #17409. |
length(A) == length(I) | ||
end | ||
function checkbounds_logical(::Type{Bool}, A::AbstractVector, I::AbstractVector{Bool}) | ||
indices(A) == indices(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.
Are this and checkbounds_logical(::Type{Bool}, A::AbstractVector, I::AbstractArray{Bool})
swapped? Should the vector[array]
case be checking indices
, and vector[vector]
be checking length
?
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.
As I'm thinking about it, no, but I could be missing something so I welcome discussion. The idea is that A[i]
is retained if I[i] == true
. Consequently, the indices of A
and I
have to match. The exception to the requirement for exact matching is when we're using linear indexing. But when both of them are vectors, linear indexing is trumped by Cartesian indexing.
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.
Okay, makes sense. I'm not entirely sure what you mean by "linear indexing is trumped by Cartesian indexing" though.
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.
Lines 1242 to 1245 in 3627ed2
# In 1d, there's a question of whether we're doing cartesian indexing | |
# or linear indexing. Support only the former. | |
sub2ind(inds::Indices{1}, I::Integer...) = throw(ArgumentError("Linear indexing is not defined for one-dimensional arrays")) | |
sub2ind(inds::Tuple{OneTo}, I::Integer...) = (@_inline_meta; _sub2ind(inds, 1, 1, I...)) # only OneTo is safe |
checkbounds
was revised in #17137 and #17340, but the latter left a few dangling uncalled_chkbnds
inmultidimensional.jl
. Here's another proposed revision that will hopefully be performant(haven't had time to test yet)and also be a cleaner & more general API. As a bonus, this version is the first (to my knowledge) to ever support arrays ofCartesianIndex
es, which will be important if we ever decide to makefind*
return such arrays. Finally, lots of tests added.CC @JeffBezanson, @mbauman, @andreasnoack (ref JuliaParallel/DistributedArrays.jl#74).