这是一段看似非常特殊的C++代码.出于某种奇怪的原因,奇迹般地对数据进行排序使得代码几乎快了六倍.
#include <algorithm>
#include <ctime>
#include <iostream>
int main()
{
    // Generate data
    const unsigned arraySize = 32768;
    int data[arraySize];
    for (unsigned c = 0; c < arraySize; ++c)
        data[c] = std::rand() % 256;
    // !!! With this, the next loop runs faster.
    std::sort(data, data + arraySize);
    // Test
    clock_t start = clock();
    long long sum = 0;
    for (unsigned i = 0; i < 100000; ++i)
    {
        // Primary loop
        for (unsigned c = 0; c < arraySize; ++c) …我一直在挖掘Linux内核的某些部分,发现这样的调用:
if (unlikely(fd < 0))
{
    /* Do something */
}
要么
if (likely(!err))
{
    /* Do something */
}
我找到了它们的定义:
#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)
我知道它们是为了优化,但它们是如何工作的?使用它们可以预期性能/尺寸减少多少?至少在瓶颈代码中(当然在用户空间中)是否值得麻烦(并且可能失去可移植性).
我遇到了#define他们使用的一个__builtin_expect.
文件说:
内置功能:
long __builtin_expect (long exp, long c)您可以使用
__builtin_expect为编译器提供分支预测信息.一般来说,你应该更喜欢使用实际的配置文件反馈(-fprofile-arcs),因为程序员在预测程序实际执行情况方面是非常糟糕的.但是,有些应用程序难以收集此数据.返回值是值
exp,它应该是一个整数表达式.内置的语义是预期的exp == c.例如:Run Code Online (Sandbox Code Playgroud)if (__builtin_expect (x, 0)) foo ();表示我们不打算打电话
foo,因为我们预计x会为零.
那么为什么不直接使用:
if (x)
    foo ();
而不是复杂的语法__builtin_expect?
在最新的英特尔软件开发手册中,它描述了两个操作码前缀:
Group 2 > Branch Hints
    0x2E: Branch Not Taken
    0x3E: Branch Taken
这些允许跳转指令的显式分支预测(像操作码一样Jxx)
我记得在几年前读过x86显式分支预测本质上是gccs分支谓词内在函数上下文中的无操作.
我现在还不清楚这些x86分支提示是否是一个新功能,或者它们在实践中是否基本上是无操作.
任何人都可以清除这个吗?
(那就是:gccs分支预测函数会生成这些x86分支提示吗? - 并且当前的Intel CPU不会忽略它们吗? - 这是什么时候发生的?)
更新:
我创建了一个快速测试程序:
int main(int argc, char** argv)
{
    if (__builtin_expect(argc,0))
        return 1;
    if (__builtin_expect(argc == 2, 1))
        return 2;
    return 3;
}
拆卸以下内容:
00000000004004cc <main>:
  4004cc:   55                      push   %rbp
  4004cd:   48 89 e5                mov    %rsp,%rbp
  4004d0:   89 7d fc                mov    %edi,-0x4(%rbp)
  4004d3:   48 89 75 f0             mov    %rsi,-0x10(%rbp)
  4004d7:   8b 45 fc                mov    -0x4(%rbp),%eax
  4004da: …C ++ 20引入的属性[[likely]]和[[unlikely]]该语言,其可以被用于允许编译器优化为的情况下一个执行路径或者多更容易或远小于可能比其他的。
考虑到错误分支预测的成本,这似乎是一个在代码的性能关键部分可能非常有用的功能,但我不知道它实际上会导致编译器做什么。
是否有一段简单的代码可以添加[[likely]]和[[unlikely]]属性更改编译器的程序集输出?也许更重要的是,这些变化有什么作用?
我为自己的理解创建了一个简单的示例,以查看程序集是否有任何差异,但似乎这个示例太简单了,无法实际显示对程序集的任何更改:
void true_path();
void false_path();
void foo(int i) {
    if(i) {
        true_path();
    } else {
        false_path();
    }
}
void bar(int i) {
    if(i) [[likely]] {
        true_path();
    } else [[unlikely]] {
        false_path();
    }
}
如果我理解正确的分支(x86),处理器有时会推测性地采用代码路径并执行指令并"取消"错误路径的结果.如果错误的代码路径中的操作非常昂贵,例如导致高速缓存未命中的内存读取或某些昂贵的数学运算,该怎么办?处理器是否会提前尝试执行昂贵的操作?处理器通常如何处理这个问题?
if (likely) {
    // do something lightweight (addition, subtraction, etc.)
} else {
    // do something expensive (cache-miss, division, sin/cos/tan etc.)
}
gcc提供了可能/不太可能的提示,帮助编译器生成具有更好分支预测的机器代码.
有没有关于如何正确使用或不使用这些提示影响某些真实系统上实际代码性能的数据?
从这里我知道英特尔近年来实施了几种静态分支预测机制:
80486年龄:永远不被采取
Pentium4年龄:未采取后退/前锋
像Ivy Bridge,Haswell这样的新型CPU变得越来越无形,请参阅Matt G的实验.
英特尔似乎不想再谈论它,因为我在英特尔文档中找到的最新资料大约是十年前写的.
我知道静态分支预测(远远不是)比动态更重要,但在很多情况下,CPU将完全丢失,程序员(使用编译器)通常是最好的指南.当然,这些情况通常不是性能瓶颈,因为一旦频繁执行分支,动态预测器就会捕获它.
由于英特尔不再在其文档中明确声明动态预测机制,因此GCC的builtin_expect()只能从热路径中删除不太可能的分支.
我不熟悉CPU的设计,我不知道究竟是什么机制,目前英特尔使用其静态预测,但我还是觉得英特尔的最佳机制应该清楚地记录他的CPU",我打算去当动态预测失败,向前或向后',因为通常程序员是当时最好的指南.
更新:
我发现你提到的主题逐渐超出我的知识范围.这里涉及一些动态预测机制和CPU内部细节,我在两三天内无法学习.所以请允许我暂时退出你的讨论并充电.
这里仍然欢迎任何答案,也许会帮助更多人
compiler-construction x86 intel cpu-architecture branch-prediction
一些软件(通常是面向性能的,例如Linux内核、DPDK)具有用于影响分支预测的C帮助程序。
我有一个绝对简单的代码片段(假设我知道 a > b 的百分比)来表示嵌套某些逻辑时条件嵌套和应用likely/的问题:unlikely
bool foo()
{
    foo1(1);
    foo2(2);
    /* if (unlikely(a > b)) */
    /* if (a > b)*/
    {
        puts("Ohhh!!! Rare case");
        return true;
    }
    return false;
}
int main(void)
{
    /* if (unlikely(foo())) */
    /* if (foo()) */
    {
        puts("Azaza");
    }
}
那么从理论角度来看,为了提高性能,哪两行应该取消注释呢?
显然有3种方法可以帮助编译器进行分支预测:
1.
if (unlikely(a > b))
...
if (unlikely(foo()))
2.
if (a > b)
...
if (unlikely(foo()))
3.
if (unlikely(a > b))
...
if (foo()) …