GCC让我很难为源代码生成最佳程序集:
memset(X, 0, 16);
for (int i= 0; i < 16; ++i) {
X[0] ^= table[i][Y[i]].asQWord;
}
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] = /* ... */;
使用选项-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
答案 0 :(得分: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;
}
}
此处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会更精心地解释。)