4

abstract type(ジュリア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それを本質的に に置き換えたことに 注意してくださいtmp.x::Int64getXこれには驚きました。先ほど見た同じ定義の 2 つのインスタンス化のうちの 1 つにディスパッチすることを期待していたので、型がわかっているのでアサートは必要ありません。


getX実際に抽象基本型に対してのみ定義されている場合、これはある程度意味があると思いましたAT-私が想像している方法でディスパッチするメソッドはありません。そこで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

どうすればこれを機能させることができますか?

4

2 に答える 2

0

ああ、自分自身のさまざまなバージョンを手動で定義する必要がありましたgetX


の型ディスパッチjuliaメカニズムとC++のテンプレートのインスタンス化を混同していました。C++ テンプレート メカニズムと同じように、呼び出されたfor everyの新しいバージョンを定義する ものを間違って想像したと思います。juliagetXT

この場合、私が言ったとき、私はほとんど正しかった

getXタイプがわかっているため、アサートが不要な 2 つのインスタンス化 [...] のいずれかにディスパッチすることを期待していました。

ただし、この場合、ディスパッチ先のメソッドは実際には 2 つではなく、1 つしかありません。実際に 2 つの異なるメソッドを定義すると、ディスパッチ メカニズムは型の安定性を満たすことができます。

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

ここでこれを修正するためのインスピレーションを見つけました: https://stackoverflow.com/a/40223936/751061

于 2018-03-29T17:20:43.337 に答える