我正在尝试编写通过使用流水线掩盖CPU操作延迟的C代码。这是摘录:
__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
有没有一种方法可以防止编译器重新排序指令,从而保留流水线源代码?
答案 0 :(得分:4)
对于顺序混乱的CPU,通常不需要这么小规模的软件流水线,只要您使用的是多个累加器,那么CPU就会找到一些ILP。
就小规模的指令调度而言,现代的x86 CPU出奇地强大,因为uop缓存大多消除了前端解码/对齐问题。 (但是指令位置wrt。32字节边界仍然会对uop缓存产生影响,如果前端出现瓶颈,这可能会很重要。)
直到您获得更长的Dep链(大于RS大小)之前,由于指令调度而导致的后端瓶颈很少见:有关现代CPU如何处理多个长Dep链的详细信息,请参见Understanding the impact of lfence on a loop with two long dependency chains, for increasing lengths限制是为了找到ILP。
唯一可以运行此AVX代码的有序CPU是第一代Xeon Phi(骑士之角),您通常希望使用它的AVX512变体而不是AVX2。
同意该指令调度可能比您在源代码中使用的顺序差。
在更大的规模上,或者如果您发现即使在这种规模下,手动调度指令(例如通过编辑由编译器生成的asm)也可以提高性能,然后尝试使用更好的编译器。
gcc,clang和ICC都可以编译内部函数,因此您不必担心MSVC。