在C ++中进行尾递归

时间:2015-05-26 23:28:38

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

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

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

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

  3. 更新

    编译器是gcc和MSVC。

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

2 个答案:

答案 0 :(得分:9)

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

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

我建议以一种方式重写它,使读者清楚地知道发生了什么,但不依赖编译器优化尾调用。虽然讨厌,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;
}

当然,这是一个简单的版本是平凡重写,以完全避免递归,而且可能甚至不应该手动实现,但是我在这里使用了特定的转换是一个你可以申请的任何< / em>函数,其中尾递归是可能的,你需要尾递归。评论应该确保读者仍然可以轻松发现正在发生的事情。

答案 1 :(得分:1)

使用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的优化级别进行编译时(无尾递归优化),会出现断言失败。