浅谈尾递归的优化

浅谈尾调用和尾递归 这篇博文中,我谈了什么是尾递归以及编译器如何优化尾递归。这篇文章,咱来个具体的例子,通过汇编代码来看看优化和不优化的区别。

求阶乘的尾递归写法

// file_name : factorial.c
#include <stdio.h>

int factorial_tail(int n, int product_from_n)
{
    if (n == 1)
        return product_from_n;
    else
        return factorial_tail(n - 1, n * product_from_n);
}

int main(void)
{
    int result = factorial_tail(5,1);
    printf("%d\n",result);
}

实验思路是将上面的源码编译为普通版本和优化版本,通过对比2个版本的汇编代码来了解编译器如何优化尾递归。

编译为2个版本

gcc -S factorial.c -o factorial.s         #普通版本
gcc -S factorial.c -O2 -o factorial_O2.s  #优化版本

注:我的gcc版本是 4.8.4

对比文件

普通版本(未优化)

这里写图片描述

为了说明问题,我删除了一些伪指令,并加了些注释:

factorial_tail:
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp
    movl    %edi, -4(%rbp) // 传递参数 n
    movl    %esi, -8(%rbp) // 传递参数 product_from_n
    cmpl    $1, -4(%rbp)   //把参数n和1比较
    jne .L2                //不相等就跳转到.L2
    movl    -8(%rbp), %eax //相等的话 eax = product_from_n
    jmp .L3                //跳转到.L3
.L2:
    movl    -4(%rbp), %eax // eax = n
    imull   -8(%rbp), %eax // eax = product_from_n*n
    movl    -4(%rbp), %edx // edx = n
    subl    $1, %edx       // edx--;
    movl    %eax, %esi     //esi = eax = (product_from_n*n)
    movl    %edx, %edi     //edi = edx = (n-1)
    call    factorial_tail //递归调用
.L3:
    leave                  //leave和ret用于返回main函数
    ret

main:
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp
    movl    $1, %esi
    movl    $5, %edi
    call    factorial_tail

可以看到,编译器就是把C代码翻译成汇编了,没有做什么优化。递归依然是递归。

优化版本

这里写图片描述

我整理后的代码如下:

factorial_tail:
    cmpl    $1, %edi   //把edi(edi=参数n)和1比较
    movl    %esi, %eax // eax = esi =product_from_n
    je  .L2            //if(edi==1),则跳到.L2
.L3:
    imull   %edi, %eax  // eax = (edi * eax)= (n * product_from_n)
    subl    $1, %edi    // edi--; (即n--;)
    cmpl    $1, %edi    // edi和1相比较,即n和1相比较
    jne .L3             // if(edi != 1) 则跳转到.L3
.L2:
    rep ret   //返回主调函数,返回值在eax中

由此可见,编译器把递归转化成循环了。从第5行到第9行是典型的循环结构。

再多说一句,最后一行的rep指令是什么意思呢?我搜到的结果如下。

Some AMD processors have a one cycle pipeline stall when a “ret” instruction is the target of a conditional jump or is immediately preceded by a conditional jump. To avoid this, gcc generates “rep; ret” in those cases intead. Since the “rep” prefix means nothing with the “ret” instruction, this effectively becomes a two byte “ret” instruction, and that is sufficient to avoid the pipeline bubble.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值