给出以下代码
#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)会创建所需的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&gt; = 5.1
答案 0 :(得分:4)
您使用的标志和参数不保证循环将完全展开。 GCC documentation说明了您正在使用的-funroll-all-loops
标志:
打开完整的环剥离(即用a完全去除环 小的常数迭代次数)
如果编译器确定给定代码段的迭代次数不是&#34;一个小常量&#34; (即数字太高),它可能只会像在这里那样进行部分剥离或展开。此外,您使用的param
选项仅为最大值,但不强制完全展开小于设定值的循环。换句话说,如果循环的迭代次数多于您设置的最大值,那么循环将不会完全展开;但反之则不然。
进行优化时会考虑许多因素。这里代码中的瓶颈是对printf
函数的调用,编译器在进行成本计算时可能会考虑到这一点,或者判断展开的指令大小开销太重要了。正如你告诉它展开循环一样,似乎确定最好的解决方案是用10次展开和跳转来转换初始循环。
如果您用其他内容替换printf
,编译器可能会以不同方式进行优化。例如,尝试使用以下内容替换它:
volatile int temp = k;
使用此新代码段的循环将在较新版本的GCC(以及较旧版本的GCC)上完全展开。请注意,volatile关键字只是一种技巧,因此编译器不会完全优化循环。
总而言之,据我所知,没有办法强制GCC的后期版本产生相同的输出。
作为旁注,从优化级别-O2
开始,没有任何其他编译器标志,最新版本的Clang完全展开您的循环。