0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

编译器优化教程:寄存器分配 1

jf_78858299 来源:毕昇编译 作者:王博洋 2023-01-30 16:15 次阅读

概念介绍

在介绍算法之前,我们回顾下基本概念:

  • |X| :X的度数,(无向图中)节点的邻居个数。
  • CFG :控制流图。
  • successor :本文指CFG中基本块的后继。
  • 四元式 :(op,result,arg1,arg2),比如常见的a=b+c就可以看作四元式(+,a,b,c)。
  • SSA(Static Single Assignment) :静态单赋值。
  • use/def :举个例子,对于指令n: c <- c+b来说 use[n]={c,b},def[n]={c}。
  • live-in :当以下任一条件满足时,则称变量a在节点n中是live-in的,写作a∈in[n]。节点n本文中代表指令。
    1. a∈use[n];
    2. 存在从节点n到其他节点的路径使用了a且不包括a的def。
  • live-out : 变量a在节点n的任一后继的live-in集合中。写作a∈out[n]
  • 干涉 :在某一时刻,两个变量在同一live-in集合中。
  • RIG(Register Interfere Graph) : 无向图,其点集和边集构成如下:
  • 节点:变量
  • 边:如果两节点存在干涉,那么这两节点之间就有一条干涉边
  • k-着色 :给定无向图G=(V,E),其中V为顶点集合,E为边集合。将V分为k个组,每组中没有相邻顶点,可称该图G是k着色的。当然可着色前提下,k越小越好。

需要注意的是,我们后续的算法会作用在最普通的四元式上,而不是SSA。在介绍寄存器分配算法之前,我们需要活跃变量分析来构建干涉图。

活跃变量分析与图着色算法

活跃变量分析

简单来说,就是计算每个点上有哪些变量被使用。

算法描述如下[1]:

input: CFG = (N, E, Entry, Exit)
begin
// init
for each basic block B in CFG
	in[B] = ∅
// iterate
do{
	for each basic block B other than Exit{
		out[B] = ∪(in[s]),for all successors s of B
		in[B] = use[B]∪(out[B]-def[B])
	}
}until all in[] do't change

活跃变量分析还有孪生兄弟叫Reaching Definitions,不过实现功能类似,不再赘述。

举个例子:对图1的代码进行活跃变量分析

图片

图1[2]

可以得到每个点的活跃变量如图2所示:

图片

图2

过程呢?限于篇幅,仅仅计算第一轮指令1的结果,剩余部分读者可自行计算。

步骤 下标 out in
第一次迭代 1 {} {b,c}
... ... ... ...

可画出RIG如图3:

图片

图3

图着色

经过上文的活跃变量分析,我们得到了干涉图,下一步对其进行上色。

但是图着色是一个NP问题,我们会采用启发式算法对干涉图进行着色。基本思路是:

  1. 找到度小于k的节点;
  2. 从图中删除;
  3. 判断是否为可着色的图;
  4. 迭代运行前3步直到着色完成。

算法描述[3]:

input: RIG, k
// init
stack = {}
// iterate
while RIG != {} {
	t := pick a node with fewer than k neighbors from RIG // 这里RIG可以先按度数排序节点再返回
	stack.push(t)
	RIG.remove(t)
}
// coloring
while stack != {} {
	t := stack.pop()
	t.color = a color different from t's assigned colored neighbors
}

对于例子1,假设有4个寄存器r1、r2、r3、r4可供分配。

步骤 stack RIG
0 {} 1675066141(1).png
1 {a} 1675066164(1).png
2 {d,a} 1675066191(1).png

所以图3中的RIG是4-着色的。但如果只有三种颜色可用,怎么办呢?

没关系,我们还有大容量的内存,虽然速度慢了那么一点点。着色失败就把变量放在内存里,用的时候再取出来。

依然是上例,但是k=3,只有三个颜色。

如果f的邻居是2-着色的就好了,但不是。那就只能选一个变量存入内存了。这里我们选择将变量f溢出至内存。溢出后的IR和RIG如图:

图片

图4 溢出后的IR

图片

图5 溢出后的RIG

所以,溢出其实是分割了变量的生命周期以降低被溢出节点的邻居数量。溢出后的着色图如图6:

图片

图6 着色后的图5

这里溢出变量f并不是明智的选择,关于如何优化溢出变量读者可自行查阅资料

至此,图着色算法基本介绍完毕。不过,如果代码中的复制指令,应该怎么处理呢?

寄存器分配之前会有Copy Propagation和Dead Code Elimination优化掉部分复制指令,但是两者并不是全能的。

比如:代码段1中,我们可以合并Y和X。但是代码段2中Copy Propagation就无能为力了,因为分支会导致不同的Y值。

// 代码段1
X = ...
A = 10
Y = X
Z = Y + A
return Z

// 代码段2
X= A + B
Y = C
if (...) {Y = X}
Z = Y + 4

所以,寄存器分配算法也需要对复制指令进行处理。如何处理?给复制指令的源和目标分配同一寄存器。

那么如何在RIG中表示呢?如果把复制指令的源和目标看作 RIG中相同的节点 ,自然会分配同一寄存器。

  • 相同节点?可以扩展RIG:新增虚线边,代表合并候选人。
  • 成为合并候选人的条件是:如果X和Y的生命周期不重合,那么对于Y=X指令中的X和Y是可合并的。
  • 为了保证合并合法且不造成溢出:合并后局部的度数

