c - 循环展开优化,这是如何工作的

标签 c assembly

考虑这个 C 代码:

int sum=0;
for(int i=0;i<5;i++)
    sum+=i;

这可以在(伪)汇编中以这种方式翻译(没有循环展开):

% pseudo-code assembly
ADDI $R10, #0   % sum
ADDI $R11, #0   % i
LOOP:
ADD $R10, $R11
ADDI $R11, #1
BNE $R11, #5 LOOP

所以我的第一个问题是如何使用循环展开在这两种方式之间转换这段代码:

1)

ADDI $R10, #0
ADDI $R10, #0
ADDI $R10, #1
ADDI $R10, #2
ADDI $R10, #3
ADDI $R10, #4

2)

   ADD $R10, #10

编译器是否能够优化代码并直接知道它必须加 10 而无需执行所有求和?

还有,有没有可能用分支指令阻塞流水线?我必须这样写吗:

% pseudo-code assembly
ADDI $R10, #0   % sum
ADDI $R11, #0   % i
LOOP:
ADD $R10, $R11
ADDI $R11, #1
NOP   % is this necessary to avoid the pipeline blocking?
NOP
NOP
NOP
BNE $R11, #5 LOOP

为了避免 fetch-decode-exe-mem-write back 循环被分支打断?

最佳答案

这更多是为了展示编译器的能力,而不是每个编译器都会做的事情。来源:

#include <stdio.h>

int main(void)
{
    int i, sum = 0;

    for(i=0; i<5; i++) {
        sum+=i;
    }

    printf("%d\n", sum);
    return 0;
}

请注意我添加的 printf。如果不使用该变量,编译器将优化整个循环。

使用 -O0 编译(无优化)

gcc -Wall -O0 -S -c lala.c:

.L3:
    movl    -8(%rbp), %eax
    addl    %eax, -4(%rbp)
    addl    $1, -8(%rbp)
.L2:
    cmpl    $4, -8(%rbp)
    jle .L3

循环以“愚蠢”的方式发生,-8(%rbp) 是变量 i

使用 -O1(优化级别 1)编译

gcc -Wall -O1 -S -c lala.c:

movl    $10, %edx

循环已被完全删除并替换为等效值。


在展开时,编译器查看会发生多少次迭代,并尝试通过执行较少的迭代来展开。例如,循环体可能被复制两次,这将导致分支数减半。 C中的这种情况:

int i = 0, sum = 0;

sum += i;
i++;

for(; i<5;i++) {
    sum+=i;
    i++;
    sum+=i;
}

请注意,必须从循环中提取一次迭代。这是因为 5 是奇数,所以不能简单地通过复制内容来减半工作量。在这种情况下,循环只会进入两次。 -O0生成的汇编代码:

    movl    -8(%rbp), %eax
    addl    %eax, -4(%rbp)
    addl    $1, -8(%rbp)
    jmp .L2
.L3:
    movl    -8(%rbp), %eax
    addl    %eax, -4(%rbp)
    addl    $1, -8(%rbp)
    movl    -8(%rbp), %eax
    addl    %eax, -4(%rbp)
    addl    $1, -8(%rbp)
.L2:
    cmpl    $4, -8(%rbp)

在 C 中完全展开:

for(i=0; i<5;i++) {
    sum+=i;
    i++;
    sum+=i;
    i++;
    sum+=i;
    i++;
    sum+=i;
    i++;
    sum+=i;
}

这次循环实际上只进入了一次。使用 -O0 生成的程序集:

.L3:
    movl    -8(%rbp), %eax
    addl    %eax, -4(%rbp)
    addl    $1, -8(%rbp)
    movl    -8(%rbp), %eax
    addl    %eax, -4(%rbp)
    addl    $1, -8(%rbp)
    movl    -8(%rbp), %eax
    addl    %eax, -4(%rbp)
    addl    $1, -8(%rbp)
    movl    -8(%rbp), %eax
    addl    %eax, -4(%rbp)
    addl    $1, -8(%rbp)
    movl    -8(%rbp), %eax
    addl    %eax, -4(%rbp)
    addl    $1, -8(%rbp)
.L2:
    cmpl    $4, -8(%rbp)
    jle .L3

关于c - 循环展开优化,这是如何工作的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10301372/

相关文章:

c - 程序运行但要求输入两次

c - argp 和 getopt 有什么区别?

c - 程序集,多个参数 -m32/linux(与 C 中的 stdarg 相同)

assembly - 炸弹实验室阶段_4

c - 打开 Watcom 内联汇编 SEG 和 OFFSET 运算符

c# - 构建一个汇编程序

c - 以char为索引的函数指针跳转表

使用 while 的 C 程序 - 如果 counter>8 则出现奇怪的计数器错误

c - 为什么这些变量前面使用&符号?

c++ - 优化稀疏下三角线性系统的反向求解