C++ 完全预测分支的分支预测开销

C++ 完全预测分支的分支预测开销,c++,performance,x86,branch,C++,Performance,X86,Branch,我读到一个完美预测的分支有零/几乎零的开销。(例如:)我不太明白人们这样说是什么意思。至少需要评估分支条件,它可以是简单的布尔函数或函数调用,需要时间。 < P>分支预测是预测在指令级的条件的结果,这是C或C++条件下的实际结果——如果这是一百万个字符串比较的结果,它可能不是特别有用,因为这种比较需要很多时间。如果它是for循环的结束条件,循环遍历两个字符串,每个字符串有一百万个字符,那么它非常有用,因为它在该循环中发生了很多次(假设字符串相等) 对两个长字符串进行字符串比较是不自由的。可以自由

我读到一个完美预测的分支有零/几乎零的开销。(例如:)我不太明白人们这样说是什么意思。至少需要评估分支条件,它可以是简单的布尔函数或函数调用,需要时间。

< P>分支预测是预测在指令级的条件的结果,这是C或C++条件下的实际结果——如果这是一百万个字符串比较的结果,它可能不是特别有用,因为这种比较需要很多时间。如果它是for循环的结束条件,循环遍历两个字符串,每个字符串有一百万个字符,那么它非常有用,因为它在该循环中发生了很多次(假设字符串相等)

对两个长字符串进行字符串比较是不自由的。可以自由地正确猜测字符串比较是否将继续(直到我们找到字符串的结尾或差异,此时分支预测出错)

“不可预测”分支将导致处理器不知道代码继续在哪里。现代CPU有相当长的管道(15-30步),因此如果管道中没有“正确”的代码,处理器将不得不等待“正确”的代码通过管道

所以要回答实际的问题:

当分支本身被很好地预测时,处理器已经在管道中获得了RIGTH指令,并且在我们能够执行正确的指令继续程序之前,没有“管道气泡”在管道中穿行。下面是一个类比。如果预测是错误的,那么管道中除了正确的指令之外还会有其他指令,处理器必须仔细检查这些指令,然后将它们扔掉

把它想象成一个汽车工厂,在一条生产线上生产a型和B型的汽车,首先将车身安装到底盘上,涂上油漆(神奇的油漆,它几乎会立刻变干),然后装上发动机和变速箱,然后装上车轮,装上车灯,最后装上玻璃,这就是一辆完整的汽车。执行每一步需要20分钟,汽车的传送带将每20分钟向前移动到下一个位置[在本练习中,我们忽略了移动本身需要时间的事实]

你负责生产线,生产线上有很多车。突然,大老板说,“我们刚接到B型车的订单,马上换成B型车。”。所以,你开始把B车零件送入生产线。但下一辆B型车在另一端问世还需要一段时间

分支预测的工作方式是“猜测”代码是改变方向还是转到下一条指令。如果它猜对了,就像是在猜测“大老板”什么时候来告诉你在A型和B型汽车之间换车,这样你就可以在老板想要的时候准备好合适的汽车从生产线出来,而不必等到整个生产线都停下来

当它工作时,它是伟大的,因为预期的东西已经准备好完成。如果您猜错了,您仍然需要等待生产线的其余部分运行当前集合,并将它们隐藏到“我们没有这些的客户”角落(或CPU指令方面的“放弃指令”)

大多数现代CPU也允许“推测性执行”。这意味着处理器将在实际确定条件之前开始执行指令。因此,在老板这么说之前,处理器将从A车切换到B车。如果此时,老板说“不,你应该继续修车”,你有一大堆车要丢弃,你已经开始修车了。你不一定要制造所有的汽车,但你必须让它们通过生产线,每20分钟一步。

Summary 评估分支条件总是需要一些工作,即使完全可以预测,但由于现代CPU的内部并行性,额外的工作不必增加特定指令序列的成本

细节 我认为部分困惑在于许多人对CPU指令执行的心理表现模型。是的,每一条指令都需要一些工作,所以这就意味着每一条指令在执行时间上都有一些成本,不管成本有多小,对吧

如果执行的总成本只是每个指令的工作量的加法,那么这是正确的-您只需将所有工作量相加,就可以得到最终的成本。由于现代CPU中存在大量的并行性,所以它不能像那样工作

把它想象成组织一个生日聚会。你可能需要买10分钟的面粉,然后烤一个60分钟的蛋糕,然后去买一个30分钟的特殊礼物。这些时间安排是活动所需的全部“工作”。然而,有人可以在捡拾面粉和烤蛋糕的时候去捡拾礼物。然而,没有面粉就不能烤蛋糕。所以你有两个依赖链:70分钟买面粉->烤蛋糕链和30分钟提货礼品链。无限制的并行性,只有70分钟的蛋糕相关链有助于时间,一切准备就绪。领取礼物需要30分钟的工作,但由于其他需要更长时间(即关键路径)且并行进行的工作,最终不需要花费时间(而不是延迟完成所有任务)

更多的额外任务可以并行完成,直到没有人分配给他们为止。(此时,执行吞吐量限制开始增加延迟,这称为资源冲突。如果资源冲突延迟了关键路径,而不是较短的依赖链之一。CPU不会
int mul1(int count, int x) {
    do {
        x *= 111;
    } while (--count);

    return x;
}
.L2:
  imul eax, eax, 111
  sub edi, 1
  jne .L2
int mul2(int count, int x) {
    do {
        x *= 111;
        if (x == 0) {
            abort();
        }
    } while (--count);

    return x;
}
.L7:
  imul eax, eax, 111
  test eax, eax
  je .L12  ; ends up calling abort
  sub edi, 1
  jne .L7
Running benchmarks groups using timer libpfc

** Running benchmark group stackoverflow tests **
                     Benchmark    Cycles
                     No branch     3.000
             Added test-branch     3.000