【问题标题】:what is the difference between a recursive version of "find" and a not recursive one?“查找”的递归版本和非递归版本有什么区别?
【发布时间】:2017-01-08 15:30:17
【问题描述】:

Accelerated C++ Programming一书的第205页,find有以下两个实现

 template <class In, class X> In find(In begin, In end, const X& x)

我有兴趣了解以下两种实现在性能方面的差异(编译后是否实际上相同?)。

非递归

template <class In, class X> In find(In begin, In end, const X& x)
{
    while (begin != end && *begin != x)
        ++begin;
    return begin;
}

递归

template <class In, class X> In find(In begin, In end, const X& x)
{
    if (begin == end || *begin == x)
        return begin;
    begin++;
    return find(begin, end, x);
}

通过使用 Kerrek 建议的 Compiler Explorer,我得到了以下信息

非递归https://godbolt.org/g/waKUF2

递归https://godbolt.org/g/VKNnYZ

编译后好像一模一样? (如果我正确使用该工具.. 对不起,我对 C++ 很陌生)

【问题讨论】:

  • 你为什么不试试两者并告诉我们你发现了什么?
  • 尾递归可以带来不错的、干净的解决方案,但你最好希望你的编译器将其优化为循环而不是递归(如果通过了必要的优化标志,几乎所有编译器都会这样做,但是如果关闭优化(例如在调试版本中),可能不会)。否则,如果您搜索长列表,可能会出现堆栈溢出。
  • @Kerrek 我是来自 python 背景的 c++ 新手,我还不熟悉 cprofile 或编译过程,但你是对的,我当然应该自己尝试一下。刚刚看到你的例子谢谢你告诉我这个!
  • @YuhaoTigerHuang:我总是觉得人们想讨论一些他们实际上似乎不太感兴趣的事情很好奇。如果你确实关心 C++ 编译器如何生成代码,你会 a)对机器代码有一些兴趣(因此您甚至可以连贯地表达您的兴趣),并且 b) 了解如何检查编译器输出(例如 gcc -S 或配置文件)。否则,如果您对这些事情没有特别的兴趣,那么只需使用您更喜欢的代码,直到您真正遇到会迫使您产生兴趣的问题。

标签: c++ recursion iterator non-recursive accelerated-c++


【解决方案1】:

递归函数将在堆栈上添加额外的元素。这可能会导致堆栈溢出错误,具体取决于开始递归之前的堆栈状态和递归次数。

每个函数调用都会将数据推送到包含返回地址的堆栈中。这种情况一直持续到找到数据为止。这时候所有的函数都会开始返回上一个函数返回的值,直到我们最终回到原来调用find的函数。

为每个函数调用存储的确切数据量取决于调用约定和架构。将数据压入堆栈也会产生开销,这会使算法变慢,但这取决于算法。

这仅适用于未优化尾调用的递归。

【讨论】:

    【解决方案2】:

    在大多数情况下,递归速度较慢,并且占用了更多的堆栈。递归的主要优点是,对于像树遍历这样的问题,它使算法更容易或更“优雅”。

    查看一些比较: Recursion vs Iteration

    【讨论】:

    • 只有当编译器没有优化时,你的答案才是正确的。几乎所有编译器都可以优化尾递归(尽管可能需要您启用此类优化)。在优化的情况下,这里应该没有区别。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-08
    • 2018-05-06
    • 1970-01-01
    • 2016-02-01
    • 2014-09-13
    • 2013-11-16
    • 1970-01-01
    相关资源
    最近更新 更多