为何在线程循环外生成大随机数矩阵比循环内逐列生成更快?
为什么三段Julia代码的性能差异这么大?
核心原因分析
第一段代码(最快)
X = randn( 3, 1000000 ) Y = Vector{Float64}( undef, 1000000 ) @threads for n in 1:size( X, 2 ) Y[n] = do_stuff( X[:,n] ) end
- 批量生成随机数效率拉满:
randn(3, 1000000)一次性生成所有数据,底层用CPU矢量化指令批量处理,缓存利用率极高,单线程生成速度远快于多次小批量调用。 - 并行阶段无资源竞争:并行循环仅做读取X的列、计算
do_stuff、写入Y的操作。Julia默认列优先存储,X的每一列内存连续,线程读取时缓存命中率高,且各线程操作独立的列和Y元素,无锁竞争。
第二段代码(慢20%)
Y = Vector{Float64}( undef, 1000000 ) @threads for n in 1:size( X, 2 ) X = randn( 3 ) Y[n] = do_stuff( X ) end
- 随机数生成的锁竞争拖后腿:全局随机数生成器(RNG)是线程安全的,但依赖内部锁实现。多个线程同时调用
randn(3)时,会频繁争抢锁,导致大量等待时间,直接抵消并行的收益。 - 小批量生成的固定开销被放大:每次仅生成3个元素,无法利用矢量化指令优势,RNG状态更新等单调用固定开销占比过高,整体生成效率远低于批量模式。
第三段代码(慢40-50%)
X = Vector{Float64}( undef, 3, 1000000 ) Y = Vector{Float64}( undef, 1000000 ) @threads for n in 1:size( X, 2 ) X[:,n] = randn( 3 ) Y[n] = do_stuff( X[:,n] ) end
- 继承第二段的所有问题:同样存在RNG锁竞争和小批量生成的低效问题。
- 额外的内存复制开销:
X[:,n] = randn(3)需要把生成的3个元素复制到X的对应列,多了一次不必要的内存拷贝操作。 - 缓存命中率进一步下降:X是大数组,多线程同时写入不同列会频繁触发缓存行刷新同步,降低内存访问效率;
do_stuff(X[:,n])操作列视图时,还可能带来额外的边界检查或寻址开销。
内容的提问来源于stack exchange,提问作者aquaticapetheory
相关产品推荐
相关产品推荐

