向量化、非向量化与多线程Julia代码的性能对比
在Julia中实现大型浮点数数组与标量相乘的最快方式
嘿,这个问题问到点子上了!处理大型数组的标量乘法,Julia里有不少优化手段,咱们结合你给出的测试代码来拆解分析,最后给出最优方案。
首先先把你的测试代码贴出来方便参考:
using BenchmarkTools global const dim1 = 2000 global const dim2 = 2000 global const α = 0.9 global const α_array = α.*eye(dim2, dim2) function decay1(x) x .*= α end function decay2(x) for j in 1:dim2 for i in 1:dim1 @inbounds x[i,j] *= α end end end function decay3(x) Threads.@threads for j in 1:dim2 for i in 1:dim1 @inbounds x[i, j] *= α end end end
逐个分析你的实现
- decay1(广播原地乘法):Julia的广播机制
.=已经做了非常多的底层优化,比如自动启用SIMD指令、利用CPU缓存,而且代码简洁。对于大多数场景,这个实现已经足够高效,不需要手动写循环。 - decay2(单线程手动循环):虽然加了
@inbounds消除边界检查,但单线程的手动循环很难打过Julia内置的广播优化——毕竟广播是经过编译器深度优化的,比手写循环更能挖掘硬件潜力。 - decay3(多线程手动循环):在多核CPU上,这个版本会比decay2快不少,因为它把列循环分给了多个线程并行处理。不过要注意,你需要先设置好Julia的线程数(比如启动Julia时加
-t 4,或者在代码里用Threads.nthreads(4))才能发挥多线程的优势。
更快的优化方案:用BLAS库
其实还有个更高效的选择——直接调用BLAS的scal!函数。BLAS是专门针对线性代数运算高度优化的库,用汇编实现了极致的性能,充分利用CPU的缓存和SIMD指令集,对于标量乘数组这种操作,它的表现通常会比广播还要好一点。
实现代码很简单:
using LinearAlgebra function decay_blas(x) BLAS.scal!(length(x), α, x, 1) end
基准测试参考结果
在一台4核CPU的机器上,基准测试的大致性能排序(从快到慢)是:decay_blas > decay1 > decay3 > decay2
额外小提示
你代码里用了global const来定义常量,这很好——全局变量如果不加const会严重影响性能,不过更推荐的做法是把这些常量和函数放在一个模块里,或者作为函数参数传递,这样编译器能做更多优化。
内容的提问来源于stack exchange,提问作者Lost
相关产品推荐
相关产品推荐

