gpt4 book ai didi

c - 当编译器对 Sandy 上的 AVX 指令重新排序时,它会影响性能吗?

转载 作者:太空狗 更新时间:2023-10-29 16:34:23 36 4
gpt4 key购买 nike

请不要说这是过早的微优化。鉴于我有限的知识,我想尽可能多地了解所描述的 SB 功能和程序集的工作原理,并确保我的代码使用此架构功能。感谢您的理解。

几天前我开始学习内在函数,所以答案对某些人来说似乎很明显,但我没有可靠的信息来源来弄清楚这个问题。

我需要为 Sandy Bridge CPU 优化一些代码(这是一项要求)。现在我知道它可以在每个周期执行一次 AVX 乘法和一次 AVX 加法,并阅读这篇论文:

http://research.colfaxinternational.com/file.axd?file=2012%2F7%2FColfax_CPI.pdf

它展示了如何在 C++ 中完成。所以,问题是我的代码不会使用 Intel 的编译器自动矢量化(这是任务的另一个要求),所以我决定使用像这样的内部函数手动实现它:

__sum1 = _mm256_setzero_pd();
__sum2 = _mm256_setzero_pd();
__sum3 = _mm256_setzero_pd();
sum = 0;
for(kk = k; kk < k + BS && kk < aW; kk+=12)
{
const double *a_addr = &A[i * aW + kk];
const double *b_addr = &newB[jj * aW + kk];
__aa1 = _mm256_load_pd((a_addr));
__bb1 = _mm256_load_pd((b_addr));
__sum1 = _mm256_add_pd(__sum1, _mm256_mul_pd(__aa1, __bb1));

__aa2 = _mm256_load_pd((a_addr + 4));
__bb2 = _mm256_load_pd((b_addr + 4));
__sum2 = _mm256_add_pd(__sum2, _mm256_mul_pd(__aa2, __bb2));

__aa3 = _mm256_load_pd((a_addr + 8));
__bb3 = _mm256_load_pd((b_addr + 8));
__sum3 = _mm256_add_pd(__sum3, _mm256_mul_pd(__aa3, __bb3));
}
__sum1 = _mm256_add_pd(__sum1, _mm256_add_pd(__sum2, __sum3));
_mm256_store_pd(&vsum[0], __sum1);

这里解释了我手动展开循环的原因:

Loop unrolling to achieve maximum throughput with Ivy Bridge and Haswell

他们说您需要展开 3 倍才能在桑迪上获得最佳性能。我的天真测试证实这确实比没有展开或 4 倍展开运行得更好。

好的,问题来了。来自 Intel Parallel Studio 15 的 icl 编译器生成这个:

    $LN149:
movsxd r14, r14d ;78.49
$LN150:
vmovupd ymm3, YMMWORD PTR [r11+r14*8] ;80.48
$LN151:
vmovupd ymm5, YMMWORD PTR [32+r11+r14*8] ;84.49
$LN152:
vmulpd ymm4, ymm3, YMMWORD PTR [r8+r14*8] ;82.56
$LN153:
vmovupd ymm3, YMMWORD PTR [64+r11+r14*8] ;88.49
$LN154:
vmulpd ymm15, ymm5, YMMWORD PTR [32+r8+r14*8] ;86.56
$LN155:
vaddpd ymm2, ymm2, ymm4 ;82.34
$LN156:
vmulpd ymm4, ymm3, YMMWORD PTR [64+r8+r14*8] ;90.56
$LN157:
vaddpd ymm0, ymm0, ymm15 ;86.34
$LN158:
vaddpd ymm1, ymm1, ymm4 ;90.34
$LN159:
add r14d, 12 ;76.57
$LN160:
cmp r14d, ebx ;76.42
$LN161:
jb .B1.19 ; Prob 82% ;76.42

对我来说,这看起来一团糟,正确的顺序(使用方便的 SB 功能所需的乘法加法)被打破了。

问题:

  • 此汇编代码是否会利用我所指的 Sandy Bridge 功能?

  • 如果不是,我需要做什么才能利用该功能并防止代码像这样“纠结”?

此外,当只有一个循环迭代时,顺序非常干净,即加载、乘法、加法,这是应该的。

最佳答案

对于 x86 CPU,许多人期望从点积中获得最大的 FLOPS

for(int i=0; i<n; i++) sum += a[i]*b[i];

但结果是 not to be the case .

能给出最大FLOPS的是这个

for(int i=0; i<n; i++) sum += k*a[i];

其中 k 是一个常量。为什么 CPU 没有针对点积进行优化?我可以推测。 CPU 优化的其中一项是 BLAS . BLAS 正在考虑许多其他例程的构建 block 。

随着 n 的增加,Level-1 和 Level-2 BLAS 例程成为内存带宽限制。只有 Level-3 例程(例如矩阵乘法)能够受计算限制。这是因为 Level-3 计算为 n^3,而读取为 n^2。因此,CPU 针对 Level-3 例程进行了优化。 Level-3 例程不需要针对单个点积进行优化。他们每次迭代只需要从一个矩阵中读取 (sum += k*a[i])。

由此我们可以得出结论,为获得 Level-3 例程的最大 FLOPS,每个周期需要读取的位数是

read_size = SIMD_WIDTH * num_MAC

其中 num_MAC 是每个周期可以完成的乘法累加运算的数量。

                   SIMD_WIDTH (bits)   num_MAC  read_size (bits)  ports used
Nehalem 128 1 128 128-bits on port 2
Sandy Bridge 256 1 256 128-bits port 2 and 3
Haswell 256 2 512 256-bits port 2 and 3
Skylake 512 2 1024 ?

对于 Nehalem-Haswell,这符合硬件的能力。我实际上并不知道 Skylake 将能够在每个时钟周期读取 1024 位,但如果它不能,AVX512 就不会很有趣,所以我对我的猜测充满信心。可以在 http://www.anandtech.com/show/6355/intels-haswell-architecture/8 找到每个港口的 Nahalem、Sandy Bridge 和 Haswell 的漂亮地 block 。

到目前为止,我已经忽略了延迟和依赖链。要真正获得最大 FLOPS,您需要在 Sandy Bridge 上至少展开循环 3 次(我使用 4 次是因为我发现使用 3 的倍数不方便)

回答有关性能问题的最佳方法是找到您期望的理论最佳操作性能,然后比较您的代码与该性能的接近程度。我称之为效率。这样做你会发现,尽管你在程序集中看到的指令重新排序,但性能仍然很好。但是您可能需要考虑许多其他微妙的问题。以下是我遇到的三个问题:

l1-memory-bandwidth-50-drop-in-efficiency-using-addresses-which-differ-by-4096 .

obtaining-peak-bandwidth-on-haswell-in-the-l1-cache-only-getting-62%

difference-in-performance-between-msvc-and-gcc-for-highly-optimized-matrix-multp .

我还建议您考虑使用 IACA研究性能。

关于c - 当编译器对 Sandy 上的 AVX 指令重新排序时,它会影响性能吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27769834/

36 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com