闭包列表的类型稳定性

时间:2019-04-25 00:42:46

标签: julia type-stability

我正在尝试用Julia设计一些代码,这些代码将接受用户提供的函数列表,并从本质上对它们应用一些代数运算。

看来,如果函数是闭包,则不会推断出此函数列表的返回值,从而导致根据@code_warntype导致类型不稳定的代码。

我尝试为闭包提供返回类型,但似乎无法找到正确的语法。

这里是一个例子:

functions = Function[x -> x]

function f(u)
    ret = zeros(eltype(u), length(u))

    for func in functions
        ret .+= func(u)
    end

    ret
end

运行此:

u0 = [1.0, 2.0, 3.0]
@code_warntype f(u0)

并获取

Body::Array{Float64,1}
1 ─ %1  = (Base.arraylen)(u)::Int64
│   %2  = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{Float64,1}, svec(Any, Int64), :(:ccall), 2, Array{Float64,1}, :(%1), :(%1)))::Array{Float64,1}
│   %3  = invoke Base.fill!(%2::Array{Float64,1}, 0.0::Float64)::Array{Float64,1}
│   %4  = Main.functions::Any
│   %5  = (Base.iterate)(%4)::Any
│   %6  = (%5 === nothing)::Bool
│   %7  = (Base.not_int)(%6)::Bool
└──       goto #4 if not %7
2 ┄ %9  = φ (#1 => %5, #3 => %15)::Any
│   %10 = (Core.getfield)(%9, 1)::Any
│   %11 = (Core.getfield)(%9, 2)::Any
│   %12 = (%10)(u)::Any
│   %13 = (Base.broadcasted)(Main.:+, %3, %12)::Any
│         (Base.materialize!)(%3, %13)
│   %15 = (Base.iterate)(%4, %11)::Any
│   %16 = (%15 === nothing)::Bool
│   %17 = (Base.not_int)(%16)::Bool
└──       goto #4 if not %17
3 ─       goto #2
4 ┄       return %3

那么,如何使此代码类型稳定?

2 个答案:

答案 0 :(得分:4)

您的代码中存在几层问题(不幸的是要确保类型稳定性):

  1. functions是一个全局变量,因此从根本上讲您的代码将不会是类型稳定的
  2. 即使您将functions移入了函数定义,并且它是一个向量,代码仍将是类型不稳定的,因为容器将具有抽象的eltype(即使您删除了{{1} } Function之前的前缀(如果您有多个不同的功能)
  3. 如果将向量更改为元组(那么集合[将是类型稳定的),则该函数仍将是类型不稳定的,因为您使用的循环无法内部推断{ {1}}

解决方案是使用functions函数,该函数会将循环展开为func(u)的一系列连续应用程序-这样您的代码将是类型稳定的。

但是,总的来说,我认为假设@generated是昂贵的操作,那么代码中的类型不稳定性应该不会有太大问题,因为最终您将func(u)的返回值转换为func(u)

编辑:一个func(u)版本,用于与Tim Holy的建议进行比较。

Float64

答案 1 :(得分:4)

如果要为任意函数提供类型稳定性,则必须将它们作为元组传递,这使julia可以提前知道哪个函数将在哪个阶段应用。

function fsequential(u, fs::Fs) where Fs<:Tuple
    ret = similar(u)
    fill!(ret, 0)
    return fsequential!(ret, u, fs...)
end

@inline function fsequential!(ret, u, f::F, fs...) where F
    ret .+= f(u)
    return fsequential!(ret, u, fs...)
end
fsequential!(ret, u) = ret

julia> u0 = [1.0, 2.0, 3.0]
3-element Array{Float64,1}:
 1.0
 2.0
 3.0

julia> fsequential(u0, (identity, x-> x .+ 1))
3-element Array{Float64,1}:
 3.0
 5.0
 7.0

如果用@code_warntype进行检查,您会发现它是可推断的。

fsequential!是有时称为“ lispy tuple编程”的示例,在该示例中,您一次迭代处理一个参数,直到用尽所有vararg参数。这是一个强大的范例,它比带有数组的for循环允许更灵活的推理(因为它允许Julia为每个“循环迭代”编译单独的代码)。但是,通常只有在容器中的元素数量很少的情况下才有用,否则最终将导致疯狂的漫长的编译时间。

类型参数FFs看起来是不必要的,但是它们旨在迫使Julia为您传入的特定函数专门编写代码。