Julia是一门随便写写就有不错的效率的语言。但高性能和灵活性通常是矛盾的,Julia也不例外。当你想尽量榨取Julia的性能时,除了阅读官方文档的建议外,也可以看看本文总结的几条经验之谈。这些都是我最近编写CFD程序时踩过的坑。有些你可能已经熟悉了,仅供参考。
- 尽可能使用内置函数或扩展包里的函数。例如算术平均数
mean
。你可能会发现新版本的Julia里没有内置的mean
,于是自己随手写成sum()/length()
。其实,如今mean
位于Statistics
包,效率比sum()/length()
更高。另一个典型例子是LinearAlgebra
包的normalize
。总之,当你要写一个比较泛用的函数时,建议先找找有没有造好的轮子。 - 避免使用拼接数组。例如,你可能会像这样把两个向量拼成一个更长的向量:
v = [v1; v2]
。这会显著降低性能。建议先创建一个固定长度的零向量,然后把v1
和v2
赋给它,效率会提高一两个量级。 - 避免对一个很大的数组的同一个元素做很多次重复索引。本来索引是很快的,但大量的重复索引也会相当耗时。可先把该元素赋给一个变量,然后调用这个变量,避免重复索引。例如:
julia> A = ones(Float64,1_000_000);
julia> a = A[1];
julia> @time for i = 1:1_000_000
A[1] = 2
end
0.015121 seconds
julia> @time for i = 1:1_000_000
a = 2
end
0.002962 seconds
- 对于分布式数组DArray,应从程序设计层面上减少跨进程的元素访问。本质上就是进程间通信成本的问题。之所以要专门讲这个,是因为在
DistributedArrays
包里提供的DArray允许直接跨进程访问。这固然是极大地方便了编程,但成本也相当高昂。事实证明,哪怕Julia写并行程序很方便,也要好好斟酌一下怎么写才最优,否则性能恐怕达不到期望。 - 使用DArray时, 除了跨进程访问外,在各进程内部localpart上的访问也应尽量减少次数。例如,假设你要对DArray的每个元素操作10次。如果每次操作都访问一次,会导致耗时随次数接近线性增长。建议先把该元素赋给一个变量,操作完再传回,这样至多访问两次。如果元素是数组或自定义类型,Julia会自动引用而无需传回,只访问一次。总之,要高度关注并减少DArray的访问次数。
- 如果循环次数很大,而循环体较复杂,应尝试把整个循环体打包成一个函数,即具有如下形式:
for i = 1:N
body_function()
end
function body_function()
# 循环体
end
这有助于编译优化。我自己测试的一段代码快了20%多一点,不过相比前几条建议来说不是特别显著。
- 使用
close()
或d_closeall()
显式回收DArray的内存。DistributedArrays.jl
的文档里是这么解释的:
当主进程触发 gc (garbage collection) 时,workers上的内存会随之被回收。但主进程只存储DArray对象而DArray对象本身是很小的(数据在workers上),所以在DArray用完之后通常不会立即触发主进程的gc,导致workers上的内存回收随之延迟。