Поворотное развертывание в GCC

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

Согласно Документация 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    $0, -4(%rsp)
movl    $1, -4(%rsp)
movl    $2, -4(%rsp)
movl    $3, -4(%rsp)
movl    $4, -4(%rsp)
movl    $0, %eax
ret

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

Это наблюдение приводит меня к следующим тесно связанным вопросам. Почему GCC выполняет развертывание цикла, даже если флаги, соответствующие этому поведению, отключены? Развертывание также контролируется другими флагами, которые могут заставить компилятор развернуть цикл в некоторых случаях, даже если -funroll-loops выключен? Есть ли способ полностью отключить развертывание цикла в GCC (часть от компиляции с -O0)?

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

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

9

Решение

Почему 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 ключевое слово и __attribute__ ((noinline)) с GCC), и когда я проверяю ассемблерный код, я вижу, что этот умный человек все еще иногда делает то, что он хочет, когда я хочу встроить функцию, которая определенно слишком длинна для его вкуса. И в большинстве случаев это правильно, и я счастлив!

8

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]