当函数传递函数时,Julia编译器似乎不会优化

Col*_*ers 15 julia

第二次编辑: github上的这个拉取请求将解决问题.只要有人运行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 Code Online (Sandbox Code Playgroud)

然后我运行以下代码来计算三种情况:

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

@time g1(100000000, f)
@time g2(100000000)
@time g3(100000000)
Run Code Online (Sandbox Code Playgroud)

时间是:

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)
Run Code Online (Sandbox Code Playgroud)

前两种情况的内存分配和垃圾收集很多.谁有人解释为什么?

Iai*_*ing 13

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

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
Run Code Online (Sandbox Code Playgroud)

所以问题在于返回类型的推断f,它实际上可能是任何东西.问题(据我所知)是Julia为每种类型组合编译一个方法.我们在这里为任何函数生成了代码,因此任何东西都可以回来.如果Function在返回类型上是参数化的话,它会很整洁,因为那时我们可以做更聪明的事情Function{T<:Any,Int}.

我的解决方案是改变它z += fIn(n, n)::Int,并且这允许z永远是一个,Int但我仍然看到

(top(typeassert))((fIn::F)(n::Int64,n::Int64)::Any,Int)::Int64
Run Code Online (Sandbox Code Playgroud)

@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)
Run Code Online (Sandbox Code Playgroud)

所以有些收获,但并不理想.这是一个已知的问题,深入到朱莉娅的内部运作中.相关讨论:

  • 我认为它更依赖于调用函数的次数.如果被调用的函数运行得非常快并被多次调用,那将是非常讨厌的.如果函数被调用了几次,并且它是整个程序的一小部分,或者函数需要更长的时间来执行,那么就可以了. (3认同)