GCC 5.1 Развертывание цикла

Учитывая следующий код

#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 для создания сборки

EDIT: Нет повторяющихся вопросов, поскольку проблема полного развертывания циклов в более поздних версиях GCC еще не решена. Передача --param max-completely-peeled-insns=1000 --param max-completely-peel-times=10000 не влияет на сгенерированную сборку с использованием GCC >= 5.1.


person surrz    schedule 22.06.2016    source источник
comment
Интересно, что если вы измените условие for, например, на k < 9, развертывание вообще не будет выполнено...   -  person LPs    schedule 22.06.2016
comment
@LPS, за исключением очень маленькой итерации, такой как 2 или 3   -  person Garf365    schedule 22.06.2016
comment
@LP, использующие развертывание GCC 4.9.2, также работают менее чем за 9 итераций godbolt.org/g/ZPlCP6   -  person surrz    schedule 22.06.2016
comment
@surrz Да, я говорил о GCC 5.1.   -  person LPs    schedule 22.06.2016
comment
Возможный дубликат полного развертывания циклов GCC 5.1   -  person Artur Kink    schedule 22.06.2016
comment
@ArturKink это исходный пост автора, который был закрыт до того, как был отредактирован, чтобы соответствовать этому. И еще, эта тема не имеет никакого решения   -  person Garf365    schedule 23.06.2016
comment
@surrz: не могли бы вы либо принять мой ответ, либо хотя бы отреагировать на него? Как будто вы его полностью проигнорировали :-)   -  person Pyves    schedule 08.07.2016


Ответы (1)


Используемые вами флаги и параметры не гарантируют полного развертывания циклов. В документации GCC указано следующее относительно флага -funroll-all-loops, который вы используете:

включает полное удаление циклов (т. е. полное удаление циклов с небольшим постоянным числом итераций)

Если компилятор решит, что количество итераций для данного фрагмента кода не является «маленькой константой» (т. е. число слишком велико), он может выполнить только частичную очистку или развертывание, как здесь. Кроме того, параметры param, которые вы используете, являются только максимальными значениями, но не требуют полного развертывания для циклов, меньших заданного значения. Другими словами, если в цикле больше итераций, чем установленный вами максимум, то цикл не будет развернут полностью; но обратное неверно.

Многие факторы учитываются при оптимизации. Здесь узким местом в вашем коде является вызов функции printf, и компилятор, вероятно, примет это во внимание при расчете стоимости или решит, что накладные расходы на размер инструкции для развертывания слишком важны. Поскольку вы, тем не менее, говорите ему разворачивать циклы, он, кажется, определяет, что лучшим решением является преобразование начального цикла с 10 развертываниями и прыжком.

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

volatile int temp = k;

Цикл с этим новым фрагментом кода будет полностью развёрнут в новых версиях GCC (а также в старых). Обратите внимание, что ключевое слово volatile — это всего лишь уловка, поэтому компилятор не оптимизирует цикл полностью.

Подводя итог, насколько мне известно, нет никакого способа заставить более поздние версии GCC производить тот же результат.


В качестве примечания, начиная с уровня оптимизации -O2 и без каких-либо дополнительных флагов компилятора, последние версии Clang полностью разворачивают ваш цикл.

person Pyves    schedule 25.06.2016