缓存的作用是减少 CPU 等待内存请求完成时停止的次数(避免内存延迟),并且作为第二个效果,可能会减少需要传输的数据总量(保留内存带宽)。
避免遭受内存获取延迟的技术通常是首先要考虑的事情,而且有时会大有帮助。有限的内存带宽也是一个限制因素,特别是对于许多线程想要使用内存总线的多核和多线程应用程序。一组不同的技术有助于解决后一个问题。
提高空间局部性意味着您可以确保每个缓存行在映射到缓存后被完全使用。当我们查看各种标准基准时,我们发现其中很大一部分未能在缓存行被驱逐之前使用 100% 的已获取缓存行。
提高缓存行利用率在三个方面有帮助:
- 它倾向于在缓存中容纳更多有用的数据,从本质上增加有效缓存大小。
- 它倾向于在同一高速缓存行中容纳更多有用的数据,从而增加在高速缓存中可以找到所请求数据的可能性。
- 它降低了内存带宽要求,因为会减少取数。
常用技术有:
- 使用较小的数据类型
- 组织数据以避免对齐漏洞(通过减小大小对结构成员进行排序是一种方法)
- 当心标准的动态内存分配器,它可能会在内存预热时引入漏洞并在内存中传播数据。
- 确保在热循环中实际使用了所有相邻数据。否则,考虑将数据结构分解为热组件和冷组件,以便热循环使用热数据。
- 避免表现出不规则访问模式的算法和数据结构,并支持线性数据结构。
我们还应该注意,除了使用缓存之外,还有其他方法可以隐藏内存延迟。
现代 CPU:s 通常有一个或多个硬件预取器。他们训练缓存中的未命中并尝试发现规律。例如,在对后续缓存行进行几次未命中后,硬件预取器将开始将缓存行提取到缓存中,以预测应用程序的需求。如果你有一个常规的访问模式,硬件预取器通常会做得很好。如果您的程序不显示常规访问模式,您可以通过自己添加预取指令来改进。
以这样一种方式重新组合指令,使那些总是在缓存中丢失的指令彼此靠近,CPU 有时可以重叠这些提取,以便应用程序只承受一个延迟命中(内存级并行性)。
为了减少整体内存总线压力,您必须开始解决所谓的时间局部性问题。这意味着您必须在数据尚未从缓存中逐出时重用数据。
合并涉及相同数据的循环(循环融合),并采用称为平铺或阻塞的重写技术,都努力避免那些额外的内存提取。
虽然这个重写练习有一些经验法则,但您通常必须仔细考虑循环携带的数据依赖性,以确保您不会影响程序的语义。
这些东西在多核世界中真正得到了回报,在添加第二个线程后,您通常不会看到太多的吞吐量改进。