GCC 5.1循环展开

给出以下代码

#include  int main(int argc, char **argv) { int k = 0; for( k = 0; k < 20; ++k ) { printf( "%d\n", k ) ; } } 

使用GCC 5.1或更高版本

 -xc -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)会创建所需的assemlby

 .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的生成的assembly没有影响

您使用的标志和参数不保证循环将完全展开。 GCC文档说明了您正在使用的-funroll-all-loops标志的以下内容:

打开完全循环剥离(即完全删除具有少量迭代次数的循环)

如果编译器确定给定代码段的迭代次数不是“小常量”(即数字太高),则它可能只进行部分剥离或展开,就像在此处所做的那样。 此外,您使用的param选项仅为最大值 ,但不强制完全展开小于设定值的循环。 换句话说,如果循环的迭代次数多于您设置的最大次数,那么循环将不会完全展开; 但反过来却不正确。

在进行优化时会考虑许多因素 。 这里代码中的瓶颈是对printf函数的调用,编译器在进行成本计算时可能会考虑到这一点,或者判断展开的指令大小开销太重要了。 正如你告诉它展开循环一样,它似乎确定最好的解决方案是使用10次展开和aa跳转换初始循环。

如果用其他东西替换printf ,编译器可能会以不同方式进行优化 。 例如,尝试用以下内容替换它:

 volatile int temp = k; 

具有此新代码片段的循环将在较新版本的GCC(以及较旧版本的GCC)上完全展开 。 请注意,volatile关键字只是一个技巧,因此编译器不会完全优化循环。

总而言之,据我所知,没有办法强制GCC的后期版本产生相同的输出。


作为旁注,从优化级别-O2开始并且没有任何其他编译器标志,最新版本的Clang完全展开您的循环。