【发布时间】:2019-08-19 02:11:33
【问题描述】:
我尝试测量分支预测成本,我创建了一个小程序。
它在堆栈上创建一个小缓冲区,用随机 0/1 填充。我可以用N 设置缓冲区的大小。该代码重复导致相同1<<N随机数的分支。
现在,我已经预料到,如果 1<<N 足够大(例如 >100),那么分支预测器将无效(因为它必须预测 >100 个随机数)。然而,这些是结果(在 5820k 机器上),随着 N 的增长,程序变得更慢:
N time
=========
8 2.2
9 2.2
10 2.2
11 2.2
12 2.3
13 4.6
14 9.5
15 11.6
16 12.7
20 12.9
作为参考,如果缓冲区用零初始化(使用注释的init),时间或多或少是恒定的,对于N 8..16,它在 1.5-1.7 之间变化。
我的问题是:分支预测器能否有效预测如此大量的随机数?如果不是,那这里发生了什么?
(更多解释:代码执行2^32个分支,不管N。所以我预计,代码运行速度相同,不管N,因为根本无法预测分支. 但是似乎如果缓冲区大小小于 4096 (N
代码如下:
#include <cstdint>
#include <iostream>
volatile uint64_t init[2] = { 314159165, 27182818 };
// volatile uint64_t init[2] = { 0, 0 };
volatile uint64_t one = 1;
uint64_t next(uint64_t s[2]) {
uint64_t s1 = s[0];
uint64_t s0 = s[1];
uint64_t result = s0 + s1;
s[0] = s0;
s1 ^= s1 << 23;
s[1] = s1 ^ s0 ^ (s1 >> 18) ^ (s0 >> 5);
return result;
}
int main() {
uint64_t s[2];
s[0] = init[0];
s[1] = init[1];
uint64_t sum = 0;
#if 1
const int N = 16;
unsigned char buffer[1<<N];
for (int i=0; i<1<<N; i++) buffer[i] = next(s)&1;
for (uint64_t i=0; i<uint64_t(1)<<(32-N); i++) {
for (int j=0; j<1<<N; j++) {
if (buffer[j]) {
sum += one;
}
}
}
#else
for (uint64_t i=0; i<uint64_t(1)<<32; i++) {
if (next(s)&1) {
sum += one;
}
}
#endif
std::cout<<sum<<"\n";
}
(代码也包含非缓冲版本,使用#if 0。它的运行速度与使用N=16的缓冲版本大致相同)
这是内部循环反汇编(使用 clang 编译。它为 8..16 之间的所有 N 生成相同的代码,只是循环计数不同。Clang 展开循环两次):
401270: 80 3c 0c 00 cmp BYTE PTR [rsp+rcx*1],0x0
401274: 74 07 je 40127d <main+0xad>
401276: 48 03 35 e3 2d 00 00 add rsi,QWORD PTR [rip+0x2de3] # 404060 <one>
40127d: 80 7c 0c 01 00 cmp BYTE PTR [rsp+rcx*1+0x1],0x0
401282: 74 07 je 40128b <main+0xbb>
401284: 48 03 35 d5 2d 00 00 add rsi,QWORD PTR [rip+0x2dd5] # 404060 <one>
40128b: 48 83 c1 02 add rcx,0x2
40128f: 48 81 f9 00 00 01 00 cmp rcx,0x10000
401296: 75 d8 jne 401270 <main+0xa0>
【问题讨论】:
-
是的,这并不奇怪。 TAGE 预测技术旨在专门处理可能需要维护数千位历史的分支。
-
我已经在 Haswell 上运行了你的代码并重现了你的结果。此外,TMA 方法表明,当 N
-
一般;第一次执行代码时,分支预测率“不太好”,因为没有历史记录;如果没有任何变化(您可以存储上次的结果),那么执行代码两次是没有意义的,因此 CPU 具有完整分支历史的“非常高兴的情况”在实践中几乎不会发生。衡量“过分幸福案例”的基准只会提供错误信息。
-
@Brendan:是的。但是这个问题是关于预测 4096 个随机结果真的是“过分快乐的情况”吗?对我来说,这似乎不太可能(这就是为什么我没有费心去检查
perf stat。如果我检查过,这个问题就不存在了)。但事实证明,情况确实如此。当前的 CPU 分支预测器非常好,它可以记住 4096 个结果。这对我来说是一个惊喜。 20 年前,分支预测器是“强/弱”*“采用/未采用”。现在它可以做得更多。 -
@Brendan:这绝不是“纯粹无关的幻想”。举一个反例:口译员。他们经常走同一条路是很常见的。以及对您的第一条评论的回应:“如果没有任何变化(您可以存储上次的结果),那么执行代码两次是没有意义的”。那是错误的。注意,这里的分支模式只是相同的。数据可以不同(但遵循相同的路径)。就像解释器运行字节码一样。但是,无论如何,这个问题是关于理解基准测试的结果,而不是关于它是否现实。
标签: performance x86 x86-64 cpu-architecture branch-prediction