Is @inbounds really necessary?


#1

Writing @inbounds in code is tedious, and ruins the nice alignment of for loops. Is it really necessary? It feels like in simple loops like

n = length(x)
for i = 1:n
  x[i] *= 2
end

this should be something the compiler could figure out by itself. Is this something that is

  • Intrinsically impossible to do even in simple cases
  • Tricky to get right because the user might overload length and friends
  • Intentionally not implemented because it might make development harder
  • A planned improvement when somebody gets the time to do it
    ?

#2

I think it is much better to be explicit with bounds-check removal than to have to constantly think “will the compiler be able to figure out removing the bound checks here”. In addition, bounds check are in the majority of cases a non-issue. It will almost only matter in cases where it inhibits other optimizations, like SIMD.

FWIW, your original example is also better written without a loop, e.g. x .*= 2 which won’t boundscheck and in addition use SIMD.


#3

I agree that broadcast is best when it can be used, but it’s not always the case.

A lot of package code seems to be littered with @inbounds, and it’s recommended in the performance tips, so that it becomes a habit for people to write @inbounds everywhere, which makes coding less pleasant. If there was a simple rule like “inbounds is unnecessary on nicely-behaved AbstractArrays when the compiler can prove that the index will be in bounds”, that would remove the burden on the user to worry about @inbounds in most cases, and result in code that is either faster (if there was no inbounds) or more pleasant (by removing the inbounds). Tricky loops would still need explicit @inbounds.


#4

Well… don’t do that.

This is almost impossible to reason about and you would have to add it anyway, just to make sure.


#5

It is possible that you misread the relevant part of the manual, which claims that

Sometimes you can enable better optimization by promising certain program properties.

ie that @inbounds & co have the potential to improve performance, but of course you have to see for each particular case, with the caveat

Be certain before doing this. If the subscripts are ever out of bounds, you may suffer crashes or silent corruption.

There is no blanket recommendation to pepper your code with these macros — on the contrary. If you see it in libraries, the best case scenario is that the library author has

  1. benchmarked the code and it does improve performance significantly,
  2. has checks in place to prevent a segfault (ie the indices are calculated inside the library, or user input is validated).

#6

I think the need to be careful is exaggerated: unit tests ignore @inbounds so if you do get a SEGFAULT, that’s more a statement about insufficient unit tests than it is a statement about bad coding.


#7

Yeah, it’s not like any other languages have had problems with non-existing bounds checks. Clearly, they should just have written more unit tests. Come on now…


#8

There’s also the command line argument --check-bounds

https://docs.julialang.org/en/stable/manual/getting-started/#Getting-Started-1

--check-bounds={yes|no} Emit bounds checks always or never (ignoring declarations)


#9

I should add that the original example

n = length(x)
for i = 1:n
  x[i] *= 2
end

is definitely not a safe place to use @inbounds, as you cannot assume arrays are 1-based. See e.g. https://docs.julialang.org/en/latest/devdocs/offset-arrays/#Background-1

This would be, though:

for i in linearindices(x)
    x[i] *= 2
end

#10

My point was that, in the most common setting of Array (as opposed to general AbstractArray) whose functions are not overloaded, the compiler could recognize that any index between 1 and length(x) is inbounds (and similar for multidimensional iteration, and several other “classical” array types). Of course more general arrays can do crazy stuff, and redefining getindex(Array, Int) leads to nastiness, which means that the compiler would have to detect when those things happen and know about Array, which is ugly and I would understand as a reason why it’s not been implemented. Still, that’s penalizing 99% of uses for the sake of generality.


#11

Still it’s a nice demonstration of the need to be careful with @inbounds


#12

https://docs.julialang.org/en/release-0.6/manual/performance-tips/ still has @inbounds for a 1:n loop.


#13

Other languages don’t turn on bounds checking in the tests.


#14

Good point - that is possibly misleading, though it is safe here as the array is created just before, and isn’t an OffsetArray.


#15

https://github.com/JuliaLang/julia/pull/23565