文档
注册
评分
提单
论坛
小智

软件预取

原理

数据预取通过将代码中后续可能使用到的数据提前加载到cache中,减少CPU等待数据从内存中加载的时间,提升cache命中率,进而提升软件的运行效率。预取指令格式通常如下:

PRFM  prfop,  [Xn|SP{, #pimm}]
  1. prfop由type <target> <policy>三部分组成。
    • type可选模式如下。
      • PLD:数据预加载
      • PLI:指令预取
      • PST:数据预存储
    • <target>可选模式如下。
      • L1
      • L2
      • L3

      L1、L2、L3分别表示对三个不同的cache层级进行操作。

    • <policy>可选模式如下。
      • KEEP:数据预取使用后保存一定时间,适用于数据多次使用的场景。
      • STRM:流式或非临时预取,数据使用后将淘汰,用于仅使用一次的数据。
  1. Xn|SP通常表示64位通用寄存器或栈指针,使用场景中通常为预取的起始地址。
  1. pimm是以字节为单位的偏移量,代表预取的字节长度,取值为8的整数倍,范围是0~32760,默认为0。预取长度可结合实际业务场景设定,尝试预取不同长度数据,获取最佳预取值。

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

表1 数据预取核心指令

数据预取指令

指令功能说明

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)
  • addr:数据的内存地址。
  • rw:可选参数。rw可设置为0或1,0表示读操作,1表示写操作。
  • locality:可选参数。locality可设置0-3(默认为3),表示数据在cache中保持的时间,即时效性。取值为0表示访问的数据后续不再被访问,使用后在cache中淘汰;取值为3表示访问的数据将再次访问;取值为1和2,则分别表示具有低时效性和中时效性。

更多关于预取指令的描述可参考arm指令集手册:

https://developer.arm.com/documentation/ddi0596/2021-06/Base-Instructions/PRFM--immediate---Prefetch-Memory--immediate--?lang=en

修改方式

数据预取通常可以观察热点函数中LDR等数据加载指令的上下文代码,在代码中嵌入数据预取操作,常见的是在循环当中进行。在C/C++代码中,通常使用内嵌汇编形式调用预取指令,函数声明为inline形式,举例如下:
// 从ptr处预读取128字节数据 
void inline Prefetch(int *ptr)  
{
    __asm__ volatile("prfm PLDL1KEEP, [%0, #(%1)]"::"r"(ptr), "i"(128));
}

PLDL1KEEP指令组成如下:

PLDL1KEEP表示将数据预取到L1 cache中,策略为keep模式。数据使用完后,将保留一定时间,适用于数据多次使用的场景。

以下示例代码功能为两数组对应元素相乘,通过每次预取多个数据,并将循环展开来对预取数据进行使用,提升计算性能,代码如下。

for (int i = 0; i < ARRAYLEN; i++) {
    arrayC[i] = arrayA[i] * arrayB[i];
}   

添加预取:

int i;
Prefetch(&arrayA[0]);
Prefetch(&arrayB[0]);
for (i = 0; i < ARRAYLEN - ARRAYLEN % 4; i+=4) {
    Prefetch(&arrayA[i + 4]);
    Prefetch(&arrayB[i + 4]);
    arrayC[i] = arrayA[i] * arrayB[i];
    arrayC[i + 1] = arrayA[i + 1] * arrayB[i + 1];
    arrayC[i + 2] = arrayA[i + 2] * arrayB[i + 2];
    arrayC[i + 3] = arrayA[i + 3] * arrayB[i + 3];
}
for (; i < ARRAYLEN; i++) {
    arrayC[i] = arrayA[i] * arrayB[i];
}

通过测试耗时分别为:使用预取耗时5569us,不使用预取耗时9359us,优化后代码性能显著提升。

搜索结果
找到“0”个结果

当前产品无相关内容

未找到相关内容,请尝试其他搜索词