【问题标题】:Peformance difference due to type instability?由于类型不稳定导致的性能差异?
【发布时间】:2015-07-26 02:46:45
【问题描述】:

我正在 Julia 0.4-prerelease 中尝试以下代码,它以两种不同的方式(精确与级数展开)执行矩阵求幂。我尝试使用几种方法来获取数组维度n 和设置单位矩阵eye( n )

function test()
    A = [ 1.0 -1.0 ; -1.0  1.0 ]

    lam, U = eig( A )                         # diagonalization: A U = U diagm(lam)

    Bref = U * diagm( exp(lam) ) * U'         # Bref = exp(A) (in matrix sense)

    #[ Get the dimension n ]                            
    n = length( lam )                                                  # slow (1a)
    # const n = length( lam )                                          # slow (1b)
    # n::Int = length( lam )                                           # fast (1c)
    # const n::Int = length( lam )                                     # fast (1d)
    # n = size( A, 1 )                                                 # fast (1e)

    #[ Set unit matrices to B and X ]
    B = eye( n ); X = eye( n )                                         # slow with (1a) (2-1)
    # B = eye( 2 ); X = eye( 2 )                                       # fast (2-2)
    # B = eye( n::Int ); X = eye( n::Int )                             # fast (2-3) 
    # B::Array{Float64,2} = eye( n ); X::Array{Float64,2} = eye( n )   # fast (2-4)
    # B = eye( A ); X = eye( A )                                       # fast (2-5)

    #[ Calc B = exp(A) with Taylor expansion ]
    @time for k = 1:20
        X[:,:] = X * A / float( k )
        B[:,:] += X
    end

    #[ Check error ]
    @show norm( B - Bref )
end

test()

在这里我观察到,当 n 是动态变量(没有类型注释)时,代码变得比其他情况慢得多。例如,(1a) 和 (2-1) 的组合给出 下面的“慢”结果,而其他组合给出“快”结果(快 1000 倍以上)。

slow case => elapsed time: 0.043822985 seconds (1 MB allocated)
fast case => elapsed time: 1.1702e-5 seconds (16 kB allocated)

这是因为“类型不稳定”发生在 for 循环中吗?我很困惑,因为eye( n ) 始终是Array{Float64,2}(仅在初始化中使用)并且似乎没有(隐式)类型更改。同样令人困惑的是 (1e) 和 (2-1) 的组合速度很快,其中动态 n 设置为 size() 而不是 length()。总的来说,要获得良好的性能,显式注释数组维度变量会更好吗?

【问题讨论】:

    标签: julia


    【解决方案1】:

    我认为区别主要在于编译时间。如果我再放两个test()s,我会得到以下信息:

    使用2-11a

      73.599 milliseconds (70583 allocations: 3537 KB)
    norm(B - Bref) = 4.485301019485633e-14
      15.165 microseconds (200 allocations: 11840 bytes)
    norm(B - Bref) = 4.485301019485633e-14
      10.844 microseconds (200 allocations: 11840 bytes)
    norm(B - Bref) = 4.485301019485633e-14
    

    使用2-21a

       8.662 microseconds (180 allocations: 11520 bytes)
    norm(B - Bref) = 4.485301019485633e-14
       7.968 microseconds (180 allocations: 11520 bytes)
    norm(B - Bref) = 4.485301019485633e-14
       7.654 microseconds (180 allocations: 11520 bytes)
    norm(B - Bref) = 4.485301019485633e-14
    

    编译时间的差异来自于正在编译的不同代码。那个,以及剩下的一些时间上的小差异,实际上是来自一种类型的不稳定性。查看@code_warntype test() 的这一部分以获取1a 版本:

      GenSym(0) = (Base.LinAlg.__eig#214__)(GenSym(19),A::Array{Float64,2})::Tuple{Any,Any}
      #s8 = 1
      GenSym(22) = (Base.getfield)(GenSym(0),1)::Any
      GenSym(23) = (Base.box)(Base.Int,(Base.add_int)(1,1)::Any)::Int64
      lam = GenSym(22)
      #s8 = GenSym(23)
      GenSym(24) = (Base.getfield)(GenSym(0),2)::Any
      GenSym(25) = (Base.box)(Base.Int,(Base.add_int)(2,1)::Any)::Int64
      U = GenSym(24)
      #s8 = GenSym(25) # line 7:
      Bref = U * (Main.diagm)((Main.exp)(lam)::Any)::Any * (Main.ctranspose)(U)::Any::Any # line 9:
      n = (Main.length)(lam)::Any # line 11:
      B = (Main.eye)(n)::Any # line 11:
      X = (Main.eye)(n)::Any # line 13: # util.jl, line 170:
    

    我认为这是类型推断未能确定 eig 的返回类型。然后传播到 B 和 X。如果添加 n::Int,最后几行将变为

      n = (top(typeassert))((top(convert))(Main.Int,(Main.length)(lam)::Any)::Any,Main.Int)::Int64 # line 11:
      B = (Base.eye)(Base.Float64,n::Int64,n::Int64)::Array{Float64,2} # line 11:
      X = (Base.eye)(Base.Float64,n::Int64,n::Int64)::Array{Float64,2} # line 13: # util.jl, line 170:
    

    所以BX 输入正确。 An issue about this exact subject was raised recently - 如果您想获得最佳性能,除了自己注释之外似乎没有太多选择。

    【讨论】:

    • 嗯.. 我也得到了相同的结果(对 test() 的第二次和第三次调用变得“快”)。我想象 JIT 编译器首先将这个文件作为一个整体编译并测量 for-loop 的计算时间,但实际上编译是针对更小的代码块动态执行的,@time 包括这样的编译时间(正如你所解释的)?
    • 时间绝对包括编译,也包括为编译函数分配的内存。
    • 我已经更新了我的答案,以便更清楚地说明为什么首先要编译不同的代码。
    • 是的,非常感谢。现在我明白差异主要来自编译时间。此外,我已经阅读了您链接的页面(以及从那里链接的另一个页面),并且似乎存在关于 eig() 的微妙问题......(@code_warntype 的输出非常复杂@@;)无论如何,“任何" 似乎是一个警告标志。非常感谢:D
    • 尝试使用 julia --color=yes 运行 - Anys 会弹出鲜红色!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-01-24
    • 1970-01-01
    • 1970-01-01
    • 2013-04-04
    • 1970-01-01
    • 1970-01-01
    • 2013-03-28
    相关资源
    最近更新 更多