在 VS 2017 中编写流水线优化的 C AVX 代码

Writing pipeline optimized C AVX code in VS 2017

我正在尝试编写 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 个块,这意味着将出现最大延迟。

编译器优化:/O2 /Oi /Ot /GL 链接器优化:/OPT:REF /OPT:ICF /LTCG:incremental

有没有办法阻止编译器重新排序指令,从而保留流水线源代码?

在乱序执行的 CPU 上通常不需要如此小规模的软件流水线,只要您使用多个累加器,因此 [= 有一些 ILP 26=] 找到。

现代 x86 CPUs 在小规模指令调度方面出奇地健壮,现在 uop-caches 主要消除了前端解码/对齐问题。 (但是指令位置 wrt。32 字节边界仍然对 uop 缓存有影响,如果您遇到前端瓶颈,这可能很重要。)

除非你得到比 RS 大得多的更长的 dep 链,否则由于指令调度而导致的后端瓶颈很少见:有关现代 CPUs 如何处理的详细信息,请参阅 多个长 dep 链,以及查找 ILP 的限制是什么。

唯一 CPU 可以 运行 此 AVX 代码的是第一代 Xeon Phi(Knight's Corner),您通常希望使用它的 AVX512 变体而不是AVX2.


同意此指令调度可能比您在源代码中使用的顺序更差。

在更大的规模上,或者如果您发现即使在这种规模下手动调度指令(例如通过编辑编译器生成的 asm)有助于提高性能,那么请尝试使用更好的编译器。

gcc、clang 和 ICC 都可以编译内部函数,因此您不会受困于 MSVC。