`
mryufeng
  • 浏览: 982277 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

递归和尾递归汇编层面的差别

阅读更多
erlang的标准用法是尽可能的把函数调用写出尾递归的方式,实际的结果靠参数传递。尾递归的方式对进程的堆栈使用很小, 只要一个WORD, 但是非尾递归就要看递归的层数,如果数量很大,会把堆栈撑的很大。我们在汇编一级看下如何实现的:

root@nd-desktop:~# cat tailcall.erl 
-module(tailcall).
-export([start/1]).
-compile(export_all).

start(N)->
    X = loop(N),
    Y = tail_loop(N),
    X = Y,
    done.

loop(0)->
    1;
loop(N) when N >0 ->
   N * loop(N-1).


tail_loop(N)->
    tail_loop2(N, 1).
tail_loop2(0, R)->
    R;
tail_loop2(N, R) ->
    tail_loop2(N-1, N *R).


root@nd-desktop:~# erlc +"'S'" tailcall.erl
root@nd-desktop:~# cat tailcall.S         
{module, tailcall}.  %% version = 0

{exports, [{loop,1},
           {module_info,0},
           {module_info,1},
           {start,1},
           {tail_loop,1},
           {tail_loop2,2}]}.

{attributes, []}.

{labels, 16}.

{function, start, 1, 2}.
  {label,1}.
    {func_info,{atom,tailcall},{atom,start},1}.
  {label,2}.
    {allocate,1,1}.
    {move,{x,0},{y,0}}.
    {call,1,{f,5}}.
    {move,{x,0},{x,1}}.
    {move,{y,0},{x,0}}.
    {move,{x,1},{y,0}}.
    {call,1,{f,8}}.
    {test,is_eq_exact,{f,3},[{x,0},{y,0}]}.
    {move,{atom,done},{x,0}}.
    {deallocate,1}.
    return.
  {label,3}.
    {badmatch,{x,0}}.


{function, loop, 1, 5}.
  {label,4}.
    {func_info,{atom,tailcall},{atom,loop},1}.
  {label,5}.
    {test,is_eq_exact,{f,6},[{x,0},{integer,0}]}.
    {move,{integer,1},{x,0}}.
    return.
  {label,6}.
    {test,is_lt,{f,4},[{integer,0},{x,0}]}.
    {allocate_zero,1,1}.
%% 主要是这条 allocate_zero 这个指令 把当前的调用栈保存 同时分配个参数空间
%% 对应 AllocateZero 这个操作

    {gc_bif,'-',{f,0},1,[{x,0},{integer,1}],{x,1}}.
    {move,{x,0},{y,0}}.
    {move,{x,1},{x,0}}.
    {call,1,{f,5}}.
%% call opcode, 再次调用 堆栈层数+1
    {gc_bif,'*',{f,0},1,[{y,0},{x,0}],{x,0}}.
    {deallocate,1}.
%% 恢复调用栈
%% 对应 deallocate_I操作

    return.


{function, tail_loop, 1, 8}.
  {label,7}.
    {func_info,{atom,tailcall},{atom,tail_loop},1}.
  {label,8}.
    {move,{integer,1},{x,1}}.
    {call_only,2,{f,10}}.


{function, tail_loop2, 2, 10}.
  {label,9}.
    {func_info,{atom,tailcall},{atom,tail_loop2},2}.
  {label,10}.
    {test,is_eq_exact,{f,11},[{x,0},{integer,0}]}.
    {move,{x,1},{x,0}}.
    return.
  {label,11}.
    {gc_bif,'-',{f,0},2,[{x,0},{integer,1}],{x,2}}.
    {gc_bif,'*',{f,0},3,[{x,0},{x,1}],{x,1}}.
    {move,{x,2},{x,0}}.
    {call_only,2,{f,10}}.
%% 调用 call_only opcode 没有建立堆栈的过程


{function, module_info, 0, 13}.
  {label,12}.
    {func_info,{atom,tailcall},{atom,module_info},0}.
  {label,13}.
    {move,{atom,tailcall},{x,0}}.
    {call_ext_only,1,{extfunc,erlang,get_module_info,1}}.


{function, module_info, 1, 15}.
  {label,14}.
    {func_info,{atom,tailcall},{atom,module_info},1}.
  {label,15}.
    {move,{x,0},{x,1}}.
    {move,{atom,tailcall},{x,0}}.
    {call_ext_only,2,{extfunc,erlang,get_module_info,2}}.

我们在beam_emu.c中可以看到:

#define AllocateZero(Ns, Live)             \                            
do { Eterm* ptr;                          \                            
      int i = (Ns);                        \
      AH(i, 0, Live);                      \                            
      for (ptr = E + i; ptr > E; ptr--) {  \
         make_blank(*ptr);                 \
     }                                     \
  } while (0)

#define AH(StackNeed, HeapNeed, M) \
  do { \
     int needed; \
     needed = (StackNeed) + 1; \
     if (E - HTOP < (needed + (HeapNeed))) { \
           SWAPOUT; \
           reg[0] = r(0); \
           PROCESS_MAIN_CHK_LOCKS(c_p); \
           FCALLS -= erts_garbage_collect(c_p, needed + (HeapNeed), reg, (M)); \
           PROCESS_MAIN_CHK_LOCKS(c_p); \
           r(0) = reg[0]; \
           SWAPIN; \
     } \
     E -= needed; \
     SAVE_CP(E); \
  } while (0)

#define SAVE_CP(X)                              \
   do {                                         \
      *(X) = make_cp(c_p->cp);                  \
      c_p->cp = 0;                              \
   } while(0)

#define RESTORE_CP(X)           SET_CP(c_p, cp_val(*(X)))

#define D(N)             \
     RESTORE_CP(E);      \
     E += (N) + 1;

OpCase(deallocate_I): {
     Eterm* next;

     PreFetch(1, next);
     D(Arg(0));
     NextPF(1, next);
}

结论是:
尾递归的效率要比递归的好很多, 包括堆栈的使用和参数的分配,就是代码写起来不那么直观。
分享到:
评论
11 楼 EQualizer 2009-10-01  
The Book 写道
编译尾递归的函数可以使一系列语句最后的一个函数调用可以被替换为一个简单的跳转指令,指向被调用函数的开头。这就意味着一个尾递归的函数可以无限循环而不需要消耗栈空间。
10 楼 mryufeng 2009-09-25  
编译器可以做些 明显的可以转换的 但是人工又懒的去转的那种 就可以了。 我很喜欢erlang factory推荐的那个tidier 很强大,有人工的智能来帮你做erlang的最佳实践,不知道什么时候可以release给大众。
9 楼 t0uch 2009-09-25  
Trustno1 写道
weiqingfei 写道
编译器能不能考虑把非尾递归优化为尾递归?

noway

并不是所有的递归都能转化成尾递归吧
8 楼 mryufeng 2009-09-25  
尾递归和递归在erlang vm里占用的空间不同 一个使用堆 一个使用栈 但是都在一个进程的空间里, 所以效率差不多。 只是使用heap会很灵活 效率更高些些。
7 楼 argan 2009-09-25  
受教了,新版本的erlang在有些情况下,非尾递归的性能不一定差的,所以有时候不写尾递归也无所谓
6 楼 mryufeng 2009-09-25  
大部分是尾递归呀  部分的数据集很小 也无所谓的
5 楼 Trustno1 2009-09-25  
xvyu 写道
但是erlang的很多标准库都没有用尾递归,比方lists,这是为什么呢

good enough.
4 楼 xvyu 2009-09-25  
但是erlang的很多标准库都没有用尾递归,比方lists,这是为什么呢
3 楼 litaocheng 2009-09-25  
开始只是知道要怎么做,在老大的剖析下知道了为什么这么做...
继续啊!
2 楼 Trustno1 2009-09-25  
weiqingfei 写道
编译器能不能考虑把非尾递归优化为尾递归?

noway
1 楼 weiqingfei 2009-09-25  
编译器能不能考虑把非尾递归优化为尾递归?

相关推荐

    汇编语言的递归程序

    在高级语言中,递归的使用广泛且直观,但在低级语言如汇编语言中,递归的实现则需要更深入的理解和技巧。本文将详细探讨如何在汇编语言中编写递归程序,以求解一个整数的阶乘为例,展示递归在汇编中的具体应用。 ...

    C#中的尾递归与Continuation详解

    这几天恰好和朋友谈起了递归,忽然发现不少朋友对于“尾递归”的概念比较模糊,网上搜索一番也没有发现讲解地完整详细的资料,于是写了这么一篇文章,权当一次互联网资料的补充。:P 递归与尾递归 关于递归操作,相信...

    详解python使用递归、尾递归、循环三种方式实现斐波那契数列

    斐波那契数列是一个经典的计算机科学问题,它的定义是这样的:第一项和第二项分别为0和1,从第三项开始,每一项都等于前两项之和。数学公式表示为 F(n) = F(n-...这进一步证明了循环和尾递归在处理此类问题上的优越性。

    插入排序递归非递归汇编写法

    在本实验报告中,我们将使用MIPS汇编语言来实现插入排序,包括递归和非递归版本。 递归版本 在递归版本中,我们使用了递归函数sort来实现插入排序。sort函数将数组的首地址和要排序的数据个数作为参数。函数首先...

    Cholesky分解递归算法与改进[汇编].pdf

    Cholesky分解递归算法与改进[汇编].pdf

    汇编语言,计算斐波那契数列的前22项,斐波那契数列,分别用两种方法:递归调用,普通循环加法

    在本文中,我们将深入探讨如何使用汇编语言来计算斐波那契数列的前22项,并且对比两种不同的实现方法:递归调用和普通循环加法。首先,让我们了解一下斐波那契数列的基本概念。 斐波那契数列是一个数学上的序列,...

    Fibonacci数列的四种解法:递归、存储优化的递归、自下而上的递归(迭代法)、尾递归

    fibonacci数列的各种解法,递归、存储优化的递归、自下而上的递归(迭代法)、尾递归。其中分析内容请移步我的博客、

    Q1069907.zip 汇编递归求斐波那契数列前N项 以及 TASM TurboC等工具

    这个压缩包"Q1069907.zip"似乎包含了用汇编语言实现递归计算斐波那契数列前N项的代码,以及可能的编译器和工具,如TASM(Turbo Assembler)和TurboC。 斐波那契数列是由0和1开始,后面的每一项数字都是前面两项数字...

    用递归调的方法计算年龄 汇编

    递归调用算年龄 汇编 成绩为优 课设参考的而好东西

    关于尾递归和Cooper变换

    通过以上分析可以看出,尾递归与 Cooper 变换都是计算机科学领域内非常重要的概念和技术。尾递归可以帮助优化递归算法,提高程序效率;而 Cooper 变换则提供了一种方法,可以将非尾递归的递归函数转换为尾递归的形式...

    关于尾递归的使用详解

    尾递归是一种高效的递归形式,尤其在处理深度递归和大量数据时,可以有效避免栈溢出。然而,是否能从中受益取决于所使用的编程语言及其对尾递归的支持程度。在实际应用中,了解语言特性和编译器优化策略对于充分利用...

    尾递归.cpp

    /*int f(int n,int a1,int a2) { if(n) return a1; else return f(n - 1, a2, a1 + a2); }*/

    合并排序递归和非递归算法

    在实际编程中,还可以考虑其他优化策略,如使用自底向上的合并排序(先处理较小的子数组,减少不必要的合并操作),或者采用尾递归优化来减少栈空间的使用。这些方法可以在保持算法效率的同时,优化资源的利用。通过...

    Python递归及尾递归优化操作实例分析

    Python递归是编程中一种强大的技术,它允许函数在解决问题时自我调用。递归的基本原理是通过将复杂问题分解为相同或相似的子问题来解决。...在实际编程中,理解递归和尾递归的概念对于编写高效和优雅的代码至关重要。

    4.5递归算法与递归程序[汇编].pdf

    14. **递归程序设计**:编写递归程序时,需要考虑递归函数的基线条件(base case)和递归情况,以及如何在每次递归调用中向解决方案靠近。 15. **效率优化**:对于递归算法,可以使用记忆化技术(如动态规划)来...

    C#函数式编程中的递归调用之尾递归详解

    普通递归和尾递归如果仅仅只是从代码的角度出发来看,我们可能发现不了他的特点,所以笔者利用两张堆栈上的图来展示具体的差距在哪,首先我们来看看普通的递归调用的情况,如下图1.1所示: 假设这里执行的函数是...

    递归算法与非递归转化

    直接转换法通常用来消除尾递归和单向递归,将递归结构用循环结构来替代。尾递归是指在递归算法中,递归调用语句只有一个,而且是处在算法的最后。当递归调用返回时,是返回到上一层的调用点,而不是返回到初始调用点...

Global site tag (gtag.js) - Google Analytics