为什么我的Julia代码比JavaScript运行得更慢?

4
最近,我对Julia语言产生了兴趣,因为它声称是一种具有接近C性能的动态语言。然而,到目前为止,我的使用经验并不好(至少在性能方面)。 我正在编写的应用程序需要随机访问特定数组索引,然后将它们的值与其他特定数组索引进行比较(在许多迭代中)。以下代码模拟了我从程序中需要的内容: 我的Julia代码执行时间约为8秒,而在Chrome环境下,JavaScript代码仅需不到1秒! 我在Julia代码中做错了什么吗?非常感谢您的帮助。
以下是Julia代码:
n=5000;
x=rand(n)
y=rand(n)
mn=ones(n)*1000;
tic();
for i in 1:n;
    for j in 1:n;
        c=abs(x[j]-y[i]);
        if(c<mn[i])
            mn[i]=c;
        end
    end
end
toc();

JavaScript代码:(比上面的Julia代码快8倍以上!)
n=5000; x=[]; y=[]; mn=[];
for(var i=0; i<n; i++){x.push(Math.random(1))}
for(var i=0; i<n; i++){y.push(Math.random(1))}
for(var i=0; i<n; i++){mn.push(1000)}
console.time('test');
for(var i=0; i<n; i++){
    for(var j=0; j<n; j++){
        c=Math.abs(x[j]-y[i]);
        if(c<mn[i]){
            mn[i]=c;
        }       
    }
} 
console.timeEnd('test');

如果您还没有从(出色的)答案中获得这一点,请务必阅读官方文档中的性能技巧章节,它将大大帮助您避免这些性能陷阱,而不需要更深入地了解 Julia 的内部运作。 - Colin T Bowers
非常感谢,现在我确实看到了Julia在性能方面如何胜过JavaScript的地方和方式。 - Hunar
@Hunar,你是使用 Node.js 编译你的 JavaScript 代码吗? - abhimanyuaryan
2个回答

8

性能提示

避免使用全局变量

全局变量可能在任何时刻都会改变其值,因此也会改变其类型,这使得编译器难以优化使用全局变量的代码。尽可能将变量定义为本地变量或通过参数传递给函数。

任何对性能要求较高或需要进行基准测试的代码应该放在函数内部。

我们发现许多全局名称通常是常量,将其声明为常量可以大大提高性能:

julia> const n = 5000; const x, y = rand(n), rand(n); const mn = fill(1000.0, n);

julia> function foo!(mn, x, y)
           n = length(mn)
           @inbounds for i in 1:n, j in 1:n
               c = abs(x[j] - y[i])
               if(c < mn[i])
                   mn[i] = c
               end
           end
           return mn
       end
foo! (generic function with 1 method)

julia> using BenchmarkTools: @btime

julia> @btime foo!(mn, x, y)
  15.432 ms (0 allocations: 0 bytes)

1
可以在任何时候更改:更具体地说,作为任何(非内联)函数调用的可能副作用,对吧? Julia 全局变量不像 C++ 的 std::atomic 共享全局变量那样,可以被其他线程异步更改,对吗?(我并不真正了解 Julia,只是随意好奇。但我认为性能建议是说,即使唯一的函数调用是 abs(),当前编译器也不会在全局变量上寻找优化,这应该是内置且可内联的。所以这是当前编译器中的一个被忽视的优化,对吧?因此仍然是一个好的建议。) - Peter Cordes
不是专家,但我认为问题在于如果变量所持有的值的类型可以在任何时刻改变(即在每个函数调用中),那么您不能只使用接受Int的abs函数(因为您无法知道它将是一个Int)。因此,您仍然需要调用“通用”函数,检查类型,然后运行正确的函数。此外,您甚至不能确定map的结果类型,因为它可能会更改(当abs函数更改时)。编译器无法优化它无法推断的东西。 - Nico202
虽然我也不是专家,但我认为随时更改的问题来自于一切都是函数调用,而且一切都可以被覆盖。 - 是一个函数调用,<x[j] 甚至 = 也是。x[j] 实际上是 getindex(x, i),在你的代码中理论上可以被重写以改变 x(尽管这显然是一个可怕的想法)。这仍然是一个错过的优化,因为编译器可以获得非变异内置函数的知识,验证它们没有被覆盖,并基于此进行优化,但这是非常棘手的,并且有其自身的问题。 - Sundar R

4

Julia代码应该始终放在函数中,以便编译器可以对其进行优化。此外,您正在测量编译时间和执行时间:为了获得准确的度量结果,您应该调用该函数两次(第一次是为了编译)。

function test(n)
    x=rand(n);
    y=rand(n);
    mn=ones(n)*1000;

    for i in 1:n;
        for j in 1:n;
            c=abs(x[j]-y[i])
            if(c<mn[i])
                mn[i]=c
            end
        end
    end
    (mn, x, y)
end

test(1)
@time test(5000);

在我的笔记本电脑上,这需要0.04秒。在Chromium中的JavaScript需要1秒(在Firefox Web控制台中需要53秒)。


2
没错。此外,您可以为循环添加@inbounds注释,并将xymn的创建移至函数外部并传递它们(以便与JavaScript代码中的工作量相同)。最后,如果您第二次运行@time ...,执行时间应该会更快,因为您不需要支付编译成本。 - Bogumił Kamiński

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接