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 полностью развернут ваш цикл.

Другие вопросы по тегам