C++ 在C+中进行尾部递归+;
如果我执行尾部调用递归(与(;;)…break循环相反),那么我的函数可以编写得更简单。然而,如果编译器未能对其进行优化,我担心会出现性能问题,特别是因为最终用户将对其进行编译C++ 在C+中进行尾部递归+;,c++,optimization,recursion,tail-call-optimization,C++,Optimization,Recursion,Tail Call Optimization,如果我执行尾部调用递归(与(;;)…break循环相反),那么我的函数可以编写得更简单。然而,如果编译器未能对其进行优化,我担心会出现性能问题,特别是因为最终用户将对其进行编译 有没有办法告诉编译器“确保优化这个尾部调用,否则给我一个错误”(例如Scala支持这个) 如果编译器无法对其进行优化,那么性能限制是什么?我可以期望在不破坏堆栈的情况下运行多少个尾部调用 更新: 编译器是gcc和MSVC 通常情况下,我预计会有十几个跟踪电话。但极端的情况可能有数千人。平台是典型的低端笔记本电脑(如Co
更新: 编译器是gcc和MSVC
通常情况下,我预计会有十几个跟踪电话。但极端的情况可能有数千人。平台是典型的低端笔记本电脑(如Core i3或i5)。不,没有办法告诉编译器需要尾部递归。有些编译器(据我所知没有)可能支持特定于实现的注释,但这需要用户使用特定的编译器。在某些模式下,其他一些编译器故意不支持尾部调用,因为它们可以通过不支持尾部调用提供更好的调试体验。用户可能正在使用这样的编译器 允许的递归深度高度依赖于程序、函数和实现,不能给出合理的数值。给定一个特定的平台,您可能可以确定默认堆栈大小,调查该平台上某个特定编译器的帧大小,并进行简单的除法以粗略估计可以进行多少嵌套调用 我建议以一种让读者清楚发生了什么的方式重写它,但不要依赖于优化尾部调用的编译器。尽管不喜欢,但是
goto
语句对此非常有用
以一个简单的尾部递归位计数函数为例:
int bitcount(unsigned int n, int acc = 0) {
if (n == 0)
return acc;
return bitcount(n >> 1, acc + (n & 1));
}
#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;
}
它可以简单地重写为
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,您可以使用以下函数添加运行时检查:
int bitcount(unsigned int n, int acc = 0) {
if (n == 0)
return acc;
return bitcount(n >> 1, acc + (n & 1));
}
#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;
}
#包括
#包括
#包括
开始时的尺寸;
大小\u t堆栈\u帧()
{
void*数组[16];
大小=回溯(数组,16);
//标准::cout 1,acc+(n&1));
}
int main()
{
开始=堆栈_帧();
std::cout您大概需要多少尾部调用?您针对的编译器是什么?如果它确实是尾部递归的,并且您需要确定编译器将优化递归,那么您自己将其编码为循环应该很简单。请参阅查找尾部递归函数可能无法优化的其他原因。示例也可以使用while循环轻松编写,使其更加清晰,但很高兴看到,在适用的情况下,并非所有人都反对goto。@SamiKuhmonen是的,这就是我所说的“简单重写以完全避免递归”。谢谢。)