Поведение развертки цикла в GCC

этот вопрос частично является последующим вопросом к разворачивание петли GCC 5.1.

по словам документация GCC, и, как указано в моем ответе на вышеуказанный вопрос, флаги, такие как -funroll-loops поворот на " полный пилинг петли (т. е. полное удаление петель с небольшим постоянным числом итераций)". Поэтому, когда такой флаг включен, компилятор может развернуть цикл, если он определяет, что это оптимизирует выполнение данного фрагмента кода.

тем не менее, я заметил в одном из моих проектов, что GCC иногда разворачивает петли даже если соответствующие флаги не были включены. Например, рассмотрим следующий простой фрагмент кода:

int main(int argc, char **argv)
{
  int k = 0;
  for( k = 0; k < 5; ++k )
  {
    volatile int temp = k;
  }
}

при компиляции с -O1, цикл развернут и следующий код сборки генерируется с любой современной версией GCC:

main:
        movl    , -4(%rsp)
        movl    , -4(%rsp)
        movl    , -4(%rsp)
        movl    , -4(%rsp)
        movl    , -4(%rsp)
        movl    , %eax
        ret

даже при компиляции с дополнительным -fno-unroll-loops -fno-peel-loops чтобы убедиться, что флаги отключен, GCC неожиданно по-прежнему выполняет развертывание цикла на примере, описанном выше.

-funroll-loops отключен? Есть ли способ полностью отключить развертывание цикла в GCC (часть из компиляции с -O0)?

интересно лязгом компилятор имеет ожидаемое поведение здесь и, похоже, выполняет развертывание только тогда, когда -funroll-loops включен, а не в других случаях.

заранее спасибо, любые дополнительные идеи по этому вопросу будут очень признательны!

1 ответов


почему GCC выполняет развертывание цикла, даже если флаги соответствующее поведение отключено?

подумайте об этом с прагматической точки зрения: что вы хотите при передаче такого флага компилятору? Ни один разработчик C++ не попросит GCC развернуть или не развернуть циклы, просто ради того, чтобы иметь циклы или нет в сборочном коде, есть цель. Цель с -fno-unroll-loops, например, пожертвовать немного скорости, чтобы уменьшить размер вашего двоичного файла, если вы разрабатываете встроенное программное обеспечение с ограниченным объемом памяти. С другой стороны, цель с -funrool-loops это сказать компилятору, что вас не волнует размер вашего двоичного файла, поэтому он не должен стесняться разворачивать циклы.

но это не означает, что компилятор будет втемную разверните или не все ваши петли!

в вашем примере причина проста: цикл содержит только один инструкция-несколько байтов на любых платформах - и компилятор знает, что это пренебрежимо мало и в любом случае займет почти тот же размер, что и код сборки, необходимый для цикла (sub + mov + jne на x86-64).

вот почему gcc 6.2, с -O3 -fno-unroll-loops получается этот код:

int mul(int k, int j) 
{   
  for (int i = 0; i < 5; ++i)
    volatile int k = j;

  return k; 
}

... к следующему коду сборки:

 mul(int, int):
  mov    DWORD PTR [rsp-0x4],esi
  mov    eax,edi
  mov    DWORD PTR [rsp-0x4],esi
  mov    DWORD PTR [rsp-0x4],esi
  mov    DWORD PTR [rsp-0x4],esi
  mov    DWORD PTR [rsp-0x4],esi  
  ret    

он не слушает вас, потому что он (почти, в зависимости от архитектуры) не изменит размер двоичного файла, но это быстрее. Однако, если вы увеличить укусил счетчик петель...

int mul(int k, int j) 
{   
  for (int i = 0; i < 20; ++i)
    volatile int k = j;

  return k; 
}

... он следует вашему намеку:

 mul(int, int):
  mov    eax,edi
  mov    edx,0x14
  nop    WORD PTR [rax+rax*1+0x0]
  sub    edx,0x1
  mov    DWORD PTR [rsp-0x4],esi
  jne    400520 <mul(int, int)+0x10>
  repz ret 

вы получите такое же поведение, если вы держите счетчик циклов в 5 но вы добавить некоторый код в цикл.

подводя итог, подумайте обо всех этих флагах оптимизации как подсказка для компилятора, и с прагматической точки зрения разработчика. Это всегда компромисс, и когда вы создаете программное обеспечение, вы никогда хочу попросить все или нет развертывание циклов.

и наконец, еще один очень похожий пример:-f(no-)inline-functions флаг. Я борюсь каждый день с компилятором для inline (или нет!) некоторые из моих функций (с inline ключевого слова __attribute__ ((noinline)) С GCC), и когда я проверяю код сборки, я вижу, что этот smartass все еще делает иногда то, что он хочет, когда я хочу встроить функцию, которая определенно слишком длинна для ее вкуса. И большую часть времени, это право что делать, и я счастлив!