У меня есть массив структур POD и я пытаюсь суммировать одно поле. Вот минимальный пример:
struct Item
{
int x = 0;
int y = 0;
};
typedef Item Items[2];
struct ItemArray
{
Items items;
int sum_x1() const;
int sum_x2() const;
};
int ItemArray::sum_x1() const
{
int total = 0;
for (unsigned ii = 0; ii < 2; ++ii)
{
total += items[ii].x;
}
return total;
}
int ItemArray::sum_x2() const
{
int total = 0;
for (const Item& item : items)
{
total += item.x;
}
return total;
}
Две функции суммы выполняют одно и то же. Кланг компилирует их одинаково. Но GCC 6 с -O3
на x86_64 нет. Здесь sum_x1()
, хорошо выглядящий:
mov eax, DWORD PTR [rdi+8]
add eax, DWORD PTR [rdi]
ret
Теперь посмотрите sum_x2()
:
lea rdx, [rdi+16]
lea rcx, [rdi+8]
xor eax, eax
add eax, DWORD PTR [rdi]
cmp rdx, rcx
je .L12
lea rcx, [rdi+16]
add eax, DWORD PTR [rdi+8]
cmp rdx, rcx
je .L2
lea rcx, [rdi+24]
add eax, DWORD PTR [rdi+16]
cmp rdx, rcx
je .L2
lea rcx, [rdi+32]
add eax, DWORD PTR [rdi+24]
cmp rdx, rcx
je .L2
lea rcx, [rdi+40]
add eax, DWORD PTR [rdi+32]
cmp rdx, rcx
je .L2
lea rcx, [rdi+48]
add eax, DWORD PTR [rdi+40]
cmp rdx, rcx
je .L2
lea rcx, [rdi+56]
add eax, DWORD PTR [rdi+48]
cmp rdx, rcx
je .L2
lea rcx, [rdi+64]
add eax, DWORD PTR [rdi+56]
cmp rdx, rcx
je .L2
lea rcx, [rdi+72]
add eax, DWORD PTR [rdi+64]
cmp rdx, rcx
je .L2
add eax, DWORD PTR [rdi+72]
ret
.L2:
rep ret
.L12:
rep ret
Почему GCC испускает развернутый цикл переменной длины до 10, когда длина петли фиксируется на 2? Это делает это только в функции-члене, создавая sum_x2
свободную функцию.
ICC также оптимизирует sum_x2()
очень странно, хотя сгенерированный код полностью отличается. В отличие от GCC, неважно, является ли sum_x2()
функцией-членом или свободной функцией - оба являются плохими.
Я использую GCC 6, но все версии GCC, похоже, имеют проблемы с этим кодом. Добавление -march=haswell
делает его еще хуже, добавляя итерации до 15 элементов в массиве размера 2. GCC 5 и 7 генерируют еще более сложный код, добавляя инструкции SIMD.
Я хотел бы определить точную причину этой проблемы, чтобы я мог находить и исправлять подобные вхождения в моем коде. Понимание того, что вызывает такое поведение в GCC 6, будет очень полезно. В моем коде у меня много диапазонов для циклов, и я не слишком взволнован перспективой их удаления, но если GCC не сможет генерировать разумный код, у меня не будет выбора.
Попробуйте: https://godbolt.org/g/9GK4jy
Больше связанного безумия: https://godbolt.org/g/BGYggD (оптимальный код - 3 инструкции, GCC 6 - 8 инструкций, GCC 7 - 130 инструкций)