Подтвердить что ты не робот

GCC оптимизирует фиксированный диапазон для цикла, как если бы он имел более длинную переменную длину

У меня есть массив структур 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 инструкций)

4b9b3361

Ответ 1

Как описано Ричардом Бинер в моем отчете об ошибке, проблема заключается в том, что GCC до версии 8 не понял, что поле класса или структуры были подвергнуты одинаковым оптимизациям (например, счету постоянных циклов) в качестве регулярной переменной. Таким образом, он будет генерировать всевозможные причудливые коды для оптимальной петли неизвестного количества раз, даже когда это было известно во время компиляции, в случае, когда контейнер был переменной-членом.

Как я понимаю, эта ошибка, вероятно, затрагивает довольно много кода в дикой природе - например. где-нибудь малый массив элементов является объектом цикла С++ 11 для цикла.

Спасибо Ричарду Бифферу за оперативное решение (предназначенное для GCC 8).