5.3 硬件预读
标签:
杂谈 |
分类: 浅谈CacheMemory |
采用硬件预读的优点是不需要软件进行干预,不会扩大代码的尺寸,不需要浪费一条预读指令来进行预读,而且可以利用任务实际运行时的信息(Run Time Information)进行预测,这些是硬件预读的优点。
硬件预读的缺点是预读结果有时并不准确,有时预读的数据并不是程序执行所需要的,比较容易出现Cache Pollution的问题。更重要的是,采用硬件预读机制需要使用较多的系统资源。在很多情况下,耗费的这些资源与取得的效果并不成比例。
硬件预读机制的历史比软件预读更为久远,在IBM 370/168处理器系统中就已经支持硬件预读机制。大多数硬件预读仅支持存储器到Cache的预读,并在程序执行过程中,利用数据的局部性原理进行硬件预读。
最为简单的硬件预读机制是OBL(One Block Lookahead)机制,这种方式虽然简单,但是在许多情况下效率并不低于许多复杂的实现,也是许多处理器采用的方式。OBL机制有许多具体的实现方式,如Always prefetch,Prefetch-on-miss和Tagged prefetch[23]。
在使用Always Prefetch OBL实现方式时,当一段程序访问数据块b时,只要数据块b+1没有在Cache中Hit,就对数据块b+1进行预读。这种方式的缺点是可能程序访问数据块b之后,将很长时间不使用数据块b+1,从而带来较为严重的Cache Pollution。使用这种方式时的Access Ratio为2。
在使用Prefetch-on-Miss OBL实现方式时,当程序对数据块b进行读取出现Cache Miss时,首先将数据块b从存储器更新到Cache中,同时预读数据块b+1至Cache中;如果数据块b+1已经在Cache中,将不进行预读。使用这种方式时的Access Ratio为1+Miss Ratio。
Always Prefetch和Prefetch-on-Miss OBL方式没有利用之前的历史信息,在某些应用中,容易造成Cache Pollution。Tagged Prefetch是Prefetch-on-Miss实现方式的一种改进,其实现相对较为复杂,也使用了额外的硬件资源。
在使用Tagged Prefetch OBL实现方式时,需要为每一个Cache Block设置一个Tag位,该位在复位或者当前Cache Block被替换时设置为0。如果当前Cache Block是因为Prefetch的原因从其下的存储器子系统中获得时,该位依然保持为0。
当前Cache Block在预读后第一次使用,或者是Demand-Fetched时,Tag位将从0转换为1,此时如果其后的数据块不在Cache Block时将进行预读[23]。这种方式与Prefetch-on-Miss的最大区别在于访问已经Prefetch到Cache中数据的处理。
当程序访问已经预读到Cache的Block时,在使用Prefetch-on-Miss方式时,不会继续预读下一个Cache Block,而使用Tagged Prefetch方式时,会继续预读下一个Cache Block,从而减少了Demand-Fetched的概率,其实现示意如图5‑4。
从上图可以发现,对于一个顺序访问的Access Patern,使用Prefetch-on-Miss方式,每次访问过一个Prefetched Cache Block后,都会出现一次Cache Miss;而是用Tagged Prefetch时仅会出现一次Cache Miss。
但是仅用这一种访问模型,并不能证明Tagged Prefetch一定由于Prefetch-on-Miss方式。Alan J. Smith [23]根据Miss Ratio,Access Ratio和Transfer Ratio三个参数对以上实现方式进行了较为细致的对比。从Access Ratio参数的上看,Always prefetch实现方式大于后两种方式。
与Prefetch-on-miss方式相比,Tagged prefetch实现方式在Access Ratio和Transfer Ratio没有明显提高的前提下,降低了50%~90%的Miss Ratio [23]。但是我们依然不能得出Tagged prefetch一定优于Prefetch-on-miss方式的结论。与其他方式相比,Tagged Prefetch方式每一个Cache Block多使用了一个Tag位,依然是某种程度的Trade-off。
Tagged Prefetch实现有许多衍生机制,比如可以将数据块b+1,b+2,…,b+k预读到Cache中。其中k为Prefetch的深度,当k为1时,即为标准的Tagged Prefetch。更有甚者提出了一种Adaptive Sequential Prefetching实现方式,此时k可以根据任务执行的Run Time信息进行调整,可以为正,也可以为负。
以上这些硬件预读算法都有其局限性,特别是在处理Strided Array相关的计算时,为此也产生了一系列可以利用Stride信息的硬件预读实现,如Lookahead Data Prefetching实现[102]。该实现的组成结构如图5‑5所示。
假设在一个3-Nested Loop
Iterations中,某条存储器访问指令mi需要陆续访问a1, a2和a3。当(a2- a1) = Δ ≠ 0时,需要对mi进行预读,Δ参数即为预读的Stride。第一次预读地址A3 = a2 +Δ,其中A3为预测值,如果预测与实际的a3相同,则继续预测,直到An ≠ an。采用这种实现方法,需要使用历史地址信息和最后一次检测成功的Δ参数,为此在硬件上需要设置一个RPT(Reference Prediction Table),RPT的组成结构与Cache类似,如图5‑6所示。
RPT由微架构的PC进行索引。当指令mi第一次执行时,将从RPT中分配一个空闲Entry,填写相应的Instruction Tag,Previous Address,并将state设置为initial状态。当指令mi第二次执行时,并在RPT中命中时,将根据当前的EA与Previous Address计算Atride参数后填入当前Entry,并将State设置为Transient状态。
此时如果地址(Effective
Adderss+Stride)所指向的数据没有在Cache中命中,进行Tantative Prefetch操作。当指令mi第三次执行时,在RPT中命中,而且A3与实际的a3相同时,表示发生了一次Correct stride Prediction,此时继续进行下一个地址的预读,同时将State改写为Steady。在RPT中,State的状态迁移如图5‑7所示。
根据图5‑7的状态迁移关系,我们考察以下实例,如图5‑8所示。其中左图为一个3-Nested Loop Iterations,并对数据a进行赋值操作,其中数组a,b和c使用的Stride参数并不相同。但是在一下程序中,数据a,b和c使用的Stride参数依然具有强烈的规律性,在RPT中分别保存着这些规律,从而在一定程度上提高了预读的准确性。
假设数组a, b和c的基地址分别以10,000, 20,000和30,000对界。在第一次进行运算时,通过计算可以在RPT表中记录相应的Previoud Address,数组a,b和c的Stride参数为初始值0,而State为初始状态Initial。
在第一次Iteration之后,RPT表中的数组b和c的Stride分别为4和400(Current Address与Previous Address之差),State改变为Transient,并开始预读之后的Cache Block,而通过计算数组a的Stride为0,与之前的值相同,State改变为Steady,即不进行预读;在第二次Iteration之后,RPT中的数组b, c和a发现Stride没有再次发生变化时,State改变为Steady,开始稳定地进行预读。
在第一重循环k执行完毕后,由于k的变化,将使RPT的数组c进入Initial状态,重新进入准备阶段;第二重循环j执行完毕后,由于j的变化,将使RPT的数组a和c进入Initial状态,重新进入准备阶段;第三重循环i执行完毕后,由于i的变化,将使RPT的数组a和b进入Initial状态,重新进入准备阶段。
周而复始,直到三重循环完全执行完毕。
采用这种硬件预读方法,可以有效解决在Loop Iterations中数据的Stride问题。在进一步考虑了Prefetch Distance,即δ参数的基础上,Lookahead data prefetching算法可以在此基础上继续优化,可以设置一个LA-PC(Lookahead Program Count)。此时预读的地址Prefetch Address等于Effective Addess + (Stride × δ),LA-PC与PC的差值即为δ。
在某些情况下,基于RPT的预读机制并不能理想地处理Triangle-Shaped Loop,这种Loop访问Stride值的计算不但与自身有关,而且与相邻的Loop直接相关。采用Correlated Reference Prediction预读机制[103]可以有效解决这一问题。
该机制的实现要点是除了关注在一个Loop内的数据访问轨迹之外,还关心相邻的Loop,以实现对Triangle-Shaped Loop的预读。为此在图5‑6中需要加入另外一组Prev Address和Stride参数,对此有兴趣的读者可参阅[103]以获得更详细的信息。
无论是软件还是硬件Prefetch的实现方式,都不可避免地出现Prefetch得来的数据并没有被及时使用,从而会在一定程度上一定程度上的重复,这种重复会进一步提高系统功耗,对于有些功耗敏感的应用,需要慎重使用Prefetch机制。Prefetch机制除了对系统有较大影响之外,还会引发一定程度的Cache Pollution。这使得Stream buffer[20]机制因此引入。

加载中…