我正在尝试编写 C 代码,通过使用管道来掩盖 CPU 操作延迟。以下是摘录:
__m256 v256f_rslt_0 = _mm256_loadu_ps(&ch_results_8[pos + (0 * FLOATS_IN_M256)]);
__m256 v256f_rslt_1 = _mm256_loadu_ps(&ch_results_8[pos + (1 * FLOATS_IN_M256)]);
__m256 v256f_rslt_2 = _mm256_loadu_ps(&ch_results_8[pos + (2 * FLOATS_IN_M256)]);
__m256 v256f_rslt_3 = _mm256_loadu_ps(&ch_results_8[pos + (3 * FLOATS_IN_M256)]);
__m256 v256f_scale_0 = _mm256_loadu_ps(&cl_8[pos + (0 * FLOATS_IN_M256)]);
__m256 v256f_scale_1 = _mm256_loadu_ps(&cl_8[pos + (1 * FLOATS_IN_M256)]);
__m256 v256f_scale_2 = _mm256_loadu_ps(&cl_8[pos + (2 * FLOATS_IN_M256)]);
__m256 v256f_scale_3 = _mm256_loadu_ps(&cl_8[pos + (3 * FLOATS_IN_M256)]);
v256f_rslt_0 = _mm256_max_ps(v256f_rslt_0, v256f_c_zero);
v256f_rslt_1 = _mm256_max_ps(v256f_rslt_1, v256f_c_zero);
v256f_rslt_2 = _mm256_max_ps(v256f_rslt_2, v256f_c_zero);
v256f_rslt_3 = _mm256_max_ps(v256f_rslt_3, v256f_c_zero);
v256f_rslt_0 = _mm256_mul_ps(v256f_rslt_0, v256f_scale_0);
v256f_rslt_1 = _mm256_mul_ps(v256f_rslt_1, v256f_scale_1);
v256f_rslt_2 = _mm256_mul_ps(v256f_rslt_2, v256f_scale_2);
v256f_rslt_3 = _mm256_mul_ps(v256f_rslt_3, v256f_scale_3);
有 5 个数学运算 * 4;显示 2 个。
但是,编译器破坏了流水线。这是 ASM 的一部分:
vmaxps ymm2, ymm0, ymm10
vmulps ymm0, ymm2, YMMWORD PTR [r9+rax-96]
vminps ymm2, ymm0, ymm7
vmovups ymm0, YMMWORD PTR [rax-64]
vmulps ymm6, ymm3, ymm8
vsubps ymm3, ymm7, ymm2
vmaxps ymm2, ymm0, ymm10
vmulps ymm0, ymm2, YMMWORD PTR [r9+rax-64]
vminps ymm2, ymm0, ymm7
vmovups ymm0, YMMWORD PTR [rax-160]
vmulps ymm5, ymm3, ymm8
vsubps ymm3, ymm7, ymm2
编译器已明确将代码分为 4 个 block ,这意味着将出现最大延迟。
编译器优化:/O2/Oi/Ot/GL
链接器优化:/OPT:REF/OPT:ICF/LTCG:incremental
有没有办法阻止编译器对指令重新排序,从而保留流水线源代码?
最佳答案
在乱序执行的 CPU 上,通常不需要如此小规模的软件流水线,只要您使用多个累加器,这样 CPU 就可以找到一些 ILP。
现代 x86 CPU 在小规模指令调度方面出人意料地强大,现在 uop 缓存基本上消除了前端解码/对齐问题。 (但是,32 字节边界的指令位置仍然会对 uop 缓存产生影响,如果您遇到前端瓶颈,这可能会很重要。)
由于指令调度而导致的后端瓶颈很少见,直到您获得更长的 dep 链(大于 RS 大小):请参阅 Understanding the impact of lfence on a loop with two long dependency chains, for increasing lengths了解有关现代 CPU 如何处理多个长 dep 链以及查找 ILP 的限制的大量详细信息。
唯一可以运行此 AVX 代码的有序 CPU 是第一代 Xeon Phi (Knight's Corner),您通常希望使用它的 AVX512 变体而不是 AVX2。
同意此指令调度可能比您在源代码中使用的顺序更糟糕。
在较大规模下,或者如果您发现即使在这种规模下,手动调度指令(例如通过编辑编译器生成的 asm)也有助于提高性能,那么请尝试使用更好的编译器。
gcc、clang 和 ICC 都可以编译内部函数,因此您不会受制于 MSVC。
关于c - 在 VS 2017 中编写管道优化的 C AVX 代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52041552/