Julia中的@inbounds传播规则 [英] @inbounds propagation rules in Julia

查看:172
本文介绍了Julia中的@inbounds传播规则的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找有关范围检查规则的说明.朱莉娅这就是说,如果我将@inbounds放在for循环的开头,

I'm looking for some clarification of the bounds checking rules in Julia. Is this meaning that if I put @inbounds at the beginning of the for loop,

@inbounds for ... end

然后仅传播一层"入站,因此如果其中有一个for循环,@inbounds会不会关闭在那里的边界检查?如果我使用@propagate_inbounds,它将进入嵌套的for循环中吗?

then only for "one layer" inbounds propagates, so if there is a for loop inside of that, @inbounds will not turn off the bounds checking in there? And if I use @propagate_inbounds, it will go inside the nested for loop?

并且说@inbounds总是胜过@boundscheck是正确的吗?唯一的例外是该函数未内联,而仅仅是前一个一层"规则的一种情况,因此@propagate_inbounds即使在非内联函数调用中也会关闭边界检查吗?

And is it correct to say @inbounds always wins over @boundscheck? The only exception if the function is not inlined, but is that just a case of the previous "one layer" rule, so @propagate_inbounds would turn off the bounds checking even in the non-inlined function call?

推荐答案

当手册中提到@inbounds通过一层"传播时,它特别是指函数调用边界.它只能影响要内联的功能这一事实是一个次要条件,这使得它特别令人困惑且难以测试,因此,让我们不必担心内联,直到稍后.

When the manual speaks about @inbounds propagating through "one layer," it's specifically referring to function call boundaries. The fact that it's only able to affect functions that get inlined is a secondary requirement that makes this especially confusing and tough to test, so let's not worry about inlining until later.

@inbounds宏可以对函数调用进行注释,以使它们能够取消边界检查.实际上,宏将对传递给它的表达式中的 all 函数调用执行此操作,包括任意数量的嵌套for循环,begin块,if语句等.而且,当然,索引和索引赋值只是降低了函数调用的糖",因此它以相同的方式影响它们.所有这些都是有道理的;作为@inbounds包装的代码的作者,您可以看到宏并确保这样做是安全的.

The @inbounds macro annotates function calls such that they're able to elide bounds checks. In fact, the macro will do this for all function calls in the expression that is passed to it, including any number of nested for loops, begin blocks, if statements, etc. And, of course, indexing and indexed assignment are simply "sugars" that lower to function calls, so it affects those the same way. All this makes sense; as the author of the code that's wrapped by @inbounds, you're able to see the macro and ensure that it's safe to do so.

但是@inbounds宏告诉Julia做一些有趣的事情.它改变了在完全不同的地方编写的代码的行为!例如,当您注释呼叫时:

But the @inbounds macro tells Julia to do something funny. It changes the behavior of code that's written in a totally different place! For example when you annotate the call:

julia> f() = @inbounds return getindex(4:5, 10);
       f()
13

该宏有效地进入标准库并禁用该@boundscheck块,从而允许它计算范围有效区域之外的值.

The macro effectively reaches into the standard library and disables that @boundscheck block, allowing it to compute values outside of the range's valid region.

这是一个遥不可及的鬼动作……如果不仔细限制它,最终可能会从不希望这样做或完全安全的库代码中删除边界检查.这就是为什么存在单层"限制的原因.我们只希望在作者明确意识到可能会发生并选择删除时删除边界检查.

This is a spooky action at a distance… and if it's not carefully constrained, it could end up removing bounds-checks from library code where it's not intended or fully safe to do so. That's why there's the "one-layer" restriction; we only want to remove bounds checks when authors are explicitly aware that it might occur and opt-in to the removal.

现在,作为库作者,在某些情况下,您可能希望选择允许@inbounds传播到您在方法中调用的所有函数.那就是使用Base.@propagate_inbounds的地方.与@inbounds注释函数调用不同,@propagate_inbounds注释方法定义以允许调用方法的入站状态传播到 all 函数调用在方法的实现中进行.很难用抽象来描述,所以让我们看一个具体的例子.

