在Julia中@inbounds传播规则

时间:2016-08-11 16:05:10

标签: julia bounds-checker

我正在寻找朱莉娅bounds checking rules的一些澄清。这意味着如果我将@inbounds放在for循环的开头,

@inbounds for ... end

然后只有“一层”的内部传播,所以如果内部有一个for循环,@inbounds将不会关闭那里的边界检查?如果我使用@propagate_inbounds,它将进入嵌套的for循环?

@inbounds总是胜过@boundscheck是否正确?唯一的例外是如果函数没有内联,但只是前一个“一层”规则的情况,那么@propagate_inbounds即使在非内联函数调用中也会关闭边界检查?

1 个答案:

答案 0 :(得分:7)

当手册讲述@inbounds传播“一层”时,它特指的是函数调用边界。事实上它只能影响内联函数是次要的要求,这使得这一点特别容易混淆和难以测试,所以让我们不要担心直到后来内联。

@inbounds宏注释函数调用,以便它们能够忽略边界检查。事实上,宏将为传递给它的表达式中的所有函数调用执行此操作,包括任意数量的嵌套for循环,begin块,{{1当然,索引和索引赋值只是“糖”,它们降低到函数调用,因此它以相同的方式影响它们。这一切都有道理;作为由if包装的代码的作者,您可以看到宏并确保这样做是安全的。

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

@inbounds

宏有效地进入the standard library并禁用julia> f() = @inbounds return getindex(4:5, 10); f() 13 块,允许它计算范围有效区域之外的值。

这是远距离的一个怪异行为......如果它没有受到小心约束,它最终可能会从图书馆代码中删除边界检查,这样做是不可能或完全安全的。这就是“一层”限制的原因;我们只想在作者明确意识到它可能发生并选择加入删除时删除边界检查。

现在,作为图书馆作者,可能存在您希望选择允许@boundscheck传播到您在方法中调用的所有函数的情况。这就是使用@inbounds的地方。与注释函数调用的Base.@propagate_inbounds不同,@inbounds注释方法定义,以允许调用方法的inbounds状态传播到 all 您在方法的实现中调用的函数。这在摘要中有点难以描述,所以让我们看一个具体的例子。

示例

让我们创建一个玩具自定义矢量,只需在它包装的矢量中创建一个混洗视图:

@propagate_inbounds

这非常简单 - 我们包装任何矢量类型,创建随机排列,然后在索引时我们只使用置换索引到原始数组。我们知道所有对数组子部分的访问都应该基于外部构造函数...所以即使我们不自己检查边界,如果我们索引越界,我们可以依赖内部索引表达式抛出错误。 / p>

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

注意从索引到ShuffledVector的边界错误是如何而不是,而是从索引到置换向量julia> s = M.ShuffledVector(1:4) 4-element M.ShuffledVector{UnitRange{Int64},Int64}: 1 2 4 3 julia> s[5] ERROR: BoundsError: attempt to access 4-element Array{Int64,1} at index [5] in getindex(::M.ShuffledVector{UnitRange{Int64},Int64}, ::Int64) at ./REPL[5]:9 。现在,我们的ShuffledVector用户可能希望其访问速度更快,因此他们尝试使用A.perm[5]关闭边界检查:

@inbounds

但他们仍然会遇到界限错误!这是因为julia> f(A, i) = @inbounds return A[i] f(s, 5) ERROR: BoundsError: attempt to access 4-element Array{Int64,1} at index [5] in getindex at ./REPL[5]:9 [inlined] in f(::M.ShuffledVector{UnitRange{Int64},Int64}, ::Int64) at ./REPL[15]:1 注释只是试图从我们上面编写的方法中删除@inbounds块。它不会传播到标准库,以从@boundscheck数组和A.perm范围中删除边界检查。这是相当多的开销,即使他们试图删除边界!因此,我们可以使用A.data注释编写上述getindex方法,这将允许此方法“继承”其调用方的入站状态:

Base.@propagate_inbounds

您可以验证julia> module M immutable ShuffledVector{A,T} <: AbstractVector{T} data::A shuffle::Vector{Int} end ShuffledVector{T}(A::AbstractVector{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 M julia> s = M.ShuffledVector(1:4) s[5] # It still throws errors for out-of-bounds accesses ERROR: BoundsError: attempt to access 4-element Array{Int64,1} at index [5] in getindex(::M.ShuffledVector{UnitRange{Int64},Int64}, ::Int64) at ./REPL[1]:9 julia> f(s, 5) # That @inbounds now affects the inner indexing calls, too! 0 没有分支。

但是,实际上,在这种情况下,我认为用自己的@code_llvm f(s, 5)块编写这个getindex方法实现要好得多:

@boundscheck

它有点冗长,但现在它实际上会在@inline function Base.getindex(A::ShuffledVector, i::Int) @boundscheck checkbounds(A, i) @inbounds r = A.data[A.shuffle[i]] return r end 类型上抛出边界错误,而不是在错误消息中泄露实现细节。

内联的影响

你会注意到我没有在上面的全局范围内测试ShuffledVector,而是使用这些小辅助函数。这是因为边界检查删除仅在方法内联和编译时有效。因此,只是尝试删除全局范围内的边界是行不通的,因为它无法将函数调用内联到交互式REPL中:

@inbounds

在全球范围内没有编译或内联,因此Julia无法删除这些边界。类似地,当存在类型不稳定时(例如访问非常量全局时),Julia无法内联方法,因此它无法删除这些边界检查:

julia> @inbounds getindex(4:5, 10)
ERROR: BoundsError: attempt to access 2-element UnitRange{Int64} at index [10]
 in throw_boundserror(::UnitRange{Int64}, ::Int64) at ./abstractarray.jl:272
 in getindex(::UnitRange{Int64}, ::Int64) at ./range.jl:450