ICC中的-O2扰乱了汇编程序,ICC中的-O1很好,GCC / Clang中的所有优化都很好

时间:2018-04-12 08:32:39

标签: c++ assembly compiler-optimization intrinsics icc

我最近开始使用ICC(18.0.1.126)编译一个代码,该代码在任意优化设置下与GCC和Clang一起工作正常。该代码包含一个汇编程序,它使用AVX2和FMA指令将4x4双精度矩阵相乘。经过多次摆弄后,结果表明汇编程序在使用-O1-xcore-avx2编译时工作正常,但在使用-O2-xcore-avx2编译时给出了错误的数值结果。然而,代码编译时没有任何关于所有优化设置的错误消息。它搭载早期的2015 MacBook Air和Broadwell核心i5。

我还有几个版本的4x4矩阵乘法程序,最初用于速度测试,有/无FMA,并使用汇编程序/内在函数。对所有人来说都是同样的问题。

我向例程传递一个指针,该指针指向创建为的4x4双精度数组的第一个元素     双MatrixDummy [4] [4]; 并传递给例程    (& MatrixDummy)[0] [0]

汇编程序例程在这里:

//Routine multiplies the 4x4 matrices A * B and store the result in C
inline void RunAssembler_FMA_UnalignedCopy_MultiplyMatrixByMatrix(double *A, double *B, double *C)
{
__asm__ __volatile__  ("vmovupd %0, %%ymm0 \n\t"
                       "vmovupd %1, %%ymm1 \n\t"
                       "vmovupd %2, %%ymm2 \n\t"
                       "vmovupd %3, %%ymm3"
                       :
                       :
                       "m" (B[0]),
                       "m" (B[4]),
                       "m" (B[8]),
                       "m" (B[12])
                       :
                       "ymm0", "ymm1", "ymm2", "ymm3");


__asm__ __volatile__ ("vbroadcastsd %1, %%ymm4 \n\t"
                      "vbroadcastsd %2, %%ymm5 \n\t"
                      "vbroadcastsd %3, %%ymm6 \n\t"
                      "vbroadcastsd %4, %%ymm7 \n\t"
                      "vmulpd %%ymm4, %%ymm0, %%ymm8 \n\t"
                      "vfmadd231PD %%ymm5, %%ymm1, %%ymm8 \n\t"
                      "vfmadd231PD %%ymm6, %%ymm2, %%ymm8 \n\t"
                      "vfmadd231PD %%ymm7, %%ymm3, %%ymm8 \n\t"
                      "vmovupd %%ymm8, %0"
                      :
                      "=m" (C[0])
                      :
                      "m" (A[0]),
                      "m" (A[1]),
                      "m" (A[2]),
                      "m" (A[3])
                      :
                      "ymm4", "ymm5", "ymm6", "ymm7", "ymm8");

__asm__ __volatile__ ("vbroadcastsd %1, %%ymm4 \n\t"
                      "vbroadcastsd %2, %%ymm5 \n\t"
                      "vbroadcastsd %3, %%ymm6 \n\t"
                      "vbroadcastsd %4, %%ymm7 \n\t"
                      "vmulpd %%ymm4, %%ymm0, %%ymm8 \n\t"
                      "vfmadd231PD %%ymm5, %%ymm1, %%ymm8 \n\t"
                      "vfmadd231PD %%ymm6, %%ymm2, %%ymm8 \n\t"
                      "vfmadd231PD %%ymm7, %%ymm3, %%ymm8 \n\t"
                      "vmovupd %%ymm8, %0"
                      :
                      "=m" (C[4])
                      :
                      "m" (A[4]),
                      "m" (A[5]),
                      "m" (A[6]),
                      "m" (A[7])
                      :
                      "ymm4", "ymm5", "ymm6", "ymm7", "ymm8");

__asm__ __volatile__ ("vbroadcastsd %1, %%ymm4 \n\t"
                      "vbroadcastsd %2, %%ymm5 \n\t"
                      "vbroadcastsd %3, %%ymm6 \n\t"
                      "vbroadcastsd %4, %%ymm7 \n\t"
                      "vmulpd %%ymm4, %%ymm0, %%ymm8 \n\t"
                      "vfmadd231PD %%ymm5, %%ymm1, %%ymm8 \n\t"
                      "vfmadd231PD %%ymm6, %%ymm2, %%ymm8 \n\t"
                      "vfmadd231PD %%ymm7, %%ymm3, %%ymm8 \n\t"
                      "vmovupd %%ymm8, %0"
                      :
                      "=m" (C[8])
                      :
                      "m" (A[8]),
                      "m" (A[9]),
                      "m" (A[10]),
                      "m" (A[11])
                      :
                      "ymm4", "ymm5", "ymm6", "ymm7", "ymm8");


__asm__ __volatile__ ("vbroadcastsd %1, %%ymm4 \n\t"
                      "vbroadcastsd %2, %%ymm5 \n\t"
                      "vbroadcastsd %3, %%ymm6 \n\t"
                      "vbroadcastsd %4, %%ymm7 \n\t"
                      "vmulpd %%ymm4, %%ymm0, %%ymm8 \n\t"
                      "vfmadd231PD %%ymm5, %%ymm1, %%ymm8 \n\t"
                      "vfmadd231PD %%ymm6, %%ymm2, %%ymm8 \n\t"
                      "vfmadd231PD %%ymm7, %%ymm3, %%ymm8 \n\t"
                      "vmovupd %%ymm8, %0"
                      :
                      "=m" (C[12])
                      :
                      "m" (A[12]),
                      "m" (A[13]),
                      "m" (A[14]),
                      "m" (A[15])
                      :
                      "ymm4", "ymm5", "ymm6", "ymm7", "ymm8");

}

