在此文章中,一种技术被描述为在移动的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
}();
}
}
Run Code Online (Sandbox Code Playgroud)
这是一项很棒的技术,但需要大量的样板文件。包装它以尽可能减少样板的最佳方法是什么?理想情况下 C++14 兼容允许特定于 gcc 的功能。
额外问题: if 语句中的不太可能(...) 是否多余,因为 lambda 被显式标记为冷?
想到了两种方法:
最好的设计是将此功能包装到封装属性和处理的函数中。为此,您需要传递一个要作为冷处理程序调用的回调(在本例中为 lambda)。它看起来很简单(使用 C++11 属性而不是__attribute__
语法):
template <typename Fn>
[[gnu::cold]] [[gnu::noinline]]
void cold_path(Fn&& fn)
{
std::forward<Fn>(fn)();
}
Run Code Online (Sandbox Code Playgroud)
您还可以扩展此解决方案以利用要测试的条件,例如:
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));
}
}
Run Code Online (Sandbox Code Playgroud)
把它们放在一起,你有:
void example() {
cold_path_if(testForTerriblyUnlikelyEdgeCase(), [&]{
std::cerr << "Oh no, something went wrong" << std::endl;
std::abort();
});
}
Run Code Online (Sandbox Code Playgroud)
这是它在Compiler Explorer上的样子。
如果不希望传递显式 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(); }
};
}
Run Code Online (Sandbox Code Playgroud)
这是作为一个可从函数隐式转换的类完成的,因此您可以编写类似detail::invoker foo = []{ ... }
. 然后我们想把定义的第一部分带到捕获,并将它包装成一个宏。
为此,我们需要一个唯一的变量名称,否则如果多个处理程序在同一范围内,我们可能会隐藏或重新定义变量。为了解决这个问题,我将__COUNTER__
宏附加到一个名称;但这是非标准的:
#define COLD_HANDLER ::detail::invoker some_unique_name ## __COUNTER__ = [&]() __attribute__((noinline,cold))
Run Code Online (Sandbox Code Playgroud)
这只是将自动调用程序的创建包装起来,直到定义 lambda 为止,因此您需要做的就是编写 COLD_HANDLER { ... }
使用现在看起来像:
void example() {
if (unlikely(testForTerriblyUnlikelyEdgeCase())) {
COLD_HANDLER {
//error handling code here
};
}
}
Run Code Online (Sandbox Code Playgroud)
这是编译器资源管理器上的示例
两种方法都生成与直接使用 lambda相同的程序集,只是标签和名称不同。(注:这个比较使用的是std::fprintf
代替stds::cerr
所以组装更小更容易比较)
额外问题: if 语句中的不太可能(...) 是否多余,因为 lambda 被显式标记为冷?
阅读 GCC 的文档__attribute__((cold))
似乎表明所有导致冷函数的分支都被标记为unlikely
,这应该使unlikely
宏的使用变得多余和不必要——尽管拥有它应该没有什么坏处。
从属性页面:
Cold 属性用于通知编译器某个函数不太可能被执行。该函数针对大小而不是速度进行了优化,并且在许多目标上,它被放置在文本部分的特殊子部分中,因此所有冷函数看起来都靠得很近,从而提高了程序非冷部分的代码局部性。分支预测机制将导致代码中调用冷函数的路径标记为不太可能。因此,将用于处理不太可能的条件(例如 perror)的函数标记为冷函数是有用的,以改进在极少数情况下调用标记函数的热函数的优化。
强调我的。