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

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

3天内不再提示

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

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

线性扫描

接下来介绍一种不同思路的算法:线性扫描。算法描述如下[4]:

LinearScanRegisterAllocation:
	active := {}
	for i in live interval in order of increasing start point
		ExpireOldIntervals(i)
		if length(avtive) == R
			SpillAtInterval(i)
		else
			register[i] := a regsiter removed from pool of free registers
			add i to active, sorted by increasing end point
ExpireOldInterval(i)
	for interval j in active, in order of increaing end point
		if endpoint[j] >= startpoint[i]
			return
		remove j from active
		add register[j] to pool of free registers
SpillAtInterval(i)
	spill := last interval in active
	if endpoint[spill] > endpoint[i]
		register[i] := register[spill]
		location[spill] := new stack location
		remove spill from active
		add i to active, sorted by increasing end point
	else
		location[i] := new stack location

live interval其实就是变量的生命期,用活跃变量分析可以算出来。不过需要标识第一次出现和最后一次出现的时间点。

举个例子:

图片

图10

变量名 live interval
a 1,2
d 2,3,4,5
e 3,4,5,6

llvm中实现

在上文中介绍的算法都是作用在最普通的四元式上,但LLVM-IR是SSA形式,有PHI节点,但PHI节点没有机器指令表示,所以在寄存器分配前需要把PHI节点干掉,消除PHI节点的算法限于篇幅不展开,如感兴趣的话请后台留言。

llvm作为工业级编译器,有多种分配算法,可以通过llc的命令行选项-regalloc=pbqp|greedy|basic|fast来手动控制分配算法。

不同优化等级默认使用算法也不同:O2和O3默认使用greedy,其他默认使用fast。

fast算法的策略很简单,扫描代码并为出现的变量分配寄存器,寄存器不够用就溢出到内存。用途主要是 调试

basic算法以linearscan为基础并对life interval设置了溢出权重而且用优先队列来存储life interval。

greedy算法也使用优先队列,但特点是先为生命期长的变量分配寄存器,而短生命期的变量可以放在间隙中,详情可以参考[5]。

pbqp算法全称是Partitioned Boolean Quadratic Programming,限于篇幅,感兴趣的读者请查阅[6]。

至于具体实现,自顶向下依次是:

  • TargetPassConfig::addMachinePasses含有寄存器分配和其他优化
  • addOptimizedRegAlloc中是与寄存器分配密切相关的pass,比如上文提到的消除PHI节点
  • addRegAssignAndRewriteOptimized是实际的寄存器分配算法
  • 寄存器分配相关文件在lib/CodeGen下的RegAllocBase.cpp、RegAllocGreedy.cpp、RegAllocFast.cpp、RegAllocBasic.cpp和RegAllocPBQP.cpp等。
  • RegAllocBase类定义了一系列接口,重点是selectOrSplit和enqueue/dequeue方法,数据结构的重点是priority queue。selectOrSplit方法可以类比上文中提到的SpillAtInterval。priority queue类比active list。简要代码如下:
void RegAllocBase::allocatePhysRegs() {
  // 1. virtual reg其实就是变量
  while (LiveInterval *VirtReg = dequeue()) {

    // 2.selectOrSplit 会返回一个可用的物理寄存器然后返回新的live intervals列表
    using VirtRegVec = SmallVector4>;
    VirtRegVec SplitVRegs;
    MCRegister AvailablePhysReg = selectOrSplit(*VirtReg, SplitVRegs);
 // 3.分配失败检查
    if (AvailablePhysReg == ~0u) {
     ...
    }
 // 4.正式分配
    if (AvailablePhysReg)
      Matrix->assign(*VirtReg, AvailablePhysReg);
 
    for (Register Reg : SplitVRegs) {
      // 5.入队分割后的liver interval
      LiveInterval *SplitVirtReg = &LIS->getInterval(Reg);
      enqueue(SplitVirtReg);
    }
  }
}

至于这四种算法的性能对比,我们主要考虑三个指标:运行时间、编译时间和溢出次数。

图片

图11 各算法的运行时间,图源[6]

横坐标是测试集,纵坐标是以秒为单位的运行时间

