« 上一篇下一篇 »

全局寄存器分配

代码生成算法在单个基本块运行期间使用寄存器来存放值。但是,在每个基本块的结尾处,所有活跃变量的值都被保存到内存中。为了省略一部分这样的保存及相应的加载指令,我们可以把一些寄存器指派给频繁使用的变量,并且使得这些寄存器在不同基本块中的(即全局的)指派保持一致。因为程序的大部分时间花在它的内部循环上,所以一个自然的全局寄存器指派方法是试图在整个循环中把频繁使用的值存放在固定的寄存器中。从现在开始,假设我们知道一个流图的循环结构,并且我们知道在一个基本块中计算的哪些值会在该基本块外使用。

全局寄存器分配的策略之一是分配固定多个寄存器来存放每个内部循环中最活跃的值。在不同的循环中所选择的值也有所不同。没有被分配的寄存器可以说的那样用于存放一个基本块的局部值。这个方法的缺点是固定的寄存器个数并不总是恰好等于用于全局寄存器分配的最佳数量。但是这个方法实现起来很简单,它曾经被用在Fortran H中。这是IBM在20世纪60年代后期为360系列计算机开发的Fortran优化编译器。

在早期的C编译器中,程序员可以明确地参与某些寄存器分配过程。他们使用寄存器声明来使得某些值在一个过程运行期间都保存在寄存器中。明智地使用寄存器声明确实可以提高很多程序的运行速度,但是应该鼓励程序员在分配寄存器之前先获取程序的运行时刻特征并确定程序运行的热点代码。

使用计数

通过在循环L运行时把一个变量x保存在寄存器里面,我们可以节省从内存中加载z的开销。在本节我们假设,如果把x分配在寄存器中,对x的每一次引用可以节省一个单位的(用于加载的)成本。然而,如果z在一个基本块中被计算之后又在同一个基本块中被使用,那么当使用算法来生成基本块代码时,x有很大的机会被仍然保存在寄存器中。(因此对x的使用很可能本来就不需要从内存中加载。——译者注)因此,只有当x在循环L的某个基本块内被使用,且在同一基本块中x没有被先行赋值时,我们才认为这次使用节约了一个单位的开销。如果我们能够避免在某个基本块的结尾把x保存回内存,我们也可以省略2个单位的开销:保存指令和之后的加载指令。因此,如果x被分配在某个寄存器中,对于每个向x赋值且x在其出口处活跃的基本块,我们节省了两个单位的开销。

在支出方面,如果x在循环头部的人口处活跃,我们必须在进人循环L之前把x加载到它的寄存器中。这个加载的成本是两个成本单元。类似地,对于循环L的每个出口基本块B,如果x在B的某个L之外的后继的人口处活跃,我们必须以2个单位的代价把x保存起来。然而,假设循环将迭代多次,我们可以忽略这些支出。因为每次进入循环时,这些指令只会运行一次。因此,在循环L中把一个寄存器分配给x所得到的好处的一个估算公式是

∑use(x,B) + 2 * live(x,B)

t中的全部*本块B。其中,use(x, B)是x在B中被定值之前被使用的次数。如果x在B的出口处活跃并在B中被赋予一个值,则live(x,B)的取值为1,否则live(x,B)为0。请注意,只是一个估算公式。这是因为一个循环中的各基本块的运行频率实际是不同的,也因为式是基于循环被多次迭代的假设之上的。因此在特定的机器上,有可能需要设计一个与式类似,但具有一定差异的公式。

« 上一篇下一篇 »