【问题标题】:Doing tail recursion in C++在 C++ 中进行尾递归
【发布时间】:2015-05-26 23:28:37
【问题描述】:

如果我进行尾调用递归(而不是for (;;)...break 循环),我的函数可以更简单地编写。但是我担心如果编译器无法优化它,我会遇到性能问题,特别是因为它将由最终用户编译。

  1. 有没有办法告诉编译器“确保优化这个尾调用,否则给我一个错误”(例如 Scala 支持这个)

  2. 如果编译器无法优化它,性能限制是多少?在不破坏堆栈的情况下,我预计可以运行多少次尾调用?


更新:

编译器是 gcc 和 MSVC。

通常情况下,我预计会有十几个尾随。但极端情况可能有数千人。平台是典型的低端笔记本电脑(例如 Core i3 或 i5)。

【问题讨论】:

  • 您预计大概有多少尾电话?你的目标是什么编译器?
  • 如果它真的是尾递归,并且您需要确定编译器会优化掉递归,那么自己将其编码为循环应该很简单。
  • 请参阅this answer 以查找可能无法优化尾递归函数的其他一些原因。

标签: c++ optimization recursion tail-call-optimization


【解决方案1】:

不,没有办法告诉编译器需要尾递归。一些编译器(我不知道)可能支持特定于实现的注释,但这需要用户使用该特定的编译器。其他一些编译器,在某些模式下,故意从不支持尾调用,因为它们可以通过不支持尾调用来提供更好的调试体验。用户可能正在使用这样的编译器。

允许的递归深度高度依赖于程序、函数和实现,并且无法给出合理的数字。给定一个特定平台,您可能可以确定默认堆栈大小,调查该平台上特定编译器的帧大小,并进行简单的除法以粗略估计您可以拥有多少嵌套调用。

我建议以一种让读者清楚正在发生的事情的方式重写它,但不依赖于编译器优化尾调用。尽管讨厌,goto 语句对此非常有用。

取一个简单的尾递归位计数函数:

int bitcount(unsigned int n, int acc = 0) {
  if (n == 0)
    return acc;

  return bitcount(n >> 1, acc + (n & 1));
}

可以简单地改写为

int bitcount(unsigned int n, int acc = 0) {
tail_recurse:
  if (n == 0)
    return acc;

  // tail return bitcount(n >> 1, acc + (n & 1));
  acc += n & 1;
  n = n >> 1;
  goto tail_recurse;
}

当然,这是一个简单的版本,它被简单地重写以完全避免递归,甚至可能不应该手动实现,但我在这里使用的具体转换是你可以应用于 any 可以进行尾递归以及需要尾递归的函数。评论应该确保读者仍然可以轻松地发现发生了什么。

【讨论】:

  • 这个例子也可以很容易地用while循环来写,让它更清晰,但很高兴看到并不是所有的都反对goto。
  • @SamiKuhmonen 是的,这就是我所说的“简单地重写以完全避免递归”的意思。谢谢。 :)
【解决方案2】:

使用 GCC,您可以使用 backtrace() 函数添加运行时检查:

#include <cassert>
#include <iostream>
#include <execinfo.h>

size_t start;

size_t stack_frames()
{
  void *array[16];
  size_t size = backtrace(array, 16);

  // std::cout << "Obtained " << size << " stack frames.\n";
  return size;
}

bool missing_tail()
{
  return stack_frames() > start + 2;
}

int bitcount(unsigned int n, int acc = 0)
{
  assert(!missing_tail());

  if (n == 0)
    return acc;

  return bitcount(n >> 1, acc + (n & 1));
}

int main()
{
  start = stack_frames();

  std::cout << bitcount(10) << '\n';

  return 0;
}

当使用低于 -O2 的优化级别(无尾递归优化)编译时,您会遇到断言失败。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-09-20
    • 2012-05-16
    • 1970-01-01
    • 2011-02-11
    • 1970-01-01
    • 1970-01-01
    • 2016-03-21
    • 1970-01-01
    相关资源
    最近更新 更多