图片

图12 各算法的编译时间,图源[6]

横坐标是测试集,纵坐标是编译时间

图片

图13 各算法的溢出次数,图源[6]

从这三幅图可以看出greedy算法在大多数测试集上都优于其他算法,因此greedy作为默认分配器是可行的。

小结

我们通过一个例子介绍了活跃变量分析和图着色算法。借助活跃变量分析,我们知道了变量的生命期,有了变量生命期建立干涉图,对干涉图进行着色。如果着色失败,可以选择某个变量溢出到内存中。之后在RIG的基础上介绍了寄存器合并这一变换。

然后我们简单介绍了不同思路的寄存器分配算法:linearscan。最后介绍了llvm12中算法的实现并对比了llvm中四种算法的性能差异。

参考

  1. http://www.cs.cmu.edu/afs/cs.cmu.edu/academic/class/15745-s18/www/lectures/L5-Intro-to-Dataflow-pre-class.pdf
  2. http://web.cecs.pdx.edu/~mperkows/temp/register-allocation.pdf
  3. http://www.cs.cmu.edu/afs/cs.cmu.edu/academic/class/15745-s18/www/lectures/L12-Register-Allocation.pdf http://www.cs.cmu.edu/afs/cs.cmu.edu/academic/class/15745-s18/www/lectures/L13-Register-Coalescing.pdf
  4. http://web.cs.ucla.edu/~palsberg/course/cs132/linearscan.pdf
  5. http://blog.llvm.org/2011/09/greedy-register-allocation-in-llvm-30.html
  6. T. C. d. S. Xavier, G. S. Oliveira, E. D. d. Lima and A. F. d. Silva, "A Detailed Analysis of the LLVM's Register Allocators," 2012 31st International Conference of the Chilean Computer Science Society, 2012, pp. 190-198, doi: 10.1109/SCCC.2012.29.
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 寄存器
    +关注

    关注

    31

    文章

    5280

    浏览量

    119738
  • 代码
    +关注

    关注

    30

    文章

    4708

    浏览量

    68167
  • 编译器
    +关注

    关注

    1

    文章

    1613

    浏览量

    48991
收藏 人收藏

    评论

    相关推荐

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

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

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

    编译器,有多种分配算法,可以通过llc的命令行选项-regalloc=pbqp|greedy|basic|fast来手动控制分配算法。不同优化等级默认使用算法也不同:O
    发表于 08-24 14:41

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

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

    SIMD计算机的优化编译器设计

    利用处理的相关资源,提高编译器优化性能和增强代码可适应性是SIMD处理优化编译的关键。该文基
    发表于 04-03 08:47 30次下载

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

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

    C编译器及其优化

    本章将帮助读者在ARM处理上编写高效的C代码。本章涉及的一些技术不仅适用于ARM处理,也适用于其他RISC处理。本章首先从ARM编译器及其优化
    发表于 10-17 17:22 2次下载

    静态变量、自动变量与寄存器变量的存储

    register限定词通知编译器--程序中的变量将频繁使用。它的意思是建议编译器将程序中用register限定的变量放置在计算机的内部寄存其中,这样可能得到更小更快的程序。但是,编译器
    发表于 06-03 11:27 3028次阅读
    静态变量、自动变量与<b class='flag-5'>寄存器</b>变量的存储

    编译器优化对函数的影响

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

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

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

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

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

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

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

    编译器优化选项

    一个程序首先要保证正确性,在保证正确性的基础上,性能也是一个重要的考量。要编写高性能的程序,第一,必须选择合适的算法和数据结构;第二,应该编写编译器能够有效优化以转换成高效可执行代码的源代码,要做到
    的头像 发表于 11-24 15:37 821次阅读
    <b class='flag-5'>编译器</b>的<b class='flag-5'>优化</b>选项

    Keil编译器优化方法

    我们都知道,代码是可以通过编译器优化的,有的时候,为了提高运行速度或者减少代码尺寸,会开启优化选项。
    的头像 发表于 10-23 16:35 160次阅读
    Keil<b class='flag-5'>编译器</b><b class='flag-5'>优化</b>方法