GCC为数组元素的重复XOR生成冗余代码

apr*_*lev 20 c assembly gcc compiler-optimization

GCC让我很难为以下源代码生成最佳程序集:

memset(X, 0, 16);
for (int i= 0; i < 16; ++i) {
    X[0] ^= table[i][Y[i]].asQWord;
}
Run Code Online (Sandbox Code Playgroud)

X作为一个uint64_t[2]数组,并且
Y是一个unsigned char[16]数组,并且
table是一个双维数组union qword_t:

union qword_t {
    uint8_t asBytes[8];
    uint64_t asQWord;
};

const union qword_t table[16][256] = /* ... */;
Run Code Online (Sandbox Code Playgroud)

使用选项时,-m64 -Ofast -mno-sse它会展开循环,每个xor和赋值会产生3条指令(因此发出的指令总数为3*16 = 48):

movzx  r9d, byte ptr [Y + i]                   ; extracting byte
xor    rax, qword ptr [table + r9*8 + SHIFT]   ; xoring, SHIFT = i * 0x800
mov    qword ptr [X], rax                      ; storing result
Run Code Online (Sandbox Code Playgroud)

现在,我的理解是得到的X值可以rax在所有16 xors中累积到寄存器中,然后它可以存储在[X]地址中,这可以通过这两个指令实现,每个xor具有赋值:

movzx  r9d, byte ptr [Y + i]                   ; extracting byte
xor    rax, qword ptr [table + r9*8 + SHIFT]   ; xoring, SHIFT = i * 0x800
Run Code Online (Sandbox Code Playgroud)

和单一存储:

mov    qword ptr [X], rax                      ; storing result
Run Code Online (Sandbox Code Playgroud)

(在这种情况下,指令总数为2*16 + 1 = 33)

为什么GCC会生成这些冗余mov指令?我该怎么做才能避免这种情况?

PS C99,GCC 5.3.0,Intel Core i5 Sandy Bridge

eca*_*mur 44

冗余商店通常会出现混淆; 在这种情况下,gcc将无法证明商店对其X[0]不会产生影响table.如何将变量传递给例程会有很大的不同; 如果它们是全局变量或相同更大结构的成员,那么证明非混叠更容易.

示例:

void f1(uint64_t X[2]) {
  memset(X, 0, 16);
  for (int i= 0; i < 16; ++i) {
    X[0] ^= table[i][Y[i]].asQWord;
  }
}

uint64_t X[2];
void f2() {
  memset(X, 0, 16);
  for (int i= 0; i < 16; ++i) {
    X[0] ^= table[i][Y[i]].asQWord;
  }
}
Run Code Online (Sandbox Code Playgroud)

这里的商店X[0]是沉没在循环中f2而不是在中f1,因为只有在f2gcc 中才能证明这X不是别名的成员table.

您的解决方法/修复可能是调整参数的传递方式,使用说明restrict符,或自己手动下沉存储.

  • 术语:显然编译器开发人员称其为"沉没"商店的循环.这与从循环中提升循环不变计算的想法相同,但在另一个方向上.另外:英特尔语法输出使得更容易在godbolt输出中看到商店.作为第一个操作数的QWORD PTR更加突出.gcc完全展开你链接的版本,所以有一个代码墙效果,尤其是.用于AT&T语法.(我可能使用`-fno-unroll-loops`来提高人类的可读性.) (3认同)

Eli*_*an9 8

为避免这种情况,您可以使用此代替:

uint64_t v = 0;
for (int i= 0; i < 16; ++i) {
    v ^= table[i][Y[i]].asQWord;
}
X[0] = v;
X[1] = 0;
Run Code Online (Sandbox Code Playgroud)

您可以很容易地注意到生成的指令在您的情况下是次优的,但是由于不同的原因,gcc可能无法确定.(在这种情况下,gcc无法确定该表永远不会访问与X相同的内存区域,因为ecatmur更精心地解释了.)