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

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

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

#### 软件预取

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

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

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

- type可选模式有：

  - PLD：数据预加载
  - PLI：指令预取
  - PST：数据预存储
- <target>可选模式有：

  - L1
  - L2
  - L3
分别表示对三个不同的Cache层级进行操作。
- <policy>可选模式有：

  - KEEP：数据预取使用后保存一定时间，适用于数据多次使用的场景。
  - STRM：流式或非临时预取，数据使用后将淘汰，用于仅使用一次的数据。
  - Xn|SP：通常表示64位通用寄存器或栈指针，使用场景中通常为预取的起始地址。
  - pimm：以字节为单位的偏移量，取值为8的整数倍，范围是0到32760，默认为0。

从指令组成看，预取指令中核心部分为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)
```

其中：

- addr表示数据的内存地址。
- rw为可选参数，可设置为0或1，0表示读操作，1表示写操作。
- locality为可选参数，可设置0~3，表示数据在Cache中保持的时间，即时效性。取值为0表示访问的数据后续不再被访问，使用后在Cache中淘汰；取值为3表示访问的数据将再次访问；取值1和2则分别表示具有低时效性和中时效性，该值默认为3。

未使用软件预取：

```
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
}
}
```
