我有这个函数,给定一个格雷码,返回下一个格雷码。您可以在此处找到有关其工作原理的更完整说明。问题是我想使这个增量函数模块化,以便递增对应于UINT_MAX
的格雷码返回对应于0
的格雷码(分别是最高有效位和0
)。由于这不是默认行为,因此我为此特殊情况添加了检查。以下是完整的算法:
unsigned next_gray(unsigned gray)
{
static const unsigned msb
= 1u << (CHAR_BITS - sizeof(unsigned) - 1u);
// gray is odd
if (__builtin_parity(gray))
{
if (__builtin_expect(gray == msb, false))
{
return 0u;
}
else
{
unsigned y = gray & -gray;
return gray ^ (y << 1u);
}
}
// gray is even
return gray ^ 1;
}
所以,实际的问题实际上是关于分支预测的。我经常读到,只有当一个分支真的有可能被选择或真的不太可能被选择时才使用__builtin_expect
,常见的例子是在没有错误的情况下加速程序。
考虑到我不是在处理错误情况,我不确定使用 __builtin_expect
进行这样的边界检查是否是一个好主意。这是使用__builtin_expect
的好地方,还是增加最大值是一种足以欺骗分支预测的常见操作?
注意:与往常一样,评论和答案突出显示了我的问题中不清楚的内容:)
我将提供更多上下文:这个函数是库的一部分,为了成为一个库而开发,而不是被任何实际项目使用。因此,添加__builtin_expect
意味着我希望人们主要增加其他值而不是最大值;手头没有任何实际项目,我想知道这是否是一个安全的假设。
自GCC在线文档:
您可以使用__builtin_expect
为编译器提供分支预测信息。一般来说,你应该更喜欢使用实际的配置文件反馈(-fprofile-arcs
),因为程序员在预测他们的程序实际表现方面是出了名的糟糕。但是,在某些应用程序中,很难收集此数据。
这是使用__builtin_expect的好地方,还是增加最大值是一种足以欺骗分支预测的常见操作?
这一切都取决于您的应用。 如果gray
的值是均匀分布的,那么它将是 1 分(UINT_MAX+1)
,但你能肯定地说吗? 这就是为什么文档建议使用 -fprofile-arcs
.
gcov维基百科文章实际上包含一个很好的简单示例,介绍如何使用-fprofile-arcs
和gcov
来获取信息以做出明智的决定。
更新:
如果您无法配置文件,那么在所有条件相同的情况下,边缘情况gray == msb
不太可能,因此使用__builtin_expect
可能是安全的。 但是,如果您因为不知道库将如何使用而无法进行分析,这听起来更像是悲观而不是优化。 如果我使用你的库并且总是传入gray
这样它等于msb
,那么你的库对我来说就不会那么快了。 编写时未考虑特定应用程序的通用库通常会尝试对一般情况有益,或者不对输入做出任何假设。 这就是为什么你会看到不同的malloc
实现,如jemalloc和tcmalloc。 两者都针对非常具体的用例进行了优化,如果您以不像优化的方式使用它,它就不会做得那么好。 此外,这篇博客文章可能会引起您的兴趣。