那么如何计算局部的度数?介绍三种算法:

  • 简单算法
  • Brigg's 算法
  • George's 算法
  1. 简单算法:(|X|+|Y|),很保守的算法但是可能会错过一些场景
    比如k=2时,图7应用简单算法是没办法合并的
    图片
    图7[3]
    但明显图7可以合并成图8:
    图片
    图8[3]
  2. Brigg's 算法:X和Y可合并,如果X和Y中度数≥k的邻居个数<k。但是如果X的度数很大,算法效率就不高
  3. George's算法:X和Y可合并,如果对Y的每个邻居T,|T| ‍比如k=2时,图9就可以合并X和Y。
    图片
    图9[3]
    相对于Brigg算法、George算法不用遍历节点的邻居。注意,图着色时可以按节点度数从小到大依次访问。

到此,图着色算法介绍完毕。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 寄存器
    +关注

    关注

    30

    文章

    5113

    浏览量

    118012
  • 编译器
    +关注

    关注

    1

    文章

    1579

    浏览量

    48702
  • CFG
    CFG
    +关注

    关注

    0

    文章

    10

    浏览量

    9717
收藏 人收藏

    评论

    相关推荐

    KEIL编译器如何使用R8-R11寄存器

    使用Cortex-M0+控制,由于指令对于R0-R7操作比较便利,对于R8-R11寄存器,是否编译器会使用到?
    发表于 05-15 17:10

    编译器优化导致USART波特率配置错误,请问这是为什么?如何解决?

    菜鸟一枚,遇到问题上网找不到答案,只好自己尝试,请大神指教。 问题描述:配置USART的波特率为38400,结果无法成功接收数据,检查后发现波特率配置寄存器BRR错误, 编译器优化导致USART
    发表于 07-06 03:05

    线性汇编-寄存器分配疑问 请问为什么不同的变量分配了相同的寄存器

    上面是线性汇编函数,下图为寄存器分配,为什么不同的变量分配了相同的寄存器???如何使一个变量分配一个寄存
    发表于 08-07 09:06

    stm32编译器优化

    #550-D: variable "d" was set but never used描述:变量'd'定义但从未使用,或者是,虽然这个变量你使用了,但编译器认为变量d所在的语句没有意义,编译器把它优化了.解决:仔细衡量
    发表于 08-24 07:46

    编译器优化那些事儿(5):寄存器分配

    。至此,图着色算法基本介绍完毕。不过,如果代码中的复制指令,应该怎么处理呢?寄存器分配之前会有Copy Propagation和Dead Code Elimination优化掉部分复制指令,但是两者
    发表于 08-24 14:41

    编译器优化的静态调度介绍

    约束条件进行联合求解得到的解决方案是相对更优的,但由于无论是指令调度还是寄存器分配,都是很复杂的NP完全问题,综合考虑下,编译器一般会分别处理二者。  在LLVM编译器的设计中,
    发表于 03-17 17:07

    ARM编译器优化版本1.0

    ARM编译器armcc可以优化您的代码以实现小代码和高性能。 本教程介绍了编译器执行的主要优化技术,并解释了如何控制编译器
    发表于 08-28 07:11

    寄存器组网络处理器上的寄存器分配技术

    本内容提供了多寄存器组网络处理器上的寄存器分配技术
    发表于 06-28 15:26 28次下载
    多<b class='flag-5'>寄存器</b>组网络处理器上的<b class='flag-5'>寄存器</b><b class='flag-5'>分配</b>技术

    编译器_keil的优化选项问题

    keil编译器优化选项针对ARM,对STM32编译的一些优化的问题
    发表于 02-25 14:18 3次下载

    高效的C编程之寄存器分配

    14.7 寄存器分配 编译器一项很重要的优化功能就是对寄存器分配。与
    发表于 10-17 17:17 4次下载

    编译器优化对函数的影响

    编译器如gcc,可以指定不同的优化参数,在某些条件下,有些函数可能会被优化掉。
    的头像 发表于 06-22 14:58 2620次阅读
    <b class='flag-5'>编译器</b><b class='flag-5'>优化</b>对函数的影响

    基于C++编译器的节点融合优化方法

    节点,减少诸如指令、寄存器、时钟周期和访存等开销,以达到减少程序运行时间,提升访存效率等目的。为了提升LLVM编译器的性能,文中在LLVM编译流程的中间表示阶段和DAG合并阶段、指令选择阶段提岀了节点融合
    发表于 06-15 14:29 19次下载

    编译器优化教程:寄存器分配 2

    接下来介绍一种不同思路的算法:线性扫描。算法描述如下[4]: ``` LinearScanRegisterAllocation: active := {} for i in live interval in order of increasing start point ExpireOldIntervals(i) if length(avtive) == R SpillAtInterval(i) else
    的头像 发表于 01-30 16:16 587次阅读
    <b class='flag-5'>编译器</b><b class='flag-5'>优化</b>教程:<b class='flag-5'>寄存器</b><b class='flag-5'>分配</b> 2

    什么是编译器算法之寄存器分配

    寄存器是CPU中的稀有资源,如何高效的分配这一资源是一个至关重要的问题。本文介绍了基于图着色的寄存器分配算法。
    的头像 发表于 03-02 16:11 722次阅读
    什么是<b class='flag-5'>编译器</b>算法之<b class='flag-5'>寄存器</b><b class='flag-5'>分配</b>

    怎么给D寄存器输入数值 三菱plc寄存器D怎么读取

    在单片机编程中,给D寄存器输入数值的方法取决于所使用的编程语言和编译器
    发表于 04-12 13:33 1.2w次阅读