我编写了下一个代码并测量了它的执行时间。
function main()
function Fib(n::Int)
if (n == 0 || n == 1)
return n
end
return(Fib(n - 2) + Fib(n - 1))
end
res = Fib(46)
println(res)
end
@time main()
1836311903 123.625395 秒(5.72 M 分配:88.103 MiB,0.02% gc 时间,0.02% 编译时间)2% 编译时间)
我使用 VS Code 并使用 F1 -> Julia: 在 REPL 中执行文件来运行程序。与同样的 C++ 代码相比,Julia 是不是很慢,只需要 16.5 秒左右?我做错了什么?
听说有时Julia会比C++慢,但没想到差距这么大。
TL;DR: Julia 在这个基准测试中要慢得多,因为
Fib
不是顶级函数。
该基准测试总体衡量函数调用的成本。 Julia 的函数调用往往比 C++ 的函数调用更昂贵,这是“有意为之”的。事实上,正如 Julia 文档中所述:
Julia 中的每个函数都是通用函数。泛型函数在概念上是单个函数,但由“许多定义”或方法组成。泛型函数的方法存储在方法表中。 给定调用
f(x,y)
,执行以下步骤:首先,通过typeof(f).name.mt
访问要使用的方法表。其次,形成一个参数元组类型,。 [...]。在方法表中查找该元组类型。Tuple{typeof(f), typeof(x), typeof(y)}
这个调度过程是由jl_apply_generic
执行的。
我尝试使用低级分析器来分析您的代码,我可以确认大部分时间都花在ijl_apply_generic
上,这是通用调度机制。更准确地说,大约 60% 的时间花在这方面,5% 花在整数装箱上,5% 花在其他 Julia 内部(如堆栈管理)上。简而言之,大部分时间只是纯粹的开销。
话虽如此,如果
Fib
是顶级函数,那么 Julia 显然可以优化这种调度机制。我猜它会将 Fib
视为代码内部的闭包,并且由于它们本质上更加动态的行为,因此优化闭包更加困难。这意味着您只需将
Fib
函数移到
main
函数之外,即可使函数更快。
这是更正后的代码:
function Fib(n::Int)
if (n == 0 || n == 1)
return n
end
return(Fib(n - 2) + Fib(n - 1))
end
function main()
res = Fib(46)
println(res)
end
@time main()
这是我的机器上的结果
Fib(42)
:
Initial Julia code: 12.859 seconds
Fixed Julia code: 1.100 seconds <-----
Unoptimized C++ code: 1.265 seconds
Optimized C++ code: 0.593 seconds
-O3 -march=native
表示优化版本)。
Julia 版本仍然不如 C++ 实现快,但“仅”慢了两倍左右。这是因为
Julia 没有实现尾部调用优化(TCO),而 C++ 编译器(如 Clang)通常会实现。这显然是因为上面讨论的多重调度机制(参见this
讨论)。 在斐波那契的具体情况下,请注意,这种实现从根本上来说是低效的!事实上,我们可以简单地使用“迭代实现”而不需要递归调用,并获得更好的性能(即使在 C++ 中也是如此)。一般来说,无论目标语言是什么,(非内联)函数调用都很昂贵,因此首先应该避免它们。