作为比较,以下代码应该完全相同,并使用所有编译器/优化设置。因为如果我使用这个例程而不是汇编程序,一切都有效,我希望错误必须在ICC如何使用-O2优化来处理汇编程序。

inline void Run3ForLoops_MultiplyMatrixByMatrix_OutputTo3(double *A, double *B, double *C){

int i, j, k;

double dummy[4][4];

for(j=0; j<4; j++) {
    for(k=0; k<4; k++) {
        dummy[j][k] = 0.0;
        for(i=0; I<4; i++) {
            dummy[j][k] += *(A+j*4+i)*(*(B+i*4+k));
        }
    }
}

for(j=0; j<4; j++) {
    for(k=0; k<4; k++) {
        *(C+j*4+k) = dummy[j][k];
    }
}


}

有什么想法吗?我真的很困惑。

1 个答案:

答案 0 :(得分:3)

您的代码的核心问题是假设如果您向寄存器写入一个值,那么该值仍将存在于下一个语句中。这个假设是错误的。在asm语句之间,编译器可以根据需要使用任何寄存器。例如,它可能决定使用ymm0在您的对帐单之间将变量从一个位置复制到另一个位置,从而废弃其先前的内容。

进行内联汇编的正确方法是,如果没有充分的理由,永远不要直接引用寄存器。要在程序集语句之间保留的每个值都需要使用适当的操作数放在变量中。 manual对此很清楚。

举个例子,让我重写你的代码以使用正确的内联汇编:

#include <immintrin.h>

inline void RunAssembler_FMA_UnalignedCopy_MultiplyMatrixByMatrix(double *A, double *B, double *C)
{
    size_t i;

    /* the registers you use */
    __m256 a0, a1, a2, a3, b0, b1, b2, b3, sum;
    __m256 *B256 = (__m256 *)B, *C256 = (__m256 *)C;

    /* load values from B */
    asm ("vmovupd %1, %0" : "=x"(b0) : "m"(B256[0]));
    asm ("vmovupd %1, %0" : "=x"(b1) : "m"(B256[1]));
    asm ("vmovupd %1, %0" : "=x"(b2) : "m"(B256[2]));
    asm ("vmovupd %1, %0" : "=x"(b3) : "m"(B256[3]));

    for (i = 0; i < 4; i++) {
        /* load values from A */
        asm ("vbroadcastsd %1, %0" : "=x"(a0) : "m"(A[4 * i + 0]));
        asm ("vbroadcastsd %1, %0" : "=x"(a1) : "m"(A[4 * i + 1]));
        asm ("vbroadcastsd %1, %0" : "=x"(a2) : "m"(A[4 * i + 2]));
        asm ("vbroadcastsd %1, %0" : "=x"(a3) : "m"(A[4 * i + 3]));

        asm ("vmulpd %2, %1, %0"      : "=x"(sum) : "x"(a0), "x"(b0));
        asm ("vfmadd231pd %2, %1, %0" : "+x"(sum) : "x"(a1), "x"(b1));
        asm ("vfmadd231pd %2, %1, %0" : "+x"(sum) : "x"(a2), "x"(b2));
        asm ("vfmadd231pd %2, %1, %0" : "+x"(sum) : "x"(a3), "x"(b3));
        asm ("vmovupd %1, %0" : "=m"(C256[i]) : "x"(sum));
    }
}

你应该立即注意到很多事情:

  • 我们使用的每个寄存器都是通过asm操作数
  • 抽象描述的
  • 我们保存的所有值都绑定到局部变量,因此编译器可以跟踪哪些寄存器正在使用以及哪些寄存器可能被破坏
  • 由于asm语句的所有依赖关系和副作用都是通过操作数显式描述的,因此不需要volatile限定符,编译器可以更好地优化代码

但是,您应该考虑使用内在函数,因为编译器可以使用内在函数进行更多优化,而不是使用内联汇编。这是因为编译器在某种程度上理解了内在函数的作用,并且可以使用这些知识来生成更好的代码。