Julia:函数类型和性能

cno*_*cno 9 performance julia

Julia 有没有办法概括如下所示的模式?

function compute_sum(xs::Vector{Float64})
    res = 0
    for i in 1:length(xs)
        res += sqrt(xs[i])
    end
    res
end
Run Code Online (Sandbox Code Playgroud)

这将计算每个向量元素的平方根,然后对所有内容求和。它比具有数组理解或的“天真”版本快得多map,并且也不分配额外的内存:

xs = rand(1000)

julia> @time compute_sum(xs)
  0.000004 seconds
676.8372556762225

julia> @time sum([sqrt(x) for x in xs])
  0.000013 seconds (3 allocations: 7.969 KiB)
676.837255676223

julia> @time sum(map(sqrt, xs))
  0.000013 seconds (3 allocations: 7.969 KiB)
676.837255676223
Run Code Online (Sandbox Code Playgroud)

不幸的是,“明显的”通用版本的性能很差:

function compute_sum2(xs::Vector{Float64}, fn::Function)
    res = 0
    for i in 1:length(xs)
        res += fn(xs[i])
    end
    res
end

julia> @time compute_sum2(xs, x -> sqrt(x))
  0.013537 seconds (19.34 k allocations: 1.011 MiB)
676.8372556762225
Run Code Online (Sandbox Code Playgroud)

Bog*_*ski 7

原因是它x -> sqrt(x)被定义为一个新的匿名函数,每次调用compute_sum2,因此每次调用它时都会导致新的编译。

如果你在之前定义它,例如像这样:

julia> f = x -> sqrt(x)
Run Code Online (Sandbox Code Playgroud)

那么你有:

julia> @time compute_sum2(xs, f) # here you pay compilation cost
  0.010053 seconds (19.46 k allocations: 1.064 MiB)
665.2469135020949

julia> @time compute_sum2(xs, f) # here you have already compiled everything
  0.000003 seconds (1 allocation: 16 bytes)
665.2469135020949
Run Code Online (Sandbox Code Playgroud)

请注意,一种自然的方法是定义一个具有如下名称的函数:

julia> g(x) = sqrt(x)
g (generic function with 1 method)

julia> @time compute_sum2(xs, g)
  0.000002 seconds
665.2469135020949
Run Code Online (Sandbox Code Playgroud)

您可以看到,x -> sqrt(x)每次编写时遇到它都会定义一个新的匿名函数,例如:

julia> typeof(x -> sqrt(x))
var"#3#4"

julia> typeof(x -> sqrt(x))
var"#5#6"

julia> typeof(x -> sqrt(x))
var"#7#8"
Run Code Online (Sandbox Code Playgroud)

请注意,如果在函数体中定义匿名函数,则情况会有所不同:

julia> h() = typeof(x -> sqrt(x))
h (generic function with 2 methods)

julia> h()
var"#11#12"

julia> h()
var"#11#12"

julia> h()
var"#11#12"
Run Code Online (Sandbox Code Playgroud)

你会看到这次匿名函数每次都是一样的。


Jak*_*sen 7

除了由博古米尔出色的响应,我只想补充一点,这概括了极为方便的方法是使用正常的函数式编程功能类似mapreducefold,等。

在这种情况下,您正在执行map转换(即sqrt)和减少(即+),因此您也可以使用mapreduce(sqrt, +, xs). 这基本上没有开销,并且在性能上可与手动循环相媲美。

如果您有一系列非常复杂的转换,您可以获得最佳性能并仍然使用 Transducers.jl 包中的函数。