Replace unsafe_(get|set)index with inbounds macro#14957
Conversation
|
Hrm, there's currently a failure on master with |
|
Your benchmark job has completed - no performance regressions were detected. A full report can be found here. cc @jrevels |
|
Nothing actually got benchmarked: https://github.com/JuliaCI/BaseBenchmarkReports/blob/master/f18e728/f18e728_vs_3256421.json Edit: The tag is |
|
Yay! So nice to see this simplification. |
|
Ah, that makes sense. I actually saw some speed ups in my spot-checks.
|
|
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels |
|
Should the new behavior be tested for, if we want to go that direction? |
|
Thanks for doing this, @mbauman. I'll take a look at this tomorrow. |
There's also been an intermittent subarray failure on CI and the buildbots even without setting |
base/subarray.jl
Outdated
| reindex(a::UnitRange, b::StepRange{Int}) = range(oftype(first(a), first(a)+first(b)-1), step(b), length(b)) | ||
| reindex(a::StepRange, b::Range{Int}) = range(oftype(first(a), first(a)+(first(b)-1)*step(a)), step(a)*step(b), length(b)) | ||
| reindex(a, b::Int) = unsafe_getindex(a, b) | ||
| reindex(a, b::Int) = (@inbounds r = a[b]; r) |
There was a problem hiding this comment.
We could probably get rid of reindex altogether. The only reason we had it before is that it was impossible to turn off bounds checks on (3:6)[1:2].
|
This is so great, thanks for doing it! |
|
Oh, and I don't know of any packages that depend on out-of-bounds SubArray accesses. (I have some internal code that uses this, but that's just something I'll have to fix myself.) Besides, from my perspective out-of-bounds SubArray accesses are more of a |
|
Sure, I can add tests for bounds errors. The JULIA_TESTFULL failure in subarrays is entirely deterministic… and I don't think that the abridged tests do any randomization of the test subsets, so I don't think it'd be related. I'm a little confused about the 12x regression in A = randn(256, 256);
v = randn(256);
@benchmark A*v |
|
It will take a while until we get experience with how noisy the different benchmarks are. Perhaps BLAS is more noisy than the others? |
|
Let's try it again: |
|
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels |
base/multidimensional.jl
Outdated
| Ds = start(D) | ||
| s = 0 | ||
| for b in eachindex(I) | ||
| @inbounds for b in eachindex(I) |
There was a problem hiding this comment.
This @inbounds has wider scope than the original unsafe_getindex calls. It would be safer to individually mark the I[b] and dest[d] = src[s] lines.
There was a problem hiding this comment.
It's (currently) functionally the same, no? As I understand it, we no longer propagate the inbounds state through the next calls, so those aren't affected by the @inbounds annotation. Now, that may be something we decide to do later, so I can restrict this if you'd prefer… but I think that decision will have to consider blocks like this no matter what.
There was a problem hiding this comment.
next will be affected by the @inbounds if it is inlined, since it is only one level deep. If that's okay, then decorating the entire loop is fine.
There was a problem hiding this comment.
Sure, but we've not annotated any next method with @boundscheck/@inbounds or @_propagate_inbounds_meta yet. It may be something we want to do — especially since it'd allow us to actually follow the iteration protocol in these performance sensitive cases.
In any case, all five cases of expanded scope (there's also the @inbounds @nloops) here are iteration over AbstractArrays or their eachindex iterators or an infinite repeated iterator, and we're already assuming that AbstractArrays must be implemented correctly for this to work.
There was a problem hiding this comment.
Oooh, my understanding here is wrong. I'll restrict these @inbounds cases back to the status quo for this PR… and we can work on how this works later.
|
I wonder if we ought to make |
|
Yeah, I initially changed |
|
I see. Bummer. |
|
Benchmarks are looking much better! The BLAS guys do indeed look noisy — we've got a completely different set of successes/failures this time. The SIMD tests also look to be a little noisy. And a few of the tests just look like they got hit with unlucky GC pauses (1.3x perf regression, 1.3x more time spent in GC, but with the same number of allocations/bytes). |
|
Might be worth trying out the SIMD ones locally. |
This patch does two things:
* It all but completely removes `unsafe_getindex` and `unsafe_setindex!` from the standard library. This merges their method definitions into the safe flavors with a `(at)boundscheck checkbounds(...)` clause, and it replaces call-sites with `(at)inbounds A[I...]`. A few uses remain because the inbounds macro doesn't return its value, making the construct a little awkward in some cases.
* It changes *where* bounds are checked in SubArrays. Previously, SubArrays would defer bounds checks to their parent arrays; they wouldn't check the indices directly, but rather they'd transform the indices into the proper references into the parent array, and then index into the parent array safely. E.g., previously:
julia> A = -.5:.1:.5
S = sub(A, 7:11)
S[-4], S[0], S[4]
(-0.4,0.0,0.4)
julia> S[-6]
ERROR: BoundsError: attempt to access -0.5:0.1:0.5
at index [0]
This behavior is neither tested nor depended upon in the Base library, but I believe some packages have historically depended upon this. With this patch, the behavior is much more sane; `S[0]` now immediately throws a bounds error from the SubArray itself. I believe this is a strong requirement for making views more prominent (regardless of the syntax).
Fixes the additional memory allocations
00c2625 to
808910b
Compare
|
I can't reproduce the SIMD regressions locally, but I have old hardware (Nehalem)… and there was a 10x regression on master for the past few days, too, so it hadn't been working correctly in any case. I've rebased and things now look good again. One last try? |
|
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels |
|
Why did the |
|
Just FYI, @andreasnoack and I are currently looking into how we can change the benchmark strategy in Benchmarks.jl to cut down on the large amount of variance we've been seeing with the @nanosoldier since the perf tracking infrastructure has been enabled. There are definitely some anomalies there, some related to GC and some not.
I don't understand, it seems like they did get run? |
|
Oh, sorry. I forgot that the table only shows "significant" results. So, the fact that the |
|
|
|
Bump! Any more comments? |
| end | ||
|
|
||
| @inline unsafe_setindex!(v::BitArray, x::Bool, ind::Int) = (Base.unsafe_bitsetindex!(v.chunks, x, ind); v) | ||
| @inline unsafe_setindex!(v::BitArray, x, ind::Real) = (Base.unsafe_bitsetindex!(v.chunks, convert(Bool, x), to_index(ind)); v) |
There was a problem hiding this comment.
do these need modification in their setindex! equivalents?
There was a problem hiding this comment.
Ah, good catch. I'll add that.
There was a problem hiding this comment.
Er, no, I was wrong — it's fine to delete these without expanding BitArray's specific setindex! method since the abstract fallbacks take care of the to_indexing these days. This is ok.
|
LGTM. Thanks again! |
Replace unsafe_(get|set)index with inbounds macro
|
@mbauman and @blakejohnson, thanks for this huge step forward! |
This patch does two things:
unsafe_getindexandunsafe_setindex!from the standard library. This merges their method definitions into the safe flavors with a(at)boundscheck checkbounds(...)clause, and it replaces call-sites with(at)inbounds A[I...]. A few uses remain because the inbounds macro doesn't return its value, making the construct a little awkward in some cases.This behavior is neither tested nor depended upon in the Base library, but I believe some packages (cc @timholy?) have historically depended upon this. With this patch, the behavior is much more sane;
S[0]now immediately throws a bounds error from the SubArray itself. I believe this is a strong requirement for making views more prominent (regardless of the syntax).TODO:
runbenchmarks("array", vs = "JuliaLang/julia:master")JULIA_TESTFULL=1 make -C test subarrayIf the change in semantics is at all controversial, I can split this into two PRs — one for the spelling change and one for the change in SubArray functionality.No objections.@inboundsannotations back to status quo.