41

我改编了一个简单的程序来计算和绘制 Julia 的运动涡流来测试语言,我也用 Python 编写了它,没有特别的原因。

(免责声明:1.我读过的关于stackoverflow的每一次性能比较都因不全面/正确/写得好/相关等而受到抨击-我不是假装这是一个真正的比较,我只是想知道如何进行Julia 更快。2. 我知道 python 可以被优化,在 Cython 等中实现,这不是这个讨论的一部分,它只是在这里作为 Julia 和 Python 中等效函数的参考。)

代码和性能结果可以在 gist中看到。

Julia 的性能明显慢于 Fortran。执行计算本身所花费的时间是(50000 个时间步长):

Fortran: 0.051s
Julia: 2.256s
Python: 30.846s

Julia 比 Fortran 慢得多(约 44 倍),差距缩小,但在 10 倍以上的时间步长 ( 0.50s vs 15.24s) 时仍然很重要。

这些结果与 Julia 主页上显示的结果有很大不同。我究竟做错了什么?我可以将 Julia 修复得更快吗?

我已经浏览了Julia 性能提示页面和 Julia 主页上比较背后的代码,但我没有什么需要解决的。

同样有趣的是,Julia 加载 PyPlot ( 5secsish!!) 的速度非常慢,而且读取文本文件的速度也比 Python 慢得多。我能做些什么来改善这些事情吗?

请注意,上述时间并未显示 Julia 和 Python 的加载时间,它只是计算 AFAIK 所需的原始时间 - 请参阅代码。对于fortran,这就是全部。在每种情况下,绘图已大致关闭,以便进行速度比较。

计算机:Intel i7-3770,16GB 内存,SSD HD,操作系统:Ubuntu 13.10 64bit.,Fortran:gfortran,GNU Fortran (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1,Julia:版本 0.3.0-prerelease+396 (2013-12-12 00:18 UTC),提交 c5364db*(0 天老主),x86_64-linux-gnu,Python:2.7.5+


更新:

根据 ivarne 的建议,我重写了 Julia 脚本(在上面的要点中进行了更新):将 grunt 工作封装在函数中,声明所有内容的类型并将矩阵的不同元素拆分为适用的不同数组。(我在很多地方都包含了 Float64,因为我尝试使用 Float32 看看是否有帮助,但大多数时候都没有)。

结果如下:

50,000时间步骤:

Fortran: 0.051s (entire programme)
Julia: raw calc.: 0.201s, calc. and return (?): 0.758s, total exec.: 6.947s

500,000时间步骤:

Fortran: 0.495s (entire programme)
Julia: raw calc.: 1.547s, calc. and return (?): 2.094s, total exec.: 8.521s

综上所述:

  • 你可以加快 Julia 的速度。

  • 根据您如何衡量它的性能,您可以显着影响 Julia 的明显速度。

4

2 回答 2

32

我已经关注 Julia 项目一段时间了,我对可能相关的代码有一些评论。

  • 似乎您在全局范围内运行了大量代码。目前 Julia 的全局环境非常慢,因为每次迭代都必须检查所有变量的类型。循环通常应该写在一个函数中。
  • 您似乎使用数组切片。目前,由于 Julia 没有快速的 Array 视图,因此会生成副本。您可能会尝试将它们切换为子数组,但它们目前比它们应该的要慢得多。

PyPlot(和任何其他包)的加载时间是一个已知问题,因为将 Julia 代码解析和编译为机器代码非常耗时。有一些想法是为这个过程设置一个缓存,这样这个过程就变得即时了,但它还没有完成。Base 库当前以编译状态缓存,因此大部分基础设施现在都在 master 分支上。

添加:我试图在一个独立的函数中运行测试并得到这些结果。看到这个要点

解析:

elapsed time: 0.334042578 seconds (11797548 bytes allocated)

并树主测试循环的连续符文。

elapsed time: 0.62999287 seconds (195210884 bytes allocated)
elapsed time: 0.39398753 seconds (184735016 bytes allocated)
elapsed time: 0.392036875 seconds (184735016 bytes allocated)

请注意第一次运行后时序如何改进,因为再次使用了编译的代码。

更新 2 通过一些改进的内存处理(确保数组的重用,因为分配不会复制),我将时间降低到 0.2 秒(在我的机器上)。为了避免分配新数组,肯定可以做更多的事情,但是它开始有点棘手。

这条线不符合你的想法:

vx_old = vx

但这做你想做的事:

copy!(vx_old, vx)

并去矢量化一个循环。

x += 0.5*(vx + vx_old)*delta_t
y += 0.5*(vy + vy_old)*delta_t

至:

for i = 1:nvortex
    x[i] += 0.5*(vx[i] + vx_old[i])*delta_t
    y[i] += 0.5*(vy[i] + vy_old[i])*delta_t
end
于 2013-12-16T15:44:55.093 回答
11

@ivarne 涵盖了这一点,但需要更多关注:

julia> @time x=[1:10000];
elapsed time: 1.544e-5 seconds (80120 bytes allocated)

julia> @time y = x[1:10000];
elapsed time: 2.6857e-5 seconds (80120 bytes allocated)

哇。那是很多时间和记忆。

julia> @time z = sub(x,1:10000);
elapsed time: 6.239e-6 seconds (296 bytes allocated)

好多了。为什么[:]不做什么sub?我不知道。嗯,我有点。当你去索引时z[10],朱莉娅认为,hrmm, z 就像 x 一样,除了索引偏移 0 所以z[10]x[10+0]。你去吧。如果您进行大量索引,那么从长远来看,这一点额外的添加将使您付出代价。要解决此问题,您需要一个与 Julia 的宗教相悖的概念,例如指针。

更新Julia 现在已弃用[:](版本 0.4.0)

julia> @time x=[1:10000];
WARNING: [a] concatenation is deprecated; use collect(a) instead
in depwarn at deprecated.jl:73
in oldstyle_vcat_warning at ./abstractarray.jl:29
in vect at abstractarray.jl:32
while loading no file, in expression starting on line 155
0.530051 seconds (180.12 k allocations: 9.429 MB, 5.26% gc time)

julia> @time x=[1:10000];
WARNING: [a] concatenation is deprecated; use collect(a) instead
in depwarn at deprecated.jl:73
in oldstyle_vcat_warning at ./abstractarray.jl:29
in vect at abstractarray.jl:32
while loading no file, in expression starting on line 155
0.001373 seconds (303 allocations: 714.656 KB)

收集更快

julia> @ time x=collect(1:10000);
0.003991 seconds (35 allocations: 80.078 KB)

julia> @ time x=collect(1:10000);
0.000031 seconds (8 allocations: 78.406 KB)

与子数组相当

julia> @time z = sub(x,1:10000);
0.067002 seconds (36.27 k allocations: 1.792 MB)

julia> @time z = sub(x,1:10000);
0.000016 seconds (7 allocations: 288 bytes)
于 2013-12-16T23:21:46.160 回答