在c++中做尾递归
Doing tail recursion in C++
如果我做一个尾部调用递归(与for (;;)...break
循环相反),我的函数可以写得更简单。但是,我担心如果编译器不能对它进行优化,我会遇到性能问题,特别是因为它将由最终用户编译。
-
是否有办法告诉编译器"确保你优化了这个尾部调用,否则给我一个错误"(例如Scala支持这个)
-
如果编译器不能优化它,性能限制是什么?在不破坏堆栈的情况下,我可以运行多少次尾部调用?
更新:
编译器是gcc和MSVC。
一般来说,我预计会有十几个尾巴电话。但在极端情况下,可能有数千个。平台是典型的低端笔记本电脑(如酷睿i3或i5)。
不,没有办法告诉编译器需要尾部递归。一些编译器(据我所知没有)可能支持特定于实现的注释,但这需要用户使用特定的编译器。在某些模式下,其他一些编译器故意不支持尾部调用,因为它们可以通过不支持尾部调用来提供更好的调试体验。用户可能正在使用这样的编译器。
允许的递归深度高度依赖于程序、函数和实现,无法给出合理的数字。给定一个特定的平台,您可能可以确定默认的堆栈大小,调查该平台上一个特定编译器的帧大小,并进行简单的除法,以大致估计可以有多少个嵌套调用。
我建议以一种方式重写它,使读者清楚发生了什么,但不依赖于编译器优化尾部调用。虽然很讨厌,但是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;
}
当然,这是一个简单的版本,它被简单地重写以完全避免递归,甚至可能不应该手动实现,但是我在这里使用的特定转换可以应用于任何函数,其中可能存在尾递归并且需要尾递归。注释应该确保读者仍然可以很容易地发现发生了什么。
对于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
的优化级别(无尾递归优化)编译时,您将获得断言失败
相关文章:
- 通过递归进行因子分解
- 递归函数计算序列中的平方和(并输出过程)
- 使用递归的数组的最小值.这是怎么回事
- 递归列出所有目录中的C++与Python与Ruby的性能
- 将公共递归转换为尾递归,因为大型输入的堆栈溢出
- 尾递归函数未被 g++ 优化
- 尾递归,带有通过引用传递的向量
- 如何使O(n)的函数检查字母(上和下)和()+-*/到尾递归?
- 带有对象的尾递归
- 在turbo c++中,可以将一个普通递归函数转换为尾递归函数来对其进行优化
- 为什么当递归函数结果相乘时,g++ 仍然优化尾递归
- 在 C++ 中使用尾递归函数计算列表的总和
- 叮当无限尾递归优化
- constexpr函数求值可以做尾递归优化吗?
- 尾递归函数,用于计算数组中大于平均值的所有数字
- 将递归函数转换为尾递归函数
- 为什么下面的函数没有使用 Clang 生成尾递归?
- 已知的最有效的尾递归素数验证函数是什么?
- 在c++中做尾递归
- 模板元编程的尾递归性能