Now, as a library author, there may be cases where you want to opt-in to allow @inbounds to propagate through to all functions you call within the method. That's where Base.@propagate_inbounds is used. Unlike @inbounds, which annotates function calls, @propagate_inbounds annotates method definitions to allow for the inbounds state that the method gets called with to propagate through to all function calls you make in the method's implementation. This is a bit tough to describe in the abstract, so let's look at a concrete example.

让我们创建一个玩具自定义向量,该向量将简单地在其包装的向量中创建一个随机播放的视图:

Let's create a toy custom vector that simply creates a shuffled view into the vector it wraps:

julia> module M
           using Random
           struct ShuffledVector{A,T} <: AbstractVector{T}
               data::A
               shuffle::Vector{Int}
           end
           ShuffledVector(A::AbstractVector{T}) where {T} = ShuffledVector{typeof(A), T}(A, randperm(length(A)))
           Base.size(A::ShuffledVector) = size(A.data)
           Base.@inline function Base.getindex(A::ShuffledVector, i::Int)
               A.data[A.shuffle[i]]
           end
       end

这很简单-我们包装任何向量类型,创建一个随机排列,然后在建立索引后,我们就使用该排列索引到原始数组中.而且我们知道,根据外部构造函数,对数组子部分的所有访问都应该可以.因此,即使我们自己不检查边界,但如果我们超出边界索引,我们也可以依靠内部索引表达式抛出错误. /p>

This is pretty straight-forward — we wrap any vector type, create a random permutation, and then upon indexing we just index into the original array using the permutation. And we know that all accesses into the subparts of the array should be okay based upon the outer constructor… so even though we aren't checking bounds ourselves, we can rely upon the inner indexing expressions throwing errors if we index out of bounds.

julia> s = M.ShuffledVector(1:4)
4-element Main.M.ShuffledVector{UnitRange{Int64},Int64}:
 1
 3
 4
 2

julia> s[5]
ERROR: BoundsError: attempt to access 4-element Array{Int64,1} at index [5]
Stacktrace:
 [1] getindex at ./array.jl:728 [inlined]
 [2] getindex(::Main.M.ShuffledVector{UnitRange{Int64},Int64}, ::Int64) at ./REPL[10]:10
 [3] top-level scope at REPL[15]:1

请注意,边界错误是如何从索引到ShuffledVector而不是索引到置换向量A.perm[5]的.现在,也许我们ShuffledVector的用户希望其访问速度更快,所以他们尝试使用@inbounds:

Note how the bounds error is coming not from the indexing into the ShuffledVector, but rather from indexing into the permutation vector A.perm[5]. Now perhaps a user of our ShuffledVector wants its accesses to be faster, so they try turning off bounds-checking with @inbounds:

julia> f(A, i) = @inbounds return A[i]
f (generic function with 1 method)

julia> f(s, 5)
ERROR: BoundsError: attempt to access 4-element Array{Int64,1} at index [5]
Stacktrace:
 [1] getindex at ./array.jl:728 [inlined]
 [2] getindex at ./REPL[10]:10 [inlined]
 [3] f(::Main.M.ShuffledVector{UnitRange{Int64},Int64}, ::Int64) at ./REPL[16]:1
 [4] top-level scope at REPL[17]:1

但是它们仍然会出现界线错误!这是因为@inbounds注释仅试图从我们上面编写的方法中删除@boundscheck块.它不会传播到标准库以从A.perm数组或A.data范围中删除边界检查.即使他们试图消除界限,这还是相当大的开销!因此,我们可以改写上面的getindex方法和Base.@propagate_inbounds批注,该批注将允许该方法继承"其调用者的入站状态:

But they're still getting bounds errors! This is because @inbounds annotation only tried to remove the @boundscheck blocks from the method we wrote above. It doesn't propagate through to the standard library to remove the bounds-checking from either the A.perm array nor the A.data range. That's quite a bit of overhead, even though they tried to remove bounds! So, we can instead write the above getindex method with a Base.@propagate_inbounds annotation which will allow for this method to "inherit" its caller's in-bounds state:

julia> module M
           using Random
           struct ShuffledVector{A,T} <: AbstractVector{T}
               data::A
               shuffle::Vector{Int}
           end
           ShuffledVector(A::AbstractVector{T}) where {T} = ShuffledVector{typeof(A), T}(A, randperm(length(A)))
           Base.size(A::ShuffledVector) = size(A.data)
           Base.@propagate_inbounds function Base.getindex(A::ShuffledVector, i::Int)
               A.data[A.shuffle[i]]
           end
       end
WARNING: replacing module M.
Main.M

julia> s = M.ShuffledVector(1:4);

julia> s[5]
ERROR: BoundsError: attempt to access 4-element Array{Int64,1} at index [5]
Stacktrace:
 [1] getindex at ./array.jl:728 [inlined]
 [2] getindex(::Main.M.ShuffledVector{UnitRange{Int64},Int64}, ::Int64) at ./REPL[20]:10
 [3] top-level scope at REPL[22]:1 

julia> f(s, 5) # That @inbounds now affects the inner indexing calls, too!
0

您可以使用@code_llvm f(s, 5)确认没有分支.

You can verify that there are no branches with @code_llvm f(s, 5).

但是,实际上,在这种情况下,我认为用自己的@boundscheck块编写此getindex方法实现会更好:

But, really, in this case I think it'd be much better to write this getindex method implementation with a @boundscheck block of its own:

@inline function Base.getindex(A::ShuffledVector, i::Int)
    @boundscheck checkbounds(A, i)
    @inbounds r = A.data[A.shuffle[i]]
    return r
end

这有点冗长,但是现在它实际上会将边界错误抛出给ShuffledVector类型,而不是在错误消息中泄漏实现细节.

It's a little more verbose, but now it'll actually throw the bounds error on the ShuffledVector type instead of leaking the implementation details in the error message.

您会注意到,我没有在上面的全局范围内测试@inbounds,而是使用这些小辅助函数.这是因为边界检查删除仅在方法被内联和编译时才起作用.因此,简单地尝试在全局范围内删除边界是行不通的,因为它无法将函数调用内联到交互式REPL中:

You'll notice that I don't test @inbounds in the global scope above, and instead use these little helper functions. That's because bounds check removal only works when the method gets inlined and compiled. So simply trying to remove bounds at the global scope isn't going to work since it can't inline the function call into the interactive REPL:

julia> @inbounds getindex(4:5, 10)
ERROR: BoundsError: attempt to access 2-element UnitRange{Int64} at index [10]
Stacktrace:
 [1] throw_boundserror(::UnitRange{Int64}, ::Int64) at ./abstractarray.jl:538
 [2] getindex(::UnitRange{Int64}, ::Int64) at ./range.jl:617
 [3] top-level scope at REPL[24]:1

在全局范围内,这里没有进行编译或内联,因此Julia无法消除这些限制.同样,当类型不稳定时,Julia也无法内联方法(例如,访问非常量全局变量时),因此,它也不能删除这些边界检查:

There's no compilation or inlining occurring here at global scope, so Julia is unable to remove these bounds. Similarly, Julia isn't able to inline methods when there's a type instability (like when accessing a non-constant global), so it can't remove these bounds checks, either:

julia> r = 1:2;

julia> g() = @inbounds return r[3]
g (generic function with 1 method)

julia> g()
ERROR: BoundsError: attempt to access 2-element UnitRange{Int64} at index [3]
Stacktrace:
 [1] throw_boundserror(::UnitRange{Int64}, ::Int64) at ./abstractarray.jl:538
 [2] getindex(::UnitRange{Int64}, ::Int64) at ./range.jl:617
 [3] g() at ./REPL[26]:1
 [4] top-level scope at REPL[27]:1

通常,边界检查删除应该是您在确保其他所有功能正常运行,经过充分测试并遵循通常的性能提示后所做的最后一次优化.

In general, bounds-check removal should be the last optimization you make after ensuring everything else works, is well-tested, and follows the usual performance tips.

这篇关于Julia中的@inbounds传播规则的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