【问题标题】:Julia compiler does not appear to optimize when a function is passed a function向函数传递函数时,Julia 编译器似乎没有进行优化
【发布时间】:2015-02-06 00:10:14
【问题描述】:

第二次编辑: github 上的This pull request 将解决此问题。只要运行 Julia v0.5+,匿名函数就和普通函数一样快。所以案件结束了。

编辑:我已将问题和函数定义更新为更一般的情况。

举个简单的例子,当一个函数被传递给一个函数或者一个函数被定义在一个函数中时,Julia 编译器似乎并没有进行优化。这让我感到惊讶,因为这在优化包中很常见。我是正确的还是我在做一些愚蠢的事情?一个简单的例子如下:

f(a::Int, b::Int) = a - b    #A simple function

function g1(N::Int, fIn::Function)   #Case 1: Passing in a function
    z = 0
    for n = 1:N
        z += fIn(n, n)
    end
end

function g2(N::Int)   #Case 2: Function defined within a function
    fAnon = f
    z = 0
    for n = 1:N
        z += fAnon(n, n)
    end
    return(z)
end

function g3(N::Int)   #Case 3: Function not defined within function
    z = 0
    for n = 1:N
        z += f(n, n)
    end
    return(z)
end

然后我运行以下代码对这三种情况进行计时:

#Run the functions once
g1(10, f)   
g2(10)
g3(10)

@time g1(100000000, f)
@time g2(100000000)
@time g3(100000000)

时间安排是:

elapsed time: 5.285407555 seconds (3199984880 bytes allocated, 33.95% gc time)
elapsed time: 5.424531599 seconds (3199983728 bytes allocated, 32.59% gc time)
elapsed time: 2.473e-6 seconds (80 bytes allocated)

前两种情况需要大量内存分配和垃圾回收。谁能解释一下原因?

【问题讨论】:

    标签: julia


    【解决方案1】:

    所以一个有趣的事情是在 Julia 0.4 中使用 @code_warntype,它显示了以下内容:

    julia> @code_warntype g1(10, f)
    Variables:
      N::Int64
      fIn::F
      z::Any
      #s1::Int64
      n::Int64
    
    Body:
      begin  # none, line 2:
          z = 0 # line 3:
    ... snip ....
          z = z + (fIn::F)(n::Int64,n::Int64)::Any::Any
    

    所以问题在于f 的返回类型的推断,这实际上可能是任何东西。问题(据我了解)是 Julia 为每种类型组合编译了一个方法。我们已经为 any 函数生成了代码,所以任何东西都可以返回。如果Function 是返回类型的参数,那就太好了,因为这样我们就可以做一些更聪明的事情,比如Function{T<:Any,Int}

    我的解决方案是将其更改为 z += fIn(n, n)::Int,这允许 z 始终是 Int,但我仍然看到

    (top(typeassert))((fIn::F)(n::Int64,n::Int64)::Any,Int)::Int64
    

    @code_warntype 输出中,这是有道理的,因为它确实仍然是Any,我只是确保它不会污染其余部分。但我认为它仍然需要生成代码来检查它实际上是一个Int。让我们称这个新版本为g1A

    julia> @time g1(1000000, f)
    elapsed time: 0.124437357 seconds (30 MB allocated, 2.82% gc time in 1 pauses with 0 full sweep)
    elapsed time: 0.121653131 seconds (30 MB allocated, 2.51% gc time in 2 pauses with 0 full sweep)
    elapsed time: 0.120805345 seconds (30 MB allocated, 1.17% gc time in 1 pauses with 0 full sweep)
    
    julia> @time g1A(1000000, f)
    elapsed time: 0.085875439 seconds (30 MB allocated, 5.20% gc time in 1 pauses with 0 full sweep)
    elapsed time: 0.074592531 seconds (30 MB allocated, 4.67% gc time in 2 pauses with 0 full sweep)
    elapsed time: 0.078681071 seconds (30 MB allocated, 4.75% gc time in 1 pauses with 0 full sweep)
    

    所以有些收获,但并不理想。这是一个深入 Julia 内部工作的已知问题。相关讨论:

    【讨论】:

    • 非常有用的答案,谢谢。您是否会说在开发周期的这一点上,如果性能至关重要,应该避免将函数作为参数传递给函数吗?再次感谢。
    • 我认为它更多地取决于函数将被调用多少次。如果被调用的函数运行非常快并且被多次调用,那将是相当讨厌的。如果该函数被调用了几次,并且它只是整个程序的一小部分,或者该函数需要更长的时间来执行,那么它就可以了。
    【解决方案2】:

    这在 Julia v0.5 中已修复。所有这三种情况现在都应该提供与g3 相同的性能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-09
      • 2010-09-21
      • 2017-10-03
      相关资源
      最近更新 更多