预取

正如前面对CPU缓存的介绍,对于存放在内存中的数据,CPU处理相关数据一般需要先从内存取数据到L3,再从L3取数据到L2,再从L2取数据到L1,最后将L1中的数据取到寄存器中,这时候CPU才能对相关数据进行处理。如果CPU下一次需要处理的数据都在L1中,显然这样程序的性能会优于数据都在内存中。而对于预取来说,又可以分为硬件预取和软件预取两种。硬件预取指的由硬件根据访存的历史信息,对未来可能的访存单元预先取入Cache,从而在数据真正被用到时不会造成Cache失效,具有通用性。而软件预取是指程序员通过在业务代码中编写预取指令,对特定位置进行预取,具有针对性。

本文只要介绍基于鲲鹏平台进行编程,故只介绍软件预取。

软件预取

软件预取是通过预取指令实现的,不同架构提供的预取指令也不一样。在鲲鹏平台上,预取指令格式通常如下:

PRFM prfop, [Xn|SP{, #pimm}]

prfop由type<target><policy>三部分组成。

从指令组成看,预取指令中核心部分为prfop,其决定了预取的类型、预取cache层级以及预取的数据使用模式。本小节主要说明PLD数据预取,其他模式类似,数据预取核心指令部分有以下几种使用方式。

数据预取指令

指令功能说明

PLDL1KEEP

数据预取到L1 cache,策略为keep模式,数据使用后常驻Cache。

PLDL2KEEP

数据预取到L2 cache,策略为keep模式,数据使用后常驻Cache。

PLDL3KEEP

数据预取到L3 cache,策略为keep模式,数据使用后常驻Cache。

PLDL1STRM

数据预取到L1 cache,策略为strm模式,数据使用后从Cache淘汰。

PLDL2STRM

数据预取到L2 cache,策略为strm模式,数据使用后从Cache淘汰。

PLDL3STRM

数据预取到L3 cache,策略为strm模式,数据使用后从Cache淘汰。

GCC编译器针对预取也有对应的builtin函数实现,格式如下:

__builtin_prefetch (const void *addr, int rw, int locality)

其中:

未使用软件预取:

int add_vector(int *dst, int *src1, int *src2, int size) 
{
for (int index =0 ; index < size; index += 4) {
…  // do something   
}
}

使用软件预取:

static inline void prefetch(const void* data)
  {
   __asm__ __volatile__(
     "prfm PLDL1STRM, [%[data]]         \n\t"
     :: [data] "r" (data));
 }
 int add_vector(int *dst, int *src1, int *src2, int size)
  {
   for (int index =0 ; index < size; index += 4) {
      prefetch(src1 + index + 256);
      prefetch(src2 + index + 256);
      … // do something
    }
 }