@Julia中的边界内传播规则

@Julia中的边界内传播规则,julia,bounds-checker,Julia,Bounds Checker,我想澄清一下朱莉娅的性格。这是否意味着如果我在for循环的开始处放置@inbounds @inbounds for ... end 然后,只有“一层”的边界内传播,因此,如果该边界内有for循环,@inbounds不会在其中关闭边界检查吗?如果我使用@propagate_inbounds,它将进入嵌套的for循环中 说@inbounds总是战胜@boundscheck是正确的吗?如果函数不是内联的,唯一的例外情况是,这只是以前的“一层”规则的一种情况,因此@propagate\u inboun

我想澄清一下朱莉娅的性格。这是否意味着如果我在for循环的开始处放置
@inbounds

@inbounds for ... end
然后,只有“一层”的边界内传播,因此,如果该边界内有for循环,
@inbounds
不会在其中关闭边界检查吗?如果我使用
@propagate_inbounds
,它将进入嵌套的for循环中


@inbounds
总是战胜
@boundscheck
是正确的吗?如果函数不是内联的,唯一的例外情况是,这只是以前的“一层”规则的一种情况,因此
@propagate\u inbounds
即使在非内联函数调用中也会关闭边界检查?

当手册谈到通过“一层”传播时它具体指的是函数调用边界。事实上,它只能影响内联的函数,这是一个次要的要求,这使得它特别容易混淆和难以测试,所以我们以后再担心内联

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

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

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

这在一定程度上是一个令人毛骨悚然的行为……如果不小心加以约束,它可能最终会从库代码中删除边界检查,而这样做并不打算或完全安全。这就是为什么有“一层”的限制;我们只想在作者明确意识到可能发生边界检查并选择删除时删除边界检查

现在,作为库作者,您可能希望选择允许
@inbounds
传播到方法中调用的所有函数。这就是使用
Base.@propagate_inbounds
的地方。与注释函数调用的
@inbounds
不同,
@propagate\u inbounds
注释方法定义,以允许调用方法时使用的inbounds状态传播到方法实现中进行的所有函数调用。这在抽象中有点难以描述,所以让我们看一个具体的例子

一个例子 让我们创建一个玩具自定义向量,该向量只需在其包装的向量中创建一个无序视图:

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
请注意,边界错误不是来自对ShuffledVector的索引,而是来自对排列向量的索引
A.perm[5]
。现在,我们ShuffledVector的用户可能希望它的访问速度更快,所以他们尝试使用
@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
方法。@propagate_inbounds
注释,该注释将允许此方法“继承”调用方的in-bounds状态:

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
它有点冗长,但现在它实际上会在
ShuffledVector
类型上抛出边界错误,而不是在错误消息中泄漏实现细节

内联效应 您会注意到,我没有在上面的全局范围内测试
@inbounds
,而是使用这些小助手函数。这是因为边界检查删除仅在方法内联并编译时有效。因此,简单地删除全局范围的边界是行不通的,因为它无法将函数调用内联到交互式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无法内联方法,因此也无法删除这些边界检查:

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

一般来说,边界检查删除应该是您在确保所有其他功能正常运行、经过良好测试并遵循通常的性能提示后进行的最后一次优化。

“传播”可能更正确?感谢您注意到错误。您需要添加版本号。因为我很确定这已经改变了好几次。(至少在夜间)最新的v0.5-rc或v0.6>它不会传播到标准库以从A.perm数组或A.data范围中删除边界检查。。。。我认为边界检查确实会通过本机Julia数组传播。因此,当A.data是UnitRange时,它将通过A.perm而不是A.data传播。尝试使用f(v,5)和v=M.ShuffledVector(collect(1:4))是的,这曾经是一个bug,现在已经修复了
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> 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