【问题标题】:Why is my vector multiplication routine in C so slow? [duplicate]为什么我在 C 中的向量乘法程序这么慢? [复制]
【发布时间】: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?

标签: c x86 sse simd avx


【解决方案1】:

如果您的编译器支持 OpenMP 4.0 或更高版本,我会使用它来请求编译器对原始循环进行矢量化(如果使用足够高的优化级别,它可能已经这样做了;但是 OpenMP 可以让您给出有关事情的提示如对齐等以改善结果)。与 AVX 内在函数相比,它的优势在于它可以在 ARM 等其他架构或其他 x86 SIMD 指令集(假设你告诉编译器以它们为目标)上工作,只需简单的重新编译,而不必重写你的代码:

float sum = 0.0f;
#pragma omp simd reduction(+:sum) 
for (i = 0; i < n; i++) {
    sum += a[i] * b[i];
}

【讨论】:

  • 确实如此,您可以查看编译器生成的 asm 以了解它是如何矢量化的。 (希望至少有 4 个向量累加器,最好是 8 个。Why does mulss take only 3 cycles on Haswell, different from Agner's instruction tables? (Unrolling FP loops with multiple accumulators)
  • 非常感谢大家:我会尝试 omp simd pragma 以及任何其他提示。老实说,我已经(成功地)避开了 Assembler 40 多年了,我不确定这匹老马是否会学习新技巧...... :-) 但除非深入挖掘,否则我恐怕不会进一步推进这个话题进入它。
  • 注意:我刚刚使用 omp simd pragma 编译,但遗憾的是没有大的区别。
  • @MarioH 您是否记得在启用 OpenMP 支持的情况下进行编译?您是否检查过您的编译器是否已经对循环进行了矢量化处理?
  • gcc 和 clang 在循环结束时生成一个有效的缩减步骤时都做了一个相当 poor job 的操作。此外,gcc 使用单个累加器向量,这会阻止利用 ILP。底线是,如果你想快速编写代码,你应该自己编写。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-01
  • 1970-01-01
相关资源
最近更新 更多