16.2.2 预取
通过给 CPU 发送一个特殊的 hint 来表明之后某段内存区域马上会被访问,是可能的。在 Intel 64 架构上使用 prefetch 指令来达到这个目的。该指令接收一个内存地址;CPU 会在最近尽量将这些内容载入到缓存中。这样做可以防止 cache miss。
prefetch 使用得好可以很高效,不过一定要进行测试。prefetch 操作应该在数据被访问前进行,但不能和执行执行的时间间隔太接近。缓存的预载是异步进行的,也就是说数据载入和紧跟着的指令执行几乎是同时发生的。如果预取和数据访问时间太接近的话,CPU 可能来不及把数据载入到 cache,数据访问就发生了。这时候就会有 cache-miss。
另外还需要理解一点,与数据访问操作距离的 “近” 和 "远" 指的是指令执行的序列中的指令位置。考虑到程序的结构,我们不应该把 prefetch 放在同一函数内,而应放在数据放问之前的其它位置。可以放在完全不同的另外一个模块,例如,在日志模块中,一般在数据访问之前很可能会被调用到。不过真的这样做将会对代码的可读性造成极大的负面影响,并引入不明显的模块间依赖,这里说的是一种破罐破摔的技巧了。
在 C 语言中使用 prefetch,只需要使用 GCC 内置的:
Void __builtin_prefetch (const void *addr, ...)
编译器会自动把这个 prefetch 替换为对应架构下的 prefetch 汇编指令。
除了地址之外,函数还接收两个参数,两个数值常量。
- 该地址是读(传 0,默认值)还是写(传1)?
- 局部性有多强?3 表示最大,一直到 0 表示最小。0 的话表示这个值在使用过之后可以立刻从 cache 中清除,3 表示所以级别的 cache 都应该继续保持该值。
只要 CPU 能够预测下一次内存访问发生在什么位置,那么就会自己进行预取操作。预取在连续的内存访问情况下可以工作得很好,例如进行数组遍历的时候。如果进行随机内存访问的时候,预取就不是那么得高效了。