数据依赖和控制依赖分析是许多优化的重要工具,比如指令调度、激进死代码删除(ADCE)等。

数据依赖 Data Dependence

数据依赖的概念

数据依赖是数据流之间的一种依赖,主要包括四种(参考鲸书)。

1.流依赖/真依赖(flow/true dependence):S1定义了一个值,随后S2使用了这个值。记 S 1   δ f   S 2 S1\ \delta^f\ S2 S1 δf S2

a = b + c  // S1
d = a + c  // S2

如上例子,S1给a定值, S2使用a。

2.反依赖(anti-dependence):S1使用了一个值,而随后S2定义了这个值。记 S 1   δ a   S 2 S1\ \delta^a\ S2 S1 δa S2

a = b + c  // S1
b = c + d  // S2

如上例,S1使用了b,而S2定值了b。

3.输出依赖(output dependence):两个语句都定义了一个值。记 S 1   δ o   S 2 S1\ \delta^o \ S2 S1 δo S2

a = b + c  // S1
a = 3      // S2

如上例,S1和S2都定值了a。

4.输入依赖(input dependence):两个语句都使用了一个值。记 S 1   δ i   S 2 S1\ \delta^i \ S2 S1 δi S2

a = b + c  // S1
d = b + 2  // S2

如上例,S1和S2都使用b。

以上4种数据依赖,前3种依赖都约束了语句的执行顺序,也即不能打乱S1和S2前后执行顺序。

其实,还存在一种情况:不能确定两条语句是否是否可以乱序执行。比如两条使用到不同寄存器进行间接寻址的指令,静态分析时我们可能无法确定两条指令访问的内存是否存在重叠。

r2   <--- [r1]   // S1
[r3] <--- r4     // S2

这个例子中,如果不确定r1和r3中存储的地址是否存在重叠,我们只能做保守处理,认为这两条语句存在依赖。

基本块依赖DAG

DAG

在一个基本块内,常用DAG(Directed Acyclic Graph)来表示指令的数据依赖。在DAG中节点表示语句,边表示依赖关系,如果S2必须S1执行了若干拍之后才能执行,则节点S1是S2的前驱。使用一个整数来标记边,这个整数是S1和S2之间要求的等待时间(整数为0时可以省略),等待时间是从S1开始执行到S2开始可以执行之间的延迟减去其他任何指令可以开始执行之前S1所需的执行时间(通常为一拍,但在超标量系统中常常为0)。

   r2 <-- [r1]      // S1
   r3 <-- [r1 + 4]  // S2
   r4 <-- r2 + r3   // S3
   r5 <-- r2 -1     // S4

            S1       S2
           /  \     /
        1 /   1\   /1
         V      V V
        S4      S3

DAG的拓扑排序

拓扑排序:任意一条边 n->m,节点 n 都先于节点 m。

DAG的构建方法有两种:

  • 定义法。首先找到DAG中没有入边的顶点,删除它;然后对剩余顶点重复上述过程。
  • DFS。使用DFS得到post-order,再得到reverse post-order。

DAG 的构建算法可以戳我另外两篇博客:
拓扑排序(Topological Sorting):Kahn 算法和 DFS 算法
图,树,遍历顺序 Graphs,Trees and Traversal Oreder

编译器中有一个pass叫做”指令调度“,可以借助DAG的拓扑排序得到较优流水线的指令序列。根据拓扑排序的定义可以知道,满足拓扑排序的指令序列肯定可以保证功能的正确性。但是不同的拓扑排序得到的指令序列性能会有差异。
在这里插入图片描述
上图的例子,4到8的边其实是多余的,因为存在了4到6到8的边。但是如果将4到8的边等待时间改为4则这个边不是多余的。1-2-4-5-6-7-8-3和1-2-4-5-6-8-7-3两种拓扑排序都是可行的调度,但是后一种排序比前一种多出一拍。

控制依赖 Control Dependence

控制依赖的概念

控制依赖是程序控制流导致的一种约束。例如:

    if (a > 3) goto L1  // S1
        b = 2           // S2
        c = b + 3       // S3
L1: d = 5               // S4

S2和S3仅在S1中条件不满足时候才执行。记 S 1   δ c   S 2 S1\ \delta^c\ S2 S1 δc S2 S 1   δ c   S 3 S1\ \delta^c\ S3 S1 δc S3。。

控制依赖图 CDG

(鲸书)对于一个有向图 G = < N , E > G = <N, E> G=<N,E>,节点n控制依赖于节点m,当且仅当:

  • 存在一条m到n的控制路径,n是该路径上除m之外每个节点的后支配节点,并且
  • n不是m的后支配节点。

通过求解有向图中节点的控制依赖关系得到控制依赖图(CDG)。虎书中介绍了一种构建CDG的方法:
在这里插入图片描述
Dominator和Dominance Frontier相关概念和算法戳我的博客:
支配节点树及其构建算法 Dominator-tree and its Construction Algorithms
支配边界及其构建算法 Dominance Frontier and its construction algorithm
在这里插入图片描述

CDG的一个应用:激进死代码删除ADCE

死代码删除(DCE,Dead Code Elimination)和激进死代码删除(ADCE,Aggressive Dead Code Elimination)是编译中常见的优化pass。相较于DCE,ADCE会删除冗余的分支。

ADCE的基本算法:

  • 将输入、输出、存储至存储器、有副作用等语句标记成活跃的;
  • 将那些对这些有效语句有贡献的语句标记成活跃的(通常使用du链);
  • 对于条件分支语句,其他活跃语句控制依赖于该语句,也标记成活跃的;
  • 最后删除不活跃的语句。

在这里插入图片描述

值得注意的是:ADCE会删除没有输出的无限循环,这在有些场景下是不可接受的。

参考

  • [1] 高级编译器设计与实现
  • [2] 现代编译原理C语言描述(修订版)
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