CPU的性能,是这样被编译器压榨的!
Posted 程序芯世界
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CPU的性能,是这样被编译器压榨的!相关的知识,希望对你有一定的参考价值。
1.一个例子
先来看一段非常简单的伪代码:
a = 0;
L1: b = a + 1;
c = c + b;
a = b * 2;
if a < 9 goto L1;
return c;
2. 活跃变量分析
2.1 活跃变量的定义
如果程序中的变量在某个点之后会被使用,则该变量在该点是活跃的。
1 a = 0;
2 L1: b = a + 1;
3 c = c + b;
4 a = b * 2;
5 if a < 9 goto L1;
6 return c;
定义比较抽象,看完下面的例子就明白定义的具体含义了。
-
活跃变量定义举例
在上图的代码中,变量c在第4行是活跃的,因为变量c在第4行之后还会继续使用(第6行会使用变量c),这就是我们所说的活跃变量。
2.2 活跃变量分析的用途
只要两个变量不会同时活跃,那么这两个变量就可以共用同一个寄存器,不用担心寄存器的内容被覆盖的问题。
也就是说当一个寄存器中的变量在后续不再活跃,我们可以重新分配该寄存器给其他变量。
知道了变量的活跃信息之后,就可以重复利用CPU中的寄存器啦。
看完这篇文章你就会知道最后的谜底,知道这段小程序最少需要几个寄存器。
2.3 符号及说明
-
in[b]表示在基本块b入口处活跃的变量集合。 -
out[b]表示在基本块b出口处活跃的变量集合。 -
use[b]表示在基本块b使用,但是使用前在基本块b中没有定义的变量集合。 -
def[b]表示在基本块中被定义的变量集合。 -
succ[b]表示基本块b的后继基本块。
2.4 计算方程与伪代码
-
如果一个变量在基本块b中使用,则该变量在基本块b的入口处是活跃的。
-
如果一个变量在基本块b的入口处是活跃的,在该变量在基本块b的所有前驱节点的出口是活跃的。
-
如果一个变量在基本块b的出口处是活跃的,且该变量在基本块b中没有定义,则该变量在基本块b的入口处是活跃的。
用公式表示就是:
(公式1)
(公式2)
伪代码如下:
伪代码用一句话概括就是:从后向前计算每个节点的in和out值,然后不断的循环,直到当前循环和上一次循环的所有节点in和out值一致时结束循环。
2.5 实例
公式和代码可能比较抽象,下面来看一个具体实例。
控制流图,是用在编译器中的一个抽象数据结构,它用图的形式表示一个过程内所有基本块执行的可能流向, 也能反映一个程序的实时执行过程。为简单起见,这里将每一条语句作为一个节点。
还是文章最初的那段代码,放在下面方便回看。
1 a = 0;
2 L1: b = a + 1;
3 c = c + b;
4 a = b * 2;
5 if a < 9 goto L1;
6 return c;
上面代码对应的控制流图如下:
下表是根据伪代码和公式1、公式2计算的控制流图中每个节点的in和out值。
以节点4的第一次迭代为例,表格中其它节点的计算原理与节点4一致。文中所提到的公式1与公式2,是2.4节中已经标明的计算in与out的两个公式。
-
use[4]和def[4]的计算:
-
use[4] = {b}
因为第四行的等式右边使用了b。
-
def[4] = {a}
因为第四行对变量a进行了赋值,也就是对变量a进行了定义。
需要注意的是活跃变量分析要获得将来的信息,因此是从后向前的一个过程,也就从节点6向节点1的一个过程。
-
in[4]与out[4]的计算:
从控制流图中可知,4的后继节点为5,所以有:
-
succ[4]={5}
代入到out的计算公式(公式2)里面:
-
out[4] = in[s]
前面已经计算出succ[4] = {5},代入到上式可得:
-
out[4] = in[5] = {ac}
对于in[4]的计算,直接带入到in的计算方程(公式1)中可得:
-
in[4] = use[4] (out[4] - def[4])
其中use[4],out[4]和def[4]已知,因此就可以求出in[4]了。
当然这只是第一遍迭代的结果,还需要继续迭代直到前一次计算的in与out与本次in与out的结果一致,此时算法收敛,迭代结束。
可以看到表格中第三遍迭代的结果与第二遍迭代的结果一致,因此程序迭代到第三次后结束。
3. 最终答案
再来回顾一下这段代码
1 a = 0;
2 L1: b = a + 1;
3 c = c + b;
4 a = b * 2;
5 if a < 9 goto L1;
6 return c;
-
变量a的活跃范围是:
1->2 和 4->5->2
-
变量b的活跃范围是:
2->3->4
-
变量c的活跃范围是:
1->2->3->4->5->2和5->6
因此最初问题的答案如下:
只需要2个寄存器就可以了,因为变量a与变量b的活跃范围不会冲突,可以分配一个寄存器,变量c分配一个寄存器,整段程序一共只需要两个寄存器。
所以现在我们就知道编译器是如何压榨CPU的啦,当然编译器还有很多算法去高效的利用CPU的硬件资源。
本文仅阐述了数据流分析中的活跃变量分析,这是寄存器分配的基础。后续还会写一篇关于图着色算法进行寄存器分配的文章,结合两篇文章就可以了解我们的编译器是如何分配CPU中的寄存器了。
4.总结
文章介绍了数据流分析中活跃变量计算的迭代算法。活跃变量计算是寄存器分配的基础,后续还会写一篇图着色算法在分配寄存器时的应用,两个算法结合在一起才构成了完整的寄存器分配。
寄存器的高效利用只是编译器后端优化中的一种,减少程序中的冗余计算也是编译器要做的事情。
实际上不管什么语言,程序中总会存在一些程序员容易忽略的冗余计算。编译器后端在做优化时会优化掉这些冗余计算。比如公共子表达式删除、复写传播、死代码删除、循环不变量外提等。
5.往期文章推荐
该文章涵盖了三个方向:计算机图形学,人工智能、GPU体系结构。这三个方向也是连续三届图灵奖授予的领域。
关于作者
为了保证文章的质量,更新的频率会低一些,但会坚持下去的,敬请期待。
以上是关于CPU的性能,是这样被编译器压榨的!的主要内容,如果未能解决你的问题,请参考以下文章