从抽象类型访问字段时 julia 类型不稳定
julia type unstable when accessing field from abstract type
我有一个关于访问字段时类型不稳定性的问题
abstract type
(在 julia v0.6 中)。
假设我有一个类型层次结构,所有类型层次结构都共享同一个实例变量。
我知道它既类型不稳定又不能保证正确访问
该字段正确,因为有人总是可以定义一个缺少的新子类型
预期变量。但是,即使将成员访问包装在
功能,访问仍然类型不稳定,我不知道为什么。
假设我们有一个简单的类型层次结构:
julia> begin
abstract type AT end
mutable struct T1 <: AT
x::Int
end
mutable struct T2 <: AT
x::Int
end
end
我们不是直接访问 a.x
,而是将其包装在函数屏障中:
julia> getX(a::AT)::Int = a.x
>> getX (generic function with 1 method)
julia> @code_warntype getX(T1(1))
Variables:
#self# <optimized out>
a::T1
Body:
begin
return (Core.getfield)(a::T1, :x)::Int64
end::Int64
请注意,通过此方法进行的访问是类型稳定的,因为它可以推断
a
的类型为 T1
.
但是,当我在编译器无法知道类型的上下文中使用 getX
时
变量的提前,它仍然是类型不稳定的:
julia> foo() = getX(rand([T1(1),T2(2)]))
>> foo (generic function with 1 method)
julia> @code_warntype foo()
Variables:
#self# <optimized out>
T <optimized out>
Body:
begin
SSAValue(0) = (Core.tuple)($(Expr(:new, :(Main.T1), 1)), $(Expr(:new, :(Main.T2), 2)))::Tuple{T1,T2}
SSAValue(2) = $(Expr(:invoke, MethodInstance for rand(::Array{AT,1}), :(Main.rand), :($(Expr(:invoke, MethodInstance for copy!(::Array{AT,1}, ::Tuple{T1,T2}), :(Base.copy!), :($(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{AT,1}, svec(Any, Int64), Array{AT,1}, 0, 2, 0))), SSAValue(0))))))
return (Core.typeassert)((Base.convert)(Main.Int, (Core.getfield)(SSAValue(2), :x)::Any)::Any, Main.Int)::Int64
end::Int64
请注意,它内联了 getX
的主体,并将其替换为 essentially
tmp.x::Int64
。这让我感到惊讶,因为我期待 getX
派遣到
我们在上面看到的同一定义的两个实例之一,其中没有断言
是必需的,因为类型已知。
我认为如果 getX
实际上 只是定义的话,这确实有意义
对于抽象基类型 AT
—— 没有方法可以分派到 in
我想象的方式。所以我尝试重新定义 getX
这样它就会生成
每个子类型的具体方法如下:
julia> getX(a::T where T<:AT)::Int = a.x
>> getX (generic function with 1 method)
但这实际上是一个相同的定义,没有任何改变:
julia> methods(getX)
>> # 1 method for generic function "getX":
getX(a::AT) in Main at none:1
知道如何让它工作吗?
啊,所以我需要自己手动定义getX
的不同版本。
我混淆了 julia
的类型分配机制和 C++
的模板
实例化。我想我 错误地想象 julia
定义了一个新版本
getX
对于每个 T
它被调用,以与 C++ 模板相同的方式
机制。
在这种情况下,我说的几乎是正确的
I was expecting getX
to dispatch to one of the two instantiations [...]
where no assert is necessary since the type is known.
但是,在这种情况下,实际上并没有两种不同的方法
派遣到——只有一个。如果我实际上 define 这两个不同
methods,调度机制可以满足类型稳定性:
julia> begin
abstract type AT end
mutable struct T1 <: AT
x::Int
end
mutable struct T2 <: AT
x::Int
end
end
julia> getX(a::T1) = a.x
>> getX (generic function with 1 method)
julia> getX(a::T2) = a.x
>> getX (generic function with 2 methods)
julia> foo() = getX(rand([T1(1),T2(2)]))
>> foo (generic function with 1 method)
julia> @code_warntype foo()
Variables:
#self# <optimized out>
T <optimized out>
Body:
begin
SSAValue(0) = (Core.tuple)($(Expr(:new, :(Main.T1), 1)), $(Expr(:new, :(Main.T2), 2)))::Tuple{T1,T2
}
return (Main.getX)($(Expr(:invoke, MethodInstance for rand(::Array{AT,1}), :(Main.rand), :($(Expr(:invoke, MethodInstance for copy!(::Array{AT,1}, ::Tuple{T1,T2}), :(Base.copy!), :($(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{AT,1}, svec(Any, Int64), Array{AT,1}, 0, 2, 0))), SSAValue(0)))))))::Int64
end::Int64
我在这里找到了解决这个问题的灵感:
如果您定义 getX
以采用 AT
的子类型,那么您就有 foo
的类型稳定代码:
julia> function getX(a::T)::Int where T <: AT
a.x
end
getX (generic function with 1 method)
julia> foo() = getX(rand([T1(1),T2(2)]))
foo (generic function with 1 method)
julia> @code_warntype foo()
Variables:
#self# <optimized out>
T <optimized out>
Body:
begin
SSAValue(0) = (Core.tuple)($(Expr(:new, :(Main.T1), 1)), $(Expr(:new, :(Main.T2), 2)))::Tuple{T1,T2}
return (Main.getX)($(Expr(:invoke, MethodInstance for rand(::Array{AT,1}), :(Main.rand), :($(Expr(:invoke, MethodInstance for copy!(::Array{AT,1}, ::Tuple{T1,T2}), :(Base.copy!), :($(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{AT,1}, svec(Any, Int64), Array{AT,1}, 0, 2, 0))), SSAValue(0)))))))::Int64
end::Int64
我不确定为什么会这样,也许对此事有更多了解的人会对此有所了解。
此外,这只是 type-stable,因为 getX
保证 return 和 Int
。如果你没有强制 getX
这样做,你将无法保证 return 和 Int
,因为你可能有不同的 AT
子类型 non-Ints.
我有一个关于访问字段时类型不稳定性的问题
abstract type
(在 julia v0.6 中)。
假设我有一个类型层次结构,所有类型层次结构都共享同一个实例变量。 我知道它既类型不稳定又不能保证正确访问 该字段正确,因为有人总是可以定义一个缺少的新子类型 预期变量。但是,即使将成员访问包装在 功能,访问仍然类型不稳定,我不知道为什么。
假设我们有一个简单的类型层次结构:
julia> begin
abstract type AT end
mutable struct T1 <: AT
x::Int
end
mutable struct T2 <: AT
x::Int
end
end
我们不是直接访问 a.x
,而是将其包装在函数屏障中:
julia> getX(a::AT)::Int = a.x
>> getX (generic function with 1 method)
julia> @code_warntype getX(T1(1))
Variables:
#self# <optimized out>
a::T1
Body:
begin
return (Core.getfield)(a::T1, :x)::Int64
end::Int64
请注意,通过此方法进行的访问是类型稳定的,因为它可以推断
a
的类型为 T1
.
但是,当我在编译器无法知道类型的上下文中使用 getX
时
变量的提前,它仍然是类型不稳定的:
julia> foo() = getX(rand([T1(1),T2(2)]))
>> foo (generic function with 1 method)
julia> @code_warntype foo()
Variables:
#self# <optimized out>
T <optimized out>
Body:
begin
SSAValue(0) = (Core.tuple)($(Expr(:new, :(Main.T1), 1)), $(Expr(:new, :(Main.T2), 2)))::Tuple{T1,T2}
SSAValue(2) = $(Expr(:invoke, MethodInstance for rand(::Array{AT,1}), :(Main.rand), :($(Expr(:invoke, MethodInstance for copy!(::Array{AT,1}, ::Tuple{T1,T2}), :(Base.copy!), :($(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{AT,1}, svec(Any, Int64), Array{AT,1}, 0, 2, 0))), SSAValue(0))))))
return (Core.typeassert)((Base.convert)(Main.Int, (Core.getfield)(SSAValue(2), :x)::Any)::Any, Main.Int)::Int64
end::Int64
请注意,它内联了 getX
的主体,并将其替换为 essentially
tmp.x::Int64
。这让我感到惊讶,因为我期待 getX
派遣到
我们在上面看到的同一定义的两个实例之一,其中没有断言
是必需的,因为类型已知。
我认为如果 getX
实际上 只是定义的话,这确实有意义
对于抽象基类型 AT
—— 没有方法可以分派到 in
我想象的方式。所以我尝试重新定义 getX
这样它就会生成
每个子类型的具体方法如下:
julia> getX(a::T where T<:AT)::Int = a.x
>> getX (generic function with 1 method)
但这实际上是一个相同的定义,没有任何改变:
julia> methods(getX)
>> # 1 method for generic function "getX":
getX(a::AT) in Main at none:1
知道如何让它工作吗?
啊,所以我需要自己手动定义getX
的不同版本。
我混淆了 julia
的类型分配机制和 C++
的模板
实例化。我想我 错误地想象 julia
定义了一个新版本
getX
对于每个 T
它被调用,以与 C++ 模板相同的方式
机制。
在这种情况下,我说的几乎是正确的
I was expecting
getX
to dispatch to one of the two instantiations [...] where no assert is necessary since the type is known.
但是,在这种情况下,实际上并没有两种不同的方法 派遣到——只有一个。如果我实际上 define 这两个不同 methods,调度机制可以满足类型稳定性:
julia> begin
abstract type AT end
mutable struct T1 <: AT
x::Int
end
mutable struct T2 <: AT
x::Int
end
end
julia> getX(a::T1) = a.x
>> getX (generic function with 1 method)
julia> getX(a::T2) = a.x
>> getX (generic function with 2 methods)
julia> foo() = getX(rand([T1(1),T2(2)]))
>> foo (generic function with 1 method)
julia> @code_warntype foo()
Variables:
#self# <optimized out>
T <optimized out>
Body:
begin
SSAValue(0) = (Core.tuple)($(Expr(:new, :(Main.T1), 1)), $(Expr(:new, :(Main.T2), 2)))::Tuple{T1,T2
}
return (Main.getX)($(Expr(:invoke, MethodInstance for rand(::Array{AT,1}), :(Main.rand), :($(Expr(:invoke, MethodInstance for copy!(::Array{AT,1}, ::Tuple{T1,T2}), :(Base.copy!), :($(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{AT,1}, svec(Any, Int64), Array{AT,1}, 0, 2, 0))), SSAValue(0)))))))::Int64
end::Int64
我在这里找到了解决这个问题的灵感:
如果您定义 getX
以采用 AT
的子类型,那么您就有 foo
的类型稳定代码:
julia> function getX(a::T)::Int where T <: AT
a.x
end
getX (generic function with 1 method)
julia> foo() = getX(rand([T1(1),T2(2)]))
foo (generic function with 1 method)
julia> @code_warntype foo()
Variables:
#self# <optimized out>
T <optimized out>
Body:
begin
SSAValue(0) = (Core.tuple)($(Expr(:new, :(Main.T1), 1)), $(Expr(:new, :(Main.T2), 2)))::Tuple{T1,T2}
return (Main.getX)($(Expr(:invoke, MethodInstance for rand(::Array{AT,1}), :(Main.rand), :($(Expr(:invoke, MethodInstance for copy!(::Array{AT,1}, ::Tuple{T1,T2}), :(Base.copy!), :($(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{AT,1}, svec(Any, Int64), Array{AT,1}, 0, 2, 0))), SSAValue(0)))))))::Int64
end::Int64
我不确定为什么会这样,也许对此事有更多了解的人会对此有所了解。
此外,这只是 type-stable,因为 getX
保证 return 和 Int
。如果你没有强制 getX
这样做,你将无法保证 return 和 Int
,因为你可能有不同的 AT
子类型 non-Ints.