数据预取通过将代码中后续可能使用到的数据提前加载到cache中,减少CPU等待数据从内存中加载的时间,提升cache命中率,进而提升软件的运行效率。预取指令格式通常如下:
PRFM prfop, [Xn|SP{, #pimm}]
L1、L2、L3分别表示对三个不同的cache层级进行操作。
从指令组成看,预取指令中核心部分为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)
更多关于预取指令的描述可参考arm指令集手册:
// 从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,优化后代码性能显著提升。