GCC 5.1 Loop развертывание
Учитывая следующий код
#include <stdio.h>
int main(int argc, char **argv)
{
int k = 0;
for( k = 0; k < 20; ++k )
{
printf( "%d\n", k ) ;
}
}
Использование GCC 5.1 или более поздней версии с
-x c -std=c99 -O3 -funroll-all-loops --param max-completely-peeled-insns=1000 --param max-completely-peel-times=10000
частично разворачивает цикл, он разворачивает цикл десять раз, а затем выполняет условный переход.
.LC0:
.string "%d\n"
main:
pushq %rbx
xorl %ebx, %ebx
.L2:
movl %ebx, %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 1(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 2(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 3(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 4(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 5(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 6(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 7(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 8(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 9(%rbx), %esi
xorl %eax, %eax
movl $.LC0, %edi
addl $10, %ebx
call printf
cmpl $20, %ebx
jne .L2
xorl %eax, %eax
popq %rbx
ret
Но использование более старых версий GCC, таких как 4.9.2, создает желаемую сборку
.LC0:
.string "%d\n"
main:
subq $8, %rsp
xorl %edx, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $1, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $2, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $3, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $4, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $5, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $6, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $7, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $8, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $9, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $10, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $11, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $12, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $13, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $14, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $15, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $16, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $17, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $18, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $19, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
xorl %eax, %eax
addq $8, %rsp
ret
Есть ли способ заставить более поздние версии GCC производить такой же вывод?
Использование https://godbolt.org/g/D1AR6i для создания сборки
РЕДАКТИРОВАТЬ: Нет дублированного вопроса, так как проблема полностью развернуть циклы с более поздними версиями GCC еще не была решена. Переходя --param max-completely-peeled-insns=1000 --param max-completely-peel-times=10000
не влияет на сгенерированную сборку при использовании GCC >= 5.1
1 ответ
Флаги и параметры, которые вы используете , не гарантируют, что циклы будут полностью развернуты. Документация GCC гласит следующее относительно -funroll-all-loops
флаг, который вы используете:
включает полную очистку петли (т.е. полное удаление петель с небольшим постоянным числом итераций)
Если компилятор решает, что число итераций для данного фрагмента кода не является "маленькой константой" (т. Е. Число слишком велико), он может выполнить только частичное удаление или развертывание, как это было сделано здесь. Кроме того, param
параметры, которые вы используете, являются только максимальными значениями, но не вызывают принудительное полное развертывание для циклов, меньших, чем установленное значение. Другими словами, если цикл имеет больше итераций, чем установленный вами максимум, цикл не будет полностью развернут; но обратное неверно.
Многие факторы учитываются при выполнении оптимизаций. Здесь узким местом в вашем коде является призыв к printf
функции, и компилятор, вероятно, примет это во внимание при выполнении своих расчетов стоимости или решит, что накладные расходы на размер команд для развертывания слишком важны. Тем не менее, поскольку вы все же говорите об этом, чтобы развернуть циклы, кажется, определено, что лучшим решением является преобразование начального цикла с 10 развертываниями и прыжком.
Если вы замените printf
с чем-то еще, компилятор может оптимизировать по-другому. Например, попробуйте заменить его следующим:
volatile int temp = k;
Цикл с этим новым фрагментом кода будет полностью развернут на более новых версиях GCC (а также на более старых). Обратите внимание, что ключевое слово volatile - это просто хитрость, поэтому компилятор не полностью оптимизирует цикл.
Подводя итог, насколько мне известно, нет способа заставить более поздние версии GCC производить тот же результат.
Как примечание, от уровня оптимизации -O2
и далее и без каких-либо дополнительных флагов компилятора, последние версии Clang полностью развернут ваш цикл.