理解处理器的“分支预测逻辑”,优化程序性能

 一、什么是“分支预测逻辑”?为什么需要它?

  由于处理器通过流水线技术来提高性能,而流水线要求事先知道接下来要执行的具体指令,才能保持流水线中充满待执行的指令。当在程序中遇到分支语句/条件跳转时,问题就出现了,处理器不确定下一条指令是什么,这时就需要进行“分支预测逻辑”来确定哪一条指令进入流水线。

  如果预测了一个分支加入流水线,之后确发现它是错误的分支,处理器要回退该错误预测执行的工作,再用正确的指令填充流水线。这样一个错误的预测会严重浪费时钟周期,导致程序性能下降。

举个例子:一个人走到一个岔路口(分支语句),他不知道正确的路是左边还是右边。如果他选择了一条路走了一会后发现不对,他就要再返回到岔路口再走另外一条路,白白耗费了时间。如果他能一次就找到争取的路就好了(分支预测逻辑的必要性)。如何能做到呢?如果他要多次走过岔路口,发现总是右边的路是对的,这时候当再遇到岔路口时,他就可以预测右边的路是正确的(分支预测逻辑的一种原理)。

二、怎样根据“分支预测逻辑”来优化程序性能?

1.一个排序后提升程序性能的例子:

 1 #include <algorithm>
 2 #include <ctime>
 3 #include <iostream>
 4 
 5 int main()
 6 {
 7     // 产生随机数数组,随机数在0-255之间
 8     const unsigned arraySize = 32768;
 9     int data[arraySize];
10 
11     for (unsigned c = 0; c < arraySize; ++c)
12         data[c] = std::rand() % 256;
13 
14     // 关键:加上这句后程序运行明显会更快!
15     //std::sort(data, data + arraySize);
16 
17     // 测试
18     clock_t start = clock();
19     long long sum = 0;
20 
21     for (unsigned i = 0; i < 100000; ++i)
22     {
23         for (unsigned c = 0; c < arraySize; ++c)
24         {
25             if (data[c] >= 128)  //条件分支
26                 sum += data[c];
27         }
28     }
29 
30     double elapsedTime = static_cast<double>(clock() - start) / CLOCKS_PER_SEC;
31 
32     std::cout << elapsedTime << std::endl;
33     std::cout << "sum = " << sum << std::endl;
34 }

  25行是个岔路口,如果随机数是乱序的,分支预测的结果只有50%的正确率,严重影响性能。

  然而去掉15行的注释后再执行,由于数组有序,前一部分循环在分支处总是选择同样的分支(<128),这样每到分支处就很容易预测成功,预测成功的概率在90%以上。程序执行速度加快!

还有一种利用位运算避免分支预测的巧妙方法:

把25、26行替换为:

25 int t = (data[c] - 128) >> (sizeof(int) - 1);
26 sum += (~t & data[c]);

 

2.编译器在将代码编译成汇编语言时,处理分支语句时,遇到条件判断的表达时很容易计算的情况,会用条件数据传送指令代替条件控制转移指令,使得无需预测下一条指令,从而改进代码效率。

 

 

posted @ 2013-10-22 17:51  任者  阅读(2132)  评论(2编辑  收藏  举报