为何在MATLAB中使用gpuArray时a*b*a运算耗时高于(a'*(a*b)')'?
问题:MATLAB GPUArray中
a*b*a比(a'*(a*b)')'耗时更长的原因 用户的测试场景:
- 用
gpuArray创建了3000x3000的稀疏矩阵a(密度0.1)和稠密矩阵b - 两种运算数学上等价:
(a'*(a*b)')' = a*b*a - 实际运行时发现
a*b*a的耗时明显高于前者
为什么会有这种差异?
虽然两个表达式数学结果一致,但在GPU硬件上的执行逻辑天差地别,主要体现在这几个关键点:
内存访问模式的适配性差异
GPU的性能高度依赖连续、结构化的内存访问,而稀疏矩阵的运算顺序直接决定了内存访问效率:- 对于
(a'*(a*b)')':
第一步a*b是稀疏矩阵×稠密矩阵,MATLAB会利用稀疏矩阵的行压缩(CSR)格式,按行读取非零元素,对应乘稠密矩阵的列,内存访问是连续的,GPU能高效利用带宽。
第二步a' * (a*b)'是稀疏矩阵×稠密矩阵(a'同样是稀疏的),延续了高效的连续访问模式,最后转置只是内存布局调整,几乎不耗时。 - 对于
a*b*a:
第一步同样是a*b得到稠密矩阵,但第二步是稠密矩阵×稀疏矩阵。这时候GPU需要遍历稠密矩阵的每一行,再去匹配稀疏矩阵中零散分布的非零元素,内存访问是随机且分散的——这是GPU最不擅长的操作,会导致大量内存等待周期,直接拖慢速度。
- 对于
有效计算量的隐性差异
别看数学上乘法次数一致,实际执行的有效计算量差很多:- 在
a'*(a*b)'的运算中,因为左操作数是稀疏矩阵,GPU只会对非零元素对应的位置做计算,零元素直接跳过,无效操作极少。 - 而
a*b*a的第二步,稠密矩阵的每个元素都要和稀疏矩阵对应位置相乘,哪怕稀疏矩阵这里是0,GPU依然要读取稠密矩阵的元素(结果为0但操作没省),这部分无效的内存读取和计算额外消耗了大量时间。
- 在
MATLAB GPU库的优化倾向
MATLAB的GPU运算库对稀疏矩阵左乘稠密矩阵的优化非常成熟,能充分利用GPU的 warp 执行模式和内存缓存;但对于稠密矩阵左乘稀疏矩阵的优化力度就弱很多,因为这种场景的内存访问模式天然不适合GPU的架构。
小验证建议
你可以在代码里加几行,查看中间结果的稀疏性:
% 查看(a*b)的稀疏性 ab = a*b; disp(gpuArray.issparse(ab)); % 会显示false,是稠密矩阵 % 查看a'*(a*b)'的中间结果 temp = a' * ab'; disp(gpuArray.issparse(temp)); % 也是稠密矩阵
对比a*b*a的第二步中间过程,就能直观感受到两种路径的内存访问压力差异。
内容的提问来源于stack exchange,提问作者avgn




