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)
原因是它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)
你会看到这次匿名函数每次都是一样的。
除了由博古米尔出色的响应,我只想补充一点,这概括了极为方便的方法是使用正常的函数式编程功能类似map,reduce,fold,等。
在这种情况下,您正在执行map转换(即sqrt)和减少(即+),因此您也可以使用mapreduce(sqrt, +, xs). 这基本上没有开销,并且在性能上可与手动循环相媲美。
如果您有一系列非常复杂的转换,您可以获得最佳性能并仍然使用 Transducers.jl 包中的函数。