Julia (Julia-lang) 与 Fortran 和 Python 的性能比较

Posted

技术标签:

【中文标题】Julia (Julia-lang) 与 Fortran 和 Python 的性能比较【英文标题】:Julia (Julia-lang) Performance Compared to Fortran and Python 【发布时间】:2014-01-04 00:21:15 【问题描述】:

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

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

代码和性能结果可见in a gist。

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

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

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

这些结果与the julia home page 上显示的结果明显不同。我究竟做错了什么?我可以将 Julia 修复得更快吗?

我已经浏览了 Julia Performance Tips 页面和 Julia 主页上比较背后的代码,但没有什么值得我解决的。

另外有趣的是,Julia 加载 PyPlot (5secs ish!!) 的速度非常慢,而且读取文本文件的速度也比 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), Commit c5364db* (0 days old master), 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 的速度,具体取决于您衡量其性能的方式。

【问题讨论】:

他们确实在基准测试中提到了使用 BLAS。 matmul 的性能在 Fortran、C、Julia 和 MATLAB 中几乎相同,这也让其脱颖而出。如果测试的很大一部分花费在某个预编译库中,那么语言之间的比较可能不是公平的。也许您应该尽可能尝试用 BLAS 调用替换部分代码并再次进行比较? 在 Julia 中不需要在函数上声明类型,除非你想使用它进行多次调度。列表推导通常也无需任何努力即可获得正确的类型。如果您有类型/不可变结构,则必须声明类型以获得良好的性能。 A +1 仅用于免责声明。 【参考方案1】:

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

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

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

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

解析:

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

【讨论】:

【参考方案2】:

@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] 时,Julia 认为,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)

【讨论】:

到目前为止,数组切片进行复制的原因是,当切片在原始内存中不连续时,很难处理结果。找出解决此问题的技巧是避免复制的剩余工作。

以上是关于Julia (Julia-lang) 与 Fortran 和 Python 的性能比较的主要内容,如果未能解决你的问题,请参考以下文章

Julia:抽象类型与类型联合

julia系列1:介绍与安装

julia系列1:介绍与安装

与 Python+Numba LLVM/JIT 编译的代码相比,Julia 的性能

Julia编程语言

Matlab 与 Julia 与 Fortran 中的速度