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

列序与行序寻址模式下代码vectorization的疑问及优化咨询

列序与行序寻址模式下代码vectorization的疑问及优化咨询

1. 列序寻址时到底什么被向量化了?

你看,编译器提示的是第21行的循环(也就是for (k = 0; k < 3; k++) { VRC[k] = p_M[k][s] - p_M[k][t]; })被向量化了,但因为列序布局下,p_M[k][s]是从不同的列数组里取相同索引的元素,属于非连续的内存访问,编译器只能用AVX2的gather指令(也就是你看到的__builtin_ia32_gatherdiv2df)来实现向量化——这种指令会一次性从分散的内存地址加载数据到向量寄存器里。

至于那些missed: statement clobbers memory的提示,其实是编译器的一种“标注”:gather指令本身会访问内存,编译器认为这个操作涉及内存读写,所以标记为missed,但本质上这个循环确实已经被向量化了,只是用了针对分散访问的特殊向量指令。

而行序布局的情况,p_M[s][k]是同一行里的连续元素,内存访问是连续的,编译器可以用普通的向量加载指令完成向量化,不需要gather,所以也就没有那行关于gather的missed提示。

2. 行序方式的向量化结果有差异吗?

从编译器的输出看,两者都成功向量化了那个k循环,但实际的向量化效率和底层指令差异很大:

  • 行序用的是连续内存的向量加载,这是CPU向量单元最擅长的操作,执行效率很高;
  • 列序用的是gather指令,这种指令处理分散内存访问的开销比连续访问大很多,虽然也算向量化,但性能不如行序。

不过最终的计算结果是完全一致的,只是性能上有差距。

3. 有没有办法把所有计算(包括VRR累加和sqrt)都向量化?

当然可以,核心是让编译器(或者手动用intrinsics)把整个计算流程都纳入向量操作的范围:

自动向量化方案:

  1. 把数据布局改成行序(这是基础,连续内存是高效向量化的前提);
  2. 把数组padding到4个元素(对应AVX2的256位向量宽度,刚好装4个double);
  3. 可以把循环和平方累加的逻辑稍微调整,让编译器能识别出可向量化的模式,比如:
// 假设p_M每行有4个元素,第4个是0
double vec_diff[4];
for (k = 0; k < 4; k++) {
    vec_diff[k] = p_M[s][k] - p_M[t][k];
}
// 编译器可以自动向量化这个平方和计算
VRR = vec_diff[0]*vec_diff[0] + vec_diff[1]*vec_diff[1] + vec_diff[2]*vec_diff[2] + vec_diff[3]*vec_diff[3];
VR = sqrt(VRR);

编译器会把减法、平方都用向量指令完成,然后用水平累加指令计算VRR,最后再做sqrt(sqrt本身也有向量版本,但因为VRR是标量,这里最后一步是标量sqrt,不过前面的核心计算都向量化了)。

手动intrinsics方案(更直接):

如果想完全手动控制向量操作,可以用AVX2的内置函数:

#include <immintrin.h>

// 行序布局,每行4个double,第4个为0
__m256d vec_s = _mm256_load_pd(p_M[s]);
__m256d vec_t = _mm256_load_pd(p_M[t]);
__m256d vec_diff = _mm256_sub_pd(vec_s, vec_t);
__m256d vec_sq = _mm256_mul_pd(vec_diff, vec_diff);

// 水平累加向量里的4个元素得到VRR
__m128d low_half = _mm256_castpd256_pd128(vec_sq);
__m128d high_half = _mm256_extractf128_pd(vec_sq, 1);
low_half = _mm_add_pd(low_half, high_half);
double temp[2];
_mm_storeu_pd(temp, low_half);
double VRR = temp[0] + temp[1];

double VR = sqrt(VRR);

这样整个减法、平方、累加的过程都用向量指令完成,效率最高。

4. 额外的零填充(padding)会有帮助吗?

肯定会,原因有两个:

  1. 匹配向量宽度:AVX2的向量是256位,刚好能装4个double。如果只处理3个元素,编译器只能用部分向量宽度,浪费了向量单元的算力;padding到4个元素后,可以用完整的向量指令一次性处理所有数据,向量化效率更高。
  2. 不影响计算结果:因为你填充的是0,平方后还是0,加到VRR里不会改变最终的结果。

另外,配合行序布局的话,padding后的内存访问是完全连续的,编译器可以生成最优化的向量指令,完全避免gather这种低效操作;就算是列序布局,padding到4列也能让gather指令一次性加载4个元素,比处理3个元素的情况要好一些。

备注:内容来源于stack exchange,提问作者Stanislav Fyodorov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.22 14:20:27