【发布时间】:2020-06-01 12:52:18
【问题描述】:
我试图了解分支预测单元在 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 或类似的东西。
试用 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 可以完美地猜到。
试用 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 调用。
考虑一下:在循环测量之外
全局历史为: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 条件。
这就是为什么我需要更好的测量。我试过 linux perf-event,但它会调用 ioctl,这是一个系统调用,我用系统调用污染了全局历史记录,因此不是一个好的衡量标准。
我已经阅读了 rdpmc 和 rdmsr 指令,并且我认为由于它们只是指令,因此我不会污染全局历史记录,并且我可以一次测量单个分支指令。
但是,我不知道该怎么做。我有 AMD 3600 CPU。这些是我在网上找到的链接,但我不知道该怎么做。除此之外,我是否遗漏了什么?
【问题讨论】:
-
为什么不试试裸机软件呢?例如在 ARM 微控制器上。由于没有操作系统,行为会更容易预测和更容易调试?
-
这里有一篇关于测量 ARM cortex 分支预测的好文章:community.arm.com/developer/ip-products/processors/b/…
-
好吧,我想测量一下 AMD 处理器。我认为您的链接没有为我的问题提供有价值的答案。但我会研究一下,只是为了学习新东西。 @The_Average_Engineer
-
@The_Average_Engineer:x86 CPU 在实模式下启动,并且主板中始终有内置固件,可以加载 UEFI 应用程序或旧版 BIOS 引导扇区。它不像 ARM 板,您基本上是将固件写入闪存。我不认为裸机(甚至在 UEFI 下运行)是一个非常有用的建议。至少 UEFI 应用程序不必为了运行正常的 64 位代码而做一堆 osdev 废话(例如设置 GDT 和页表),并且可以使用 UEFI 函数将结果保存到文件中。但你不会有调试器或任何东西。
标签: c x86 performancecounter branch-prediction papi