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
Теперь я понимаю, что полученное значение 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
и однохранилище:
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