为什么我的 Julia 代码比 C++ 慢?

问题描述 投票:0回答:1

我编写了下一个代码并测量了它的执行时间。

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++慢,但没想到差距这么大。

performance julia
1个回答
0
投票

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

请注意,Julia 版本为 1.9.4,C++ 代码已使用 Clang 16.0.6 编译(使用标志 

-O3 -march=native
 表示优化版本)。

Julia 版本仍然不如 C++ 实现快,但“仅”慢了两倍左右。这是因为

Julia 没有实现尾部调用优化
(TCO),而 C++ 编译器(如 Clang)通常会实现。这显然是因为上面讨论的多重调度机制(参见

this

讨论)。 在斐波那契的具体情况下,请注意,这种实现从根本上来说是低效的!事实上,我们可以简单地使用“迭代实现”而不需要递归调用,并获得更好的性能(即使在 C++ 中也是如此)。一般来说,无论目标语言是什么,(非内联)函数调用都很昂贵,因此首先应该避免它们。

© www.soinside.com 2019 - 2024. All rights reserved.