Julia并行设计疑惑:@distributed为何比@threads快3-4倍
Julia与MATLAB并行性能差异疑问解答
我正尝试将散射问题的MATLAB、Python代码迁移至Julia,查阅文档、搜索并参考相关帖子后,仍无法理解Julia的并行机制,对MATLAB的并行逻辑也存在疑惑。核心诉求是解释测试场景下@distributed比@threads快3至4倍的原因。
测试过程及现象
1. 单线程/自动并行对比
MATLAB普通循环代码:
tic k = zeros(10000, 100, 100); for i = 1:10000 k(i, :, :) = rand(100, 100)*rand(100, 100)* ... rand(100, 100)*rand(100, 100)*rand(100, 100)* ... rand(100, 100)*rand(100, 100)*rand(100, 100)*... rand(100, 100)*rand(100, 100); end toc
Julia单线程等效代码:
function f() k = zeros(10000, 100, 100) @simd for i = 1:10000 @inbounds k[i, :, :] = rand(Float64, (100, 100))*rand(Float64, (100, 100))* rand(Float64, (100, 100))*rand(Float64, (100, 100))* rand(Float64, (100, 100))*rand(Float64, (100, 100))* rand(Float64, (100, 100))*rand(Float64, (100, 100))* rand(Float64, (100, 100))*rand(Float64, (100, 100)); end return k end @time t = f();
结果:MATLAB普通循环自动利用并行(CPU占用90%),性能比Julia单线程代码高1520%。
2. 多线程改造后对比
Julia添加Threads.@threads的代码:
function f() k = zeros(10000, 100, 100) Threads.@threads for i = 1:10000 @inbounds k[i, :, :] = rand(Float64, (100, 100))*rand(Float64, (100, 100))* rand(Float64, (100, 100))*rand(Float64, (100, 100))* rand(Float64, (100, 100))*rand(Float64, (100, 100))* rand(Float64, (100, 100))*rand(Float64, (100, 100))* rand(Float64, (100, 100))*rand(Float64, (100, 100)); end return k end @time t = f();
结果:Julia性能与MATLAB普通循环相当。
3. 多进程/parfor对比
MATLAB parfor代码:
tic k = zeros(10000, 100, 100); parfor i = 1:10000 k(i, :, :) = rand(100, 100)*rand(100, 100)* ... rand(100, 100)*rand(100, 100)*rand(100, 100)* ... rand(100, 100)*rand(100, 100)*rand(100, 100)*... rand(100, 100)*rand(100, 100); end toc
Julia多进程代码:
addprocs(8) function f() k = SharedArray{Float64}(10000, 100, 100) @sync @distributed for i = 1:10000 @inbounds k[i, :, :] = rand(Float64, (100, 100))*rand(Float64, (100, 100))* rand(Float64, (100, 100))*rand(Float64, (100, 100))* rand(Float64, (100, 100))*rand(Float64, (100, 100))* rand(Float64, (100, 100))*rand(Float64, (100, 100))* rand(Float64, (100, 100))*rand(Float64, (100, 100)); end return k end @time t = f();
结果:MATLAB parfor性能提升4倍,Julia多进程代码达到同等性能,但@threads版本远慢于该结果。
疑问解答
1. 为何@threads性能远不如@distributed?为何需要测试两种方式?
核心原因是线程间的资源竞争:
@threads是多线程并行,所有线程共享同一个进程的内存空间,包括随机数生成器(RNG)。你的代码里每个循环都要调用rand,而Julia的默认RNG不是线程安全的,线程间会互相等待获取RNG的锁,这会严重拖慢速度。@distributed是多进程并行,每个进程有独立的RNG和内存空间,不存在线程间的锁竞争,每个进程可以全速运行。
至于为什么要测试两种方式,因为并行方案的性能取决于任务类型:
- 如果任务是计算密集且无共享资源竞争,多线程(
@threads)可能更快(因为进程间通信开销更小);但如果任务频繁使用共享资源(比如全局RNG、共享内存的频繁写入),多进程(@distributed)反而更高效。你的测试场景刚好踩中了多线程的痛点,所以多进程表现更好。
2. Julia为何不像MATLAB那样自动启用多线程?
这是设计选择,原因有两点:
- 避免隐式并行带来的意外行为:自动并行可能会改变代码的执行顺序(比如RNG的序列),或者在有共享状态的代码中引发难以调试的bug。Julia更倾向于让开发者显式控制并行逻辑,保证代码的可预测性。
- 灵活性:Julia的并行模型支持多线程、多进程、GPU等多种方式,显式选择让开发者可以根据任务场景最优配置,而不是被单一的自动并行限制。
MATLAB的自动并行主要是针对矩阵运算这类高度结构化的场景做了优化,但对于复杂逻辑的代码,自动并行可能失效甚至出错,这也是Julia选择显式并行的原因之一。
3. 为何CPU占用相近但性能差异大?
CPU占用率高不代表实际有效计算量高:
- MATLAB普通循环/Julia多线程版本中,CPU看起来很忙,但很多时间花在等待锁释放(比如等待RNG的使用权),或者线程间的调度开销上,实际用于矩阵乘法的有效计算时间并不多。
- MATLAB parfor/Julia多进程版本中,每个进程独立运行,没有锁竞争,CPU的时间几乎全部用于有效计算,所以同样的CPU占用率下,完成的任务量是前者的4倍左右。
简单说就是:前者是“忙等”,后者是“真干活”,所以性能差异明显。
内容的提问来源于stack exchange,提问作者Paradoxy
相关产品推荐
相关产品推荐

