You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 10:44:52