我试图了解分支预测单元在 CPU 中如何工作。
我用过papi
还有linux的perf-events
但他们都没有给出准确的结果(对于我的情况)。
这是我的代码:
void func(int* arr, int sequence_len){
for(int i = 0; i < sequence_len; i++){
// region starts
if(arr[i]){
do_sth();
}
// region ends
}
}
我的数组由 0 和 1 组成。它有一个大小为sequence_len
。例如,如果我的尺码是 8,那么它的图案为0 1 0 1 0 0 1 1
或类似的东西。
Trial 1:
我试图了解 CPU 如何预测这些分支。因此,我使用 papi 并为错误预测的分支预测设置了性能计数器(我知道它也计算间接分支)。
int func(){
papi_read(r1);
for(){
//... same as above
}
papi_read(r2);
return r2-r1;
}
int main(){
init_papi();
for(int i = 0; i < 10; i++)
res[i] = func();
print(res[i]);
}
我看到的输出是(序列长度为 200)
100 #iter1
40 #iter2
10 #iter3
3
0
0
#...
所以,一开始,CPU 盲目地预测顺序,只能成功一半。在接下来的迭代中,CPU 的预测能力会越来越好。经过一定次数的迭代后,CPU 可以完美地猜测出来。
Trial 2
我想看看 CPU 在哪个数组索引处发生了错误预测。
int* func(){
int* results;
for(){
papi_read(r1);
if(arr[i])
do_sth();
papi_read(r2);
res[i] = r2-r1;
}
return res;
}
int main(){
init_papi();
for(int i = 0; i < 10; i++)
res[i] = func();
print(res[i]);
}
预期结果:
#1st iteration, 0 means no mispred, 1 means mispred
1 0 0 1 1 0 0 0 1 1 0... # total of 200 results
Mispred: 100/200
#2nd iteration
0 0 0 0 1 0 0 0 1 0 0... # total of 200 results
Mispred: 40/200 # it learned from previous iteration
#3rd iteration
0 0 0 0 0 0 0 0 1 0 0... # total of 200 results
Mispred: 10/200 # continues to learn
#...
收到结果:
#1st iteration
1 0 0 1 1 0 0 0 1 1 0... # total of 200 results
Mispred: 100/200
#2nd iteration
1 0 0 0 1 1 0 1 0 0 0... # total of 200 results
Mispred: 100/200 # it DID NOT learn from previous iteration
#3rd iteration
0 1 0 1 0 1 0 1 1 0 0... # total of 200 results
Mispred: 100/200 # NO LEARNING
#...
我的观察
当我在 for 循环之外测量错误预测时,我可以看到 CPU 从其错误预测中学习。然而,当我尝试测量单分支指令错误预测时,CPU 要么无法学习,要么我错误地测量了它。
我的解释
我给出的序列长度是 200。 CPU 有一个小的分支预测器(如 Intel 中的 2-3 位饱和计数器)和一个大的全局分支预测器。当我在环路外部进行测量时,测量中引入的噪声较少。我所说的噪音较小,是指papi
calls.
考虑一下:循环外测量
全球历史是:papi_start, branch_outcome1, branch_outcome2, branch_outcome3, ..., papi_end, papi_start (2nd loop of main iteration), branch_outcome1, ...
因此,分支预测器以某种方式在同一分支中找到模式。
但是,如果我尝试测量单分支指令,则全局历史记录是:papi_start, branchoutcome1, papiend, papistart, branchoutcome2, papiend...
因此,我正在向全球历史介绍越来越多的分支。我假设全局历史记录无法容纳许多分支条目,因此,它无法在所需的 if 语句(分支)中找到任何相关性/模式。
因此
我需要测量单个分支的预测结果。我知道如果我不过多介绍papi的话CPU是可以学习200模式的。我查看了 papi 调用,看到了很多 for 循环和 if 条件。
这就是为什么我需要更好的测量。我试过Linuxperf-event
但这使得ioctl
调用,这是一个系统调用,我用系统调用污染了全局历史记录,因此,这不是一个好的测量方法。
我读过rdpmc
and rdmsr
指令,我假设因为它们只是指令,所以我不会污染全局历史,并且我可以一次测量单个分支指令。
但是,我不知道如何做到这一点。我有 AMD 3600 CPU。这些是我在网上找到的链接,但我不知道如何做到这一点。除此之外,我还缺少什么吗?
英特尔rdpmc https://software.intel.com/en-us/forums/software-tuning-performance-optimization-platform-monitoring/topic/595214
AMD 性能手册 https://www.amd.com/system/files/TechDocs/56255_OSRR.pdf