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

时间:2016-04-13 08:19:26

标签: c gcc assembly compiler-optimization

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

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

Xuint64_t[2]数组,和
Yunsigned char[16]数组,和
tableunion qword_t的双维数组:

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

const union qword_t table[16][256] = /* ... */;

使用选项-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

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

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

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

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

P.S。 C99,GCC 5.3.0,Intel Core i5 Sandy Bridge

2 个答案:

答案 0 :(得分:44)

冗余存储通常归结为别名;在这种情况下,gcc无法证明对X[0]的商店不会影响table。它将变量传递给例程如何有很大的不同;如果它们是全局变量或同一个更大结构的成员,那么证明非混叠更容易。

Example

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;
  }
}

此处X[0]的商店已退出f2但不在f1的商店,因为只有f2才能证明X不为table的成员添加别名。

您的解决方法/修复方法可能是调整参数的传递方式,使用the restrict specifier或自行手动下沉商店。

答案 1 :(得分:8)

为避免这种情况,您可以改为使用它:

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

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