[Computer Architecture读书笔记] 3.2 Basic Compiler Techniques for Exposing ILP

垃圾内容,读书笔记,勿看

本书重在Computer Architecture,所以对compiler optimization没有做重点介绍,本章节算是对一些compiler optimization进行了科普。理解本章节,可以按照“计算机体系结构就是trade-off”这个主旨进行。囿于现实因素,存在很多限制,而编译优化就是在夹缝中就生存,在诸多条条框框中求得一个近优解。

3.2 Basic Compiler Techniques for Exposing ILP

类似于图灵机,由于现实的束缚,不可能存在无限长“纸带”的机器,就连地址无限长都做不到。现实附加了很多约束,例如资源有限的DRAM和磁盘。约束分为两类,理论约束实现约束

for (i = 999; i >= 0; i = i -1) 
	x[i] = x[i] + s;

上述代码的理论约束有二:

  • 必须在 x[999] -> x[0] 每个元素上面加上s

这意味着,

  • 你按照什么样的形式遍历x无所谓,例如无论你是一次迭代4个元素,还是一次迭代999个元素;
  • 你按照什么样的形式加s也无所谓,例如你是 + (s + 1) - 1 还是 + (s - 1) + 1都无所谓。

这是理论上的upper bound,我们在实现的时候不可能改变语义,法无禁止皆自由只要“能力”够强,随便你玩儿😛)。

但是现实也有一个暂时的upper bound,例如在intel最新的芯片上,向量化的能力只能到512bits,一次只能处理64个bytes,“暂时”是说未来的技术会继续发展,最终触碰到物理基线(我这就有点儿瞎扯淡了)。现实的芯片有诸多类似的限制,例如branch miss带来的影响,cache miss带来的影响,DRAM与cpu的速度差等等。

所以编译器的作用就是基于程序语义,来尽可能的逼近“现实极限”。例如有cache、register以及pipeline三个约束的话,那么编译器就是在这个三个约束的情况下去优化程序,关键是这三个约束还不是正交的。在A约束的情况下优化了性能,但是由于B约束的原因,性能可能会恶化。

本书以RISC-V为例,生成的naive代码假设如下所示,可以看到这里的约束有指令的latency以及指令间的依赖,下面的代码需要8个时钟周期,3次stall。
原书示例
注:摘自原书

单看这个loop body,遵循理论依赖的前提下,优化的空间不多了。可以将addi x1, x1, -8指令调度到fld指令的后面来减少一个stall。基于前面的分析,代码并没有规定遍历与赋值的具体规则和实现。基于此,我们可以一次处理两个element。

for (i = 999; i >= 0; i = i -2) {
	x[i] = x[i] + s;
	x[i - 1] = x[i - 1] + s;
}
x[0] = x[0] + s;

整个iteration的次数降下来了,这样的话,我们就可以节省loop overhead相关的指令,例如addi x1, x1, -8以及bne指令。

Loop:
# ----- 1st iteration -------
fld       f0,0(x1) 
stall
fadd.d    f4,f0,f2 
stall
stall
fsd       f4,0(x1)       # drop addi & bne

# ----- 2nd iteration -------
fld       f6,-8(x1) 
stall
fadd.d    f8,f6,f2 
stall
stall
fsd       f8,-8(x1)       # drop addi & bne

# ----- 3rd iteration -------
fld       f0,-16(x1)  
stall
fadd.d    f12,f0,f2 
stall
stall
fsd       f12,-16(x1)     # drop addi & bne
                           # ((x1 - 8) - 8) -> x1 - 16

# ----- 4th iteration -------
fld       f14,-24(x1) 
stall
fadd.d    f16,f14,f2 
stall
stall
fsd       f16,-24(x1)     # drop addi & bne
                           # (((x1 - 8) - 8) - 8) -> x1 - 24

# loop overhead
addi      x1,x1,32 
bne       x1,x2,Loop

首先把loop控制相关的指令给删除了,这都是为了便于程序员理解的的俗物,快给删掉,编译器帮你安排的明明白白的。在满足理论约束的情况下,为了进一步提升性能,出现了指令调度的技术,loop unroll之后可以在更大范围内进行指令调度,进一步的逼近现实upper bound。但是loop unroll增加了寄存器的使用,并且提高了code size,所以loop unroll的缺点也很明显,这个在最后面再说。

经过指令调度后,减少了stall,同时也可以enable向量化优化。

Loop:
fld     f0,0(x1) 
fld     f6,8(x1) 
fld     f0,16(x1) 
fld     f14,24(x1) 
fadd.d  f4,f0,f2 
fadd.d  f8,f6,f2 
fadd.d  f12,f0,f2 
fadd.d  f16,f14,f2 
fsd     f4,0(x1) 
fsd     f8,8(x1) 
fsd     f12,16(x1) 
fsd     f16,8(x1) 
addi    x1,x1,32 
bne     x1,x2,Loop
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值