微信公众号 MindShare思享
CPU Speculation
上文说过为了提高CPU的性能,CPU会有speculation的行为,Speculation可以以很多方式实现,比如Speculation memory access,Speculation instruction execution等。
Speculation memory access 可以是speculativecache preload, speculation load.
Cache speculative prefetch
为了提供CPU的memory 访问效率,特别是比较大块的数据访问,比如memcpy。通常可以假设数据访问时有规则的,比如是连续数据访问,或是以固定的地址间隔(stride)访问。这样的话CPU的cache hardware可以做一些优化。在很多的CPU里(几乎所有的Cortex-A CPU)中cache会自动监测CPU访问的pattern,在下面的例子里,如果代码的前三个LDR都导致了cache miss,那么cache hardware就会开始推测software是想做连续的访问,在做第三个LDR的时候(还不知道后面software code会不会真的继续连续访问),CPU会开始预取(speculative prefetch)第四个cache line数据。
大多数情况下,这是成立的,因为实际的软件里的确很多类似memcpy的大块数据访问。因此这种硬件优化在Intel,Arm的CPU里非常常见。
除了监测连续的访问,Cache还可以监测隔几个cache lines的stride访问方式,比如
Addr,
Addr+2*cache_line_size,
Addr+4*cache_line_size,
更有甚者,Cache还可以同时监测几个pattern的访问,比如一个隔2个stride和一个隔5的stride.
Speculative load instruction execution
很多时候软件代码里有control dependency, 导致一些hazard,
在上面的代码里先从memory里read一个值,再比较这个值是否是0,
如果不是的话跳到else地方执行,
如果是的话从r4的地址里读出一个值。
如果不幸第一load出现TLB miss或是cache miss,那么比较和跳转指令就可能不能继续了,出现pipeline stall.
幸好我们有out of order和speculation技术,它基本上要利用register renaming来实现。
Register Renaming
有了register renaming可以减少register dependency带来的hazard, 实现方式是,
代码里的寄存器比如r0, 它只是architecture 寄存器,当指令被fetech, decode后,到了dispatch阶段,architecture寄存器会被map到真正的physical registers上,通常physical register个数会比architecture 寄存器多不少。
在Dispatch阶段,可以做如下的architecture register到physical register的map,
MapTable |
|||||
r1 |
r2 |
r3 |
原始指令 |
Map后的指令 |
|
p1 |
p2 |
p3 |
add r2,r3,r1 |
add p2,p3,p4 |
|
p4 |
p2 |
p3 |
sub r2,r1,r3 |
sub p2,p4,p5 |
|
p4 |
p2 |
p5 |
mul r2,r3,r3 |
mul p2,p5,p6 |
|
p4 |
p2 |
p6 |
div r1,4,r1 |
div p4,4,p7 |
我们可以通过这样的办法对上面的指令做这样的map,
这样的话我们就可以speculative执行第二个LDR,因为speculative执行只会改变physical寄存器的值,如果之后条件不满足,只是去掉这个map,不回写就好了。
但记住数据确实进入过cache,虽然最终程序没看到这个数据。
需要注意的是,一般来说只会做speculative的读(load)操作,而不会做speculative的写操作。因为读的结果比较容易放弃,但是写speculation的话,比较能将speculation做的事情放弃,代价会比较高。
下节将介绍cacheside channel attack.