5.1 数据预读

Prefetch指在处理器进行运算时,提前通知存储器子系统将运算所需要的数据准备好,当处理器需要这些数据时,可以直接从这些预读缓冲中,通常指Cache,获得这些数据,不必再次读取存储器,从而实现了存储器访问与运算并行,隐藏了存储器的访问延时。Prefetch的实现可以采用两种方式,HB(Hardware-Based)SD(Software-Directed)。这两种方法各有利弊,我们首先以51为基础模型讨论采用SD方式的数据预读。

5.1 <wbr>数据预读

其中实例a没有使用预读机制;实例b是一个采用预读机制的理想情况;实例c是一个采用预读机制的次理想情况。我们假设处理器执行51所示的任务需要经历四个阶段,每个阶段都由处理器执行运算指令和存储指令组成。

在其中处理器的一次存储器访问需要5个时钟周期。在第一个阶段处理器执行4个时钟周期后需要访问存储器;在第二个阶段处理器执行6个时钟周期后需要访问存储器;在第三个阶段处理器执行8个时钟周期后需要访问存储器;在第四个阶段处理器执行4个时钟周期后完成整个任务。

实例a没有使用预读机制,在运算过程中,在进行存储器访问,将不可避免的出现Cache Miss。执行上述任务共需40个时钟周期。使用预读机制可以有效缩短整个执行过程。在实例b中在执行过程中,会提前进行预读操作,虽然这些预读操作也会占用一个时钟周期,但是这些预读操作是值得的。合理使用这些数据预读,完成同样的任务CPU仅需要28个时钟周期,从而极大提高了程序的执行效率。

这种情况是非常理想的,处理器在执行整个任务时,从始至终是连贯的,处理器执行和存储器访问完全并行,然而这种理想情况并不多见。在一个任务的执行过程中,并不容易确定最佳的预读时机;其次采用预读所获得数据并不一定能够被及时利用,因为在程序执行过程中可能会出现各种各样的分支选择,有时预读的数据并没有被及时使用。

在实例c中,预读机制没有完全发挥作用,所以处理器在执行任务时,Cache Miss仍会发生,减低了整个任务的执行效率。即便这样,实例c也比完全没有使用预读的实例a的任务执行效率还是要高一些。在实例c中,执行完毕该任务共需要34个时钟周期。当然我们还可以轻松出采用预读使51中的实例执行的更加缓慢。

51中的实例可以使用硬件预读的方式。但是无论采用什么方式,都需要注意预读的数据需要及时有效,而且在产生尽可能小的Overhead的基础上供微架构使用。在实例cr1r2中,预读操作过晚,因此指令流水依然会Stall,从而影响执行效率。

r3中,预读操作过早,虽然数据可以提前进入某个Cache Block,但是这意味着过早预读的数据可能会将某个将要使用的Cache Block替换出去,因此CPU Core可能会重新读取这个被替换出去的Cache Block,从而造成了Cache Pollution。除此之外每一个Cache Block有自己的MLS,过早预读的数据,有可能被其他存储器访问替换出去,当CPU Core需要使用时,该数据无法在Cache中命中。

因此在进行数据预读时,需要首先重点关注时机,不能过早也不能过晚。如果考虑多处理器系统,无论是采用HB或者SD方式,做到恰到好处都是巨大的挑战。除了预读时机之外,需要进一步考虑,预读的数据放置到Cache Hierarchy的哪一级,L1L2还是LLC,所预读的数据是私有数据还是共享数据。需要进一步考虑预读数据的Granularity,是By Word, ByteCache Block,还是多个Cache Block;需要进一步考虑是否采用HBSD的混合方式。这一切增加了Prefetch的实现难度。

这也造成了在某些情况下,采用预读机制反而会降低效率。什么时候采用预读机制,关系到处理器系统结构的各个环节,需要结合软硬件资源统筹考虑,并不能一概而论。处理器提供了必备的软件和硬件资源以实现预读,如何“合理”使用预读机制是系统程序员考虑的一个细节问题。数据预读可以使用软件预读或者硬件预读两种方式实现,下文将详细介绍这两种实现方式。

软件和硬件预读策略所追求的指标依然是CoverageAccuracyTimeliness[101]CoverageCPU Core需要的数据有多少是从Prefetcher中获得,而不是访问存储器子系统;AccuracyPrefetched Cache中有多少数据是CPU Core真正需要的;Timeliness指预读的数据是否能够恰到好处的到达,不能太早也不能太晚。

在已知的软件或者硬件预读策略主要针对以上三个参数展开,这些策略的底线是预读所使用的开销不大于于不使用预读机制时Cache Miss的开销。在许多情况下,采用预读策略不仅不会提高程序的执行效率,甚至会极大影响程序的正常执行,带来严重的系统惩罚,最终结果不如放弃这些预读机制。

我们需要对预读算法进行定性分析。假设Prefetch Ratio参数指由于Prefetch而读取的Cache Block总数在所有存储器访问的Cache Block中所占的比率;Transfer RatioPrefetch RatioMiss Ratio之和。

Access Ratio指所有Cache的访问次数与Prefetch Lookup之间的比值。所有Cache的访问次数是ActualPrefetch Lookup之和。其中Prefetch Lookup指由Prefech算法决定当前Cache Block是否应该替换,是否应该Prefetch新的Cache Block而引发的Cache访问,是由Cache Controller主动发起的Cache访问操作;Actual LookupCache Controller之外的访问操作,如CPU Core或者外部设备对Cache的访问操作。Access Ratio的值大于1

在此基础之上,我们进一步引入参数DPA。其中参数DDemand Miss所带来的PenaltyDemand Miss指没有采用预读而产生的Cache Miss开销;参数P为预读的代价,包括数据读入,因为读入新的数据而Replacement旧的数据,等各类因为预读导致的数据传递的开销;参数A为因为预读干扰了程序对Cache正常使用而带来的惩罚。在这种情况下,一个有效的预读算法需要满足公式51

5.1 <wbr>数据预读

如果出现MissRatio(Prefetch)大于Miss Ratio(Demand)的情况,即便PAPrefetch Ratio参数为0,上述公式也无法成立。这种情况是使用预读机制所造成的最糟糕结果。此时预读造成Cache Pollution,使得Cache Miss Ratio反而低于与没有使用预读的情况

硬件还是软件预读机制都会造成这种情况。与硬件预读相比,软件预读更加灵活一些。但是在很多情况之下,我并不喜欢使用编译器强行加入的预读处理,倾向根据微架构和应用的具体要求,书写这些预读代码。有时由编译器增加的预读代码除了进一步污染指令Cache之外,不会带来更多帮助。这不是否认编译器的努力,而是提醒读者需要因地制宜。

你可能感兴趣的:(浅谈CacheMemory)