在C++中样板"冷/never_inline"错误处理技术的最佳方法是什么?

What is the best way to boilerplate the "cold/never_inline" error handling technique in C++?

本文关键字:quot 技术 处理 错误 最佳 inline 是什么 方法 never C++      更新时间:2023-10-16

在本文中,描述了一种在 gcc 中将错误代码移出行的技术,以帮助尽可能优化大小的热路径。 这方面的一个例子是:

#define unlikely(x)  __builtin_expect (!!(x), 0)
bool testForTerriblyUnlikelyEdgeCase() {
//test for error condition here
}
void example() {
if (unlikely(testForTerriblyUnlikelyEdgeCase())) {
[&]() __attribute__((noinline,cold)) {
//error handling code here
}();
}
}

这是很棒的技术,但需要绝对大量的样板。 包装它以尽可能减少样板的最佳方法是什么? 理想情况下C++14兼容,允许GCC特定的功能。

奖励问题:由于 lambda 被明确标记为冷,if 语句中的不太可能(...)是否是多余的?

我想到了两种方法:

  • 函数包装器方法,以及
  • 基于宏观的方法

函数包装器

就设计而言,最好的方法是将此功能包装到封装属性和处理的函数中。为此,您需要传递一个要作为冷处理程序调用的回调(在本例中为 lambda)。它看起来像这样简单(使用 C++11 属性而不是__attribute__语法):

template <typename Fn>
[[gnu::cold]] [[gnu::noinline]]
void cold_path(Fn&& fn)
{
std::forward<Fn>(fn)();
}

您还可以扩展此解决方案以利用要测试的条件,例如:

template <typename Expr, typename Fn>
void cold_path_if(Expr&& expr, Fn&& fn)
{
if (unlikely(std::forward<Expr>(expr))) {
cold_path(std::forward<Fn>(fn));
}
}

综上所述,您有:

void example() {
cold_path_if(testForTerriblyUnlikelyEdgeCase(), [&]{
std::cerr << "Oh no, something went wrong" << std::endl;
std::abort();
});
}

下面是它在编译器资源管理器上的外观。

基于宏观的方法

如果不需要传递显式 lambda,那么唯一想到的替代方法是为您创建 lambda 的基于宏的解决方案。为此,您需要一个将立即调用 lambda 的实用程序,以便您只需要定义函数的主体:

// A type implicitly convertible to any function type, used to make the 
// macro below not require '()' to invoke the lambda
namespace detail {
class invoker
{
public:
template <typename Fn>
/* IMPLICIT */ invoker(Fn&& fn){ fn(); }
};
}

这是作为可从函数隐式转换的类完成的,以便您可以编写类似detail::invoker foo = []{ ... }的代码。然后,我们要将定义的第一部分带到捕获,并将其包装到宏中。

为此,我们需要为变量提供一个唯一的名称,否则,如果多个处理程序在同一范围内,我们可能会隐藏或重新定义变量。为了解决此问题,我将__COUNTER__宏附加到名称中;但这是非标准的:

#define COLD_HANDLER ::detail::invoker some_unique_name ## __COUNTER__ = [&]() __attribute__((noinline,cold))

这只是将自动调用程序的创建包装到定义 lambda 为止,因此您需要做的就是编写COLD_HANDLER { ... }

现在的用法如下所示:

void example() {
if (unlikely(testForTerriblyUnlikelyEdgeCase())) {
COLD_HANDLER {
//error handling code here
};
}
}

下面是编译器资源管理器的示例


这两种方法都会产生与直接使用 lambda 相同的程序集,只是标签和名称不同。(注意:此比较使用std::fprintf而不是stds::cerr因此组件更小且更易于比较)


奖励问题:由于 lambda 被明确标记为冷,if 语句中的不太可能 (...) 是否是多余的?

阅读 GCC 的文档以了解__attribute__((cold))似乎表明所有导致冷函数的分支都标记为unlikely,这应该使unlikely宏的使用变得多余和不必要——尽管拥有它应该没有什么坏处。

在属性页面中:

cold 属性用于通知编译器不太可能执行函数。该函数针对大小而不是速度进行了优化,并且在许多目标上,它被放置在文本部分的特殊子部分中,因此所有冷函数看起来都很近,从而改善了程序非冷部分的代码局部性。导致代码中冷函数调用的路径被分支预测机制标记为不太可能。因此,将用于处理不太可能的情况(如错误)的函数标记为冷函数很有用,以改进在极少数情况下调用标记函数的热函数的优化。

强调我的。