关闭

探索C#之尾递归编译器优化

发表于:2015-9-07 09:12

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:蘑菇先生    来源:51Testing软件测试网采编

  递归运用
  一个函数直接或间接的调用自身,这个函数即可叫做递归函数。
  递归主要功能是把问题转换成较小规模的子问题,以子问题的解去逐渐逼近最终结果。
  递归最重要的是边界条件,这个边界是整个递归的终止条件。
  static int RecFact(int x)
  {
  if (x == 0)
  return 1;
  return x * RecFact(x - 1);
  }
  RecFact(10);
  上面是个经典阶乘函数的实现。这里分2步:
  转换,把10的阶乘转化成10*9!,10(9*8!)….每次转换规模就变的更小。
  逼近,转换到最小规模时0!,求解1。开始逆向合并逐渐逼近到10,得出解。
  这里的x==0就是我们的边界条件(即终止条件),也有的依赖外部变量为边界。
  如果一个递归函数没有边界,也就无法停止(无限循环至内存溢出),当然这样也没什么意义。
  RecFact调用堆栈:
  常见使用场景:
  阶乘/斐波那契数列/汉诺塔
  遍历硬盘文件
  InnerExceptions异常扑捉(exception.InnerException==null)
  尾递归优化
  当边界不明确的时候,递归就很容易出现溢出问题。
  在阶乘过程中,堆栈需要保存每次(RecFact)调用的返回地址及当时所有的局部变量状态,期间堆栈空间是无法释放的(即容易出现溢出)。
  为了优化堆栈占用问题,从而提出尾递归优化的办法。
  static void TailRecursion(int x)
  {
  Console.WriteLine(x);
  if (x == 10)
  return;
  TailRecursion(x + 1);
  }
  TailRecursion(0);
  使用尾递归堆栈可以不用保存上次的函数返回地址/各种状态值,而方法遗留在堆栈上的数据完全可以释放掉,这是尾递归优化的核心思想。
  由于尾递归期间,堆栈是可以释放/再利用的,也就解决递归过深而引起的溢出问题,这也是尾递归的优势所在。
  编译器优化
  尾递归优化,看起来是蛮美好的,但在net中却有点乱糟糟的感觉。
  Net在C#语言中是JIT编译成汇编时进行优化的。
  Net在IL上,有个特殊指令tail去实现尾递归优化的(F#中)。
  我们执行 TailRecursion(0)(x==1000000) 得出如下结论:
  C#/64位/Release是有JIT编译器进行尾递归优化的(非C#编译器优化)。
  C#/32位或C#/Debug模式中JIT是不进行优化的。
  F#在优化尾递归也分2种情况:
  1、 简单的尾递归优化成while循环,如下:
  let rec TailRecursion(x) =
  if (x = 1000) then true
  else TailRecursion(x + 1)
  (方便演示C#呈现)编译器优化成:
  public static bool TailRecursion(int x)
  {
  while (x != 0x3e8)
  {
  x++;
  }
  return true;
  }
21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号