【发布时间】:2021-08-02 03:34:10
【问题描述】:
我试图找到在 C 中乘以两个 2dim 数组(单精度)的最有效方法,并从天真的想法开始,通过遵循算术规则来实现它:
for (i = 0; i < n; i++) {
sum += a[i] * b[i]; }
它有效,但可能不是地球上最快的例程。切换到指针算法并做一些循环展开速度得到了提高。但是,当应用 SIMD 时,速度又下降了。
更准确地说:在 Intel Core i5-4690, 3.5 GHz 上使用 -O3 在 Intel oneAPI 上编译我看到以下结果:
- 天真的实施:大约。 800 MFlop/s
- 使用指针 - 循环展开:高达 5 GFlop/s
- 应用 SIMD:3,5 - 5 GFlop/s
速度当然会随着向量的大小和不同的测试运行而变化,因此上面的数字更具指示性,但仍然提出了为什么 SIMD 例程没有显着推动的问题:
float hsum_float_avx(float *pt_a, float *pt_b) {
__m256 AVX2_vect1, AVX2_vect2, res_mult, hsum;
float sumAVX;
// load unaligned memory into two vectors
AVX2_vect1 = _mm256_loadu_ps(pt_a);
AVX2_vect2 = _mm256_loadu_ps(pt_b);
// multiply the two vectors
res_mult = _mm256_mul_ps(AVX2_vect1, AVX2_vect2);
// calculate horizontal sum of resulting vector
hsum = _mm256_hadd_ps(res_mult, res_mult);
hsum = _mm256_add_ps(hsum, _mm256_permute2f128_ps(hsum, hsum, 0x1));
// store result
_mm_store_ss(&sumAVX, _mm_hadd_ps(_mm256_castps256_ps128(hsum), _mm256_castps256_ps128(hsum)));
return sumAVX; }
一定有什么问题,但我找不到 - 因此任何提示都将受到高度赞赏。
【问题讨论】:
-
水平添加指令并不以特别快而著称。
-
因此,按向量进行累积通常很有帮助,即像
sum = _mm256_add_ps(sum, res_mult);之类的东西,并且只有在循环完成后才hadd一次。 -
这些天我可能会坚持使用 OpenMP SIMD pragma 的原始循环来明确要求编译器对其进行矢量化(如果在检查程序集时它还没有这样做;可能需要调整优化选项)
-
您的文本显示“两个 2dim-array”,但您的代码看起来像是在计算两个一维向量的一个点积。你应该澄清这个问题。如果您将两个二维数组相乘,缓存问题是一个很大的因素。
-
相关:Dot Product of Vectors with SIMD 展示了编译器如何向量化和展开(如果你让他们通过
-ffast-math假装 FP 数学是关联的)。最后做 SIMD 水平和 once ,而不是在内部循环中!但是,如果您实际上有矩阵,那么您正在为输出的每个元素进行行*列点积,即使您有一个转置,您也可能不想单独执行它们,因此一个中的行和另一个中的列是在内存中连续。另见What Every Programmer Should Know About Memory?