GCC 5.1 循环展开
GCC 5.1 Loop unrolling
给定以下代码
#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 , %ebx
call printf
cmpl , %ebx
jne .L2
xorl %eax, %eax
popq %rbx
ret
但是使用旧版本的 GCC(例如 4.9.2)会创建所需的 assemlby
.LC0:
.string "%d\n"
main:
subq , %rsp
xorl %edx, %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
xorl %eax, %eax
addq , %rsp
ret
有没有办法强制 GCC 的更高版本产生相同的输出?
使用https://godbolt.org/g/D1AR6i生成程序集
编辑: 没有重复的问题,因为使用更高版本的 GCC 完全展开循环的问题尚未解决。传递 --param max-completely-peeled-insns=1000 --param max-completely-peel-times=10000
对使用 GCC >= 5.1
生成的程序集没有影响
您使用的标志和参数不保证循环将完全展开。 GCC documentation 声明以下关于您正在使用的 -funroll-all-loops
标志:
turns on complete loop peeling (i.e. complete removal of loops with a
small constant number of iterations)
如果编译器确定给定代码段的迭代次数不是 "a small constant"(即数字太高),它可能只会像此处所做的那样进行部分剥离或展开。此外,您使用的 param
选项只是 最大值 ,但不要强制完全展开小于设置值的循环。换句话说,如果一个循环的迭代次数超过了您设置的最大值,那么该循环将不会完全展开;但反之则不然。
许多因素在进行优化时被考虑在内。这里代码中的瓶颈是对 printf
函数的调用,编译器在计算成本时可能会考虑到这一点,或者判断展开的指令大小开销太重要了。当你仍然告诉它展开循环时,它似乎确定最好的解决方案是用 10 次展开和一次跳跃来转换初始循环。
如果您将 printf
替换为其他内容,编译器可能会进行不同的优化。例如,尝试将其替换为以下内容:
volatile int temp = k;
这个新代码片段的循环将完全展开在较新版本的 GCC(以及旧版本)上。请注意,volatile 关键字只是一个技巧,因此编译器不会完全优化循环。
综上所述,据我所知,没有办法强制 GCC 的更高版本产生相同的输出。
附带说明一下,从优化级别 -O2
开始,在没有任何额外的编译器标志的情况下,最新版本的 Clang 会完全展开您的循环。
给定以下代码
#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 , %ebx
call printf
cmpl , %ebx
jne .L2
xorl %eax, %eax
popq %rbx
ret
但是使用旧版本的 GCC(例如 4.9.2)会创建所需的 assemlby
.LC0:
.string "%d\n"
main:
subq , %rsp
xorl %edx, %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
movl , %edx
movl $.LC0, %esi
movl , %edi
xorl %eax, %eax
call __printf_chk
xorl %eax, %eax
addq , %rsp
ret
有没有办法强制 GCC 的更高版本产生相同的输出?
使用https://godbolt.org/g/D1AR6i生成程序集
编辑: 没有重复的问题,因为使用更高版本的 GCC 完全展开循环的问题尚未解决。传递 --param max-completely-peeled-insns=1000 --param max-completely-peel-times=10000
对使用 GCC >= 5.1
您使用的标志和参数不保证循环将完全展开。 GCC documentation 声明以下关于您正在使用的 -funroll-all-loops
标志:
turns on complete loop peeling (i.e. complete removal of loops with a small constant number of iterations)
如果编译器确定给定代码段的迭代次数不是 "a small constant"(即数字太高),它可能只会像此处所做的那样进行部分剥离或展开。此外,您使用的 param
选项只是 最大值 ,但不要强制完全展开小于设置值的循环。换句话说,如果一个循环的迭代次数超过了您设置的最大值,那么该循环将不会完全展开;但反之则不然。
许多因素在进行优化时被考虑在内。这里代码中的瓶颈是对 printf
函数的调用,编译器在计算成本时可能会考虑到这一点,或者判断展开的指令大小开销太重要了。当你仍然告诉它展开循环时,它似乎确定最好的解决方案是用 10 次展开和一次跳跃来转换初始循环。
如果您将 printf
替换为其他内容,编译器可能会进行不同的优化。例如,尝试将其替换为以下内容:
volatile int temp = k;
这个新代码片段的循环将完全展开在较新版本的 GCC(以及旧版本)上。请注意,volatile 关键字只是一个技巧,因此编译器不会完全优化循环。
综上所述,据我所知,没有办法强制 GCC 的更高版本产生相同的输出。
附带说明一下,从优化级别 -O2
开始,在没有任何额外的编译器标志的情况下,最新版本的 Clang 会完全展开您的循环。