【问题标题】:Why does non-trivial destructor of return type prevent tail-call optimization?为什么返回类型的非平凡析构函数会阻止尾调用优化?
【发布时间】:2021-07-20 15:59:18
【问题描述】:

目前,在 C++ 编译器中,尾调用优化的规则之一是返回类型必须是可简单破坏的。 (基于分析 GCC、Clang 中继行为。MSVC 对任何非平凡类型都有问题)。

这个要求还有必要吗?由于 C++17 返回值优化是强制性的,因此该函数似乎仍然可以使用跟踪调用优化,即使返回类型很重要。这里有什么问题,阻止编译器这样做?

@edit,代码示例:

#include <string>

bool h();

std::string g() {
    std::string s1 = "a", s2 = "b";
    if (h()) return s1;
    else return s2;
}

std::string f() {
    return g();  // <= here I'd expect call-tail optimization due to RVO, since it is prvalue
}

https://godbolt.org/z/YYfMr6xdd

如果我对程序集的理解正确,应该可以将 f() 函数替换为 jump。

【问题讨论】:

  • 如果 理解正确f 可以替换为g(内联)(我猜非强制性)。而g 没有 NRVO(或 RVO),因为它有多个返回路径。
  • gcc 和 clang 都保存和恢复 $rdi -> $rax。也许他们不认为g 正确设置了 $rax?
  • 这与 trivially-destructible 没有直接关系,而是与 ABI 是否允许通过寄存器有关。如果类型可以简单地破坏但足够大,则尾调用也没有优化:godbolt.org/z/TbrM74j7b
  • @dyp,是的,这是我第一次听到与 (N)RVO 相关的琐碎破坏。我认为不平凡不是阻止 (N)RVO 的借口。隐含的想法是,如果构造首先被省略,则省略的破坏没有副作用或不重要。
  • 我几乎可以肯定,在寄存器中传递这些值取决于类型的微不足道的可破坏性。 Clang [[clang::musttail]] 属性返回一个错误,该类型必须是可简单破坏的,并且在许多可简单重定位/位复制可移动的提案中使用相同的措辞。经过一些研究,我在 Herb Sutter/Niall Douglas 的谈话中听到了同样的话,如果我理解正确的话,这种小值 ABI 的低效率是由非平凡的析构函数引起的。

标签: c++ c++17 tail-call-optimization return-value-optimization


【解决方案1】:

返回值优化是如果您要返回一个临时的(更准确地说:prvalue - 另见here on cppreference.com),例如在这种情况下:

std::vector<int> foo() {
  return std::vector<int>{1,2};
}

如果你要返回一个本地对象,这就是命名返回值优化(又名“复制省略”),这不是强制性的(但大部分时间仍然这样做),例如在这种情况下:

std::vector<int> foo() {
  std::vector<int> myVec{1,2};
  return myVec;
}

在这种情况下,允许编译器首先在foo()s 框架内构造myVec,然后复制返回,然后销毁。显然大多数编译器不会这样做。不过,当foo() 结束时,可能会发生破坏。

我猜这就是为什么需要微不足道的析构函数来进行尾调用优化。如果foo() 进行了尾调用优化,则标准必须考虑创建/按复制返回/销毁的生命周期,因此有一种方法可以(简单地)销毁返回的对象。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-12
    • 2012-06-03
    • 1970-01-01
    • 2013-10-16
    • 2010-09-11
    • 1970-01-01
    相关资源
    最近更新 更多