如何针对给定的多核架构优化算法

How to optimize an algorithm for a given multi-core architecture

我想知道我应该研究哪些技术来优化给定体系结构的给定算法。如何使用更好的缓存来提高性能。如何降低缓存一致性或我应该在我的 algorithm/program 中避免哪些访问模式,以便缓存一致性不会影响我的性能?

我了解一些在 L1 中使用最近缓存的数据的标准技术,但我如何有效地在多核上使用共享缓存(比如 L2)中的数据,从而避免主内存访问,这甚至是更贵?

基本上,我感兴趣的是我应该尝试利用或避免哪些数据访问模式,以便更好地映射到我给定的体系结构。我可以使用什么数据结构,在什么场景下使用什么架构(具有不同级别的私有缓存和共享缓存)来提高性能。谢谢。

我应该学习哪些技术来优化给定架构的给定算法?

微体系结构各不相同,因此请了解您的特定处理器的详细信息。英特尔在其 optimization guide 中提供了很好的文档。如果您使用的是 Intel 处理器,则需要阅读第 8.3 和 8.6 节:

8.3 优化指南 本节总结了用于调整多线程应用程序的优化指南。列出了五个领域(按重要性排序):

  • 线程同步
  • 公交车利用率
  • 内存优化
  • 前端优化
  • 执行资源优化

本节列出了与每个领域相关的实践。每个领域的指导方针将在接下来的章节中进行更深入的讨论。大多数编码建议提高了处理器内核的性能扩展;和缩放由于 HT 技术。仅适用于一种环境的技术已注明。

8.6 内存优化

缓存的高效运行是内存优化的一个重要方面。缓存的高效运行需要解决以下问题:

  • 缓存阻塞
  • 共享内存优化
  • 消除 64 KB 别名数据访问
  • 防止一级缓存中的过度逐出

为了更好地映射到我给定的体系结构,我应该尝试利用或避免哪些数据访问模式?

利用

当缓存已满并且缓存中的访问未命中时,缓存必须逐出某些内容以为新内容腾出空间data/code,逐出的内容通常基于最近最少使用 (LRU) 的近似值.如果可能,那么您的代码应该具有强大的 locality of reference:

  • 尝试打包在算法中及时使用的数据,使其在space(地址)
  • 中接近
  • 将数据紧密打包,不要使用 64 位整数,而 32 位整数就可以,例如
  • 有时 "object"(相关数据)相对于缓存行的对齐很重要。例如,如果有一个对象数组,每个对象都是 64 字节,并且它们是随机访问的,那么在 64 字节边界对齐将通过不引入未使用的数据来提高缓存效率。如果对象未对齐,则每个接触的对象都会引入两个缓存行,但只需要 64 字节,因此不会使用 50% 的传输数据(假设缓存行为 64 字节)。
  • 正如@PaulA.Clayton在评论中指出的那样,预取数据非常重要,因为它隐藏了部分或全部内存延迟。 “此外,利用基于步幅的硬件预取可能非常有益。(软件预取在某些情况下也很有用。)尽早获取指针有助于提高内存级并行性。
  • 为了便于硬件预取并提高带入缓存的数据的利用率,请特别注意矩阵和其他大型结构的存储和访问方式...参见 Wikipedia article on row-major order.

避免

  • 不经常使用的数据不应与经常使用的数据接近
  • 避免false sharing. If two or more threads access the same cache line but are not sharing the same data within the cache line and at least one of them is a writer you have false sharing... there will be unnecessary burden and latency hit associated with cache coherency protocol.
  • 在用完旧数据之前尽量不要使用新数据

测量

正如 Andrei Alexandrescu 所说 in this talk - 在性能调优方面,唯一正确的直觉是 "I should measure this." 熟悉缓存性能监控工具,例如:

关键原则是局部性:当您有选择时,首先处理附近的数据(避免稀疏访问),并尽快执行数据重用(重新组合相同数据的连续传递)。

对于多线程程序,原则是局部分离:确保线程在不相交的数据集上工作(使用不同的副本是necessary/possible)。

除非您有充分的理由这样做,否则请远离硬件的特殊性。

应该提到的是,代码也以与数据相同的方式缓存。具有大量内联和少量 jumps/calls 的小而密集的代码将减轻 L1C 缓存的压力,并最终减轻 L2、L3 和 RAM 的压力,这些地方会与数据获取发生冲突。

如果您正在使用超线程,似乎有证据表明,与单个高度优化(O2 和更高)的线程相比,内核中两个超线程的较低优化级别 (O1) 总体上完成的工作更多。