【问题标题】:Techniques for keeping data in the cache, locality?将数据保存在缓存中的技术,位置?
【发布时间】:2023-05-18 16:45:01
【问题描述】:

对于超快速代码,我们必须保持引用的局部性——将尽可能多的紧密使用在一起的数据保存在 CPU 缓存中:

http://en.wikipedia.org/wiki/Locality_of_reference

有什么技术可以做到这一点?可以举个例子吗?

我对 Java 和 C/C++ 示例感兴趣。了解人们用来阻止大量缓存交换的方法很有趣。

问候

【问题讨论】:

  • 可以从两个方面入手:将内存中的数据打乱是一种方法,及时打乱处理是另一种方法。
  • @MSalters,但是将 0.5MB 的数据放入 RAM 并不能保证所有数据同时都在缓存中?
  • @user1107474:在其他 CPU 内核上可能还有其他进程正在竞争缓存。但是,如果 CPU 缓存对您和其他进程来说足够大,那么那些 512 kB 将在缓存中。要将它们放入 RAM,CPU 必须通过缓存,并且由于我们假设缓存足够大,因此缓存不会丢弃数据。
  • @user1107474:当人们谈论局部性时,主要关心的是帮助处理器在进程运行时预加载缓存。也就是说,要解决的问题是当内存实际上不在缓存中时会发生什么(其他一些进程一直在运行,它不适合您的L1,L2或L3缓存......如果数据量非常小,您可以假设它将在缓存中,并期望不会有 many 问题(注意 assumemany 不能保证)。

标签: java c++ c performance caching


【解决方案1】:

在 Java 世界中,JIT 将努力实现这一目标,而试图再次猜测这可能会适得其反。 This SO question 更全面地解决了特定于 Java 的问题。

【讨论】:

    【解决方案2】:

    这可能太笼统而无法给出明确的答案。 C 或 C++ 中的方法与 Java 相比会有很大不同(语言布局对象的方式不同)。

    基本方法是将要访问的数据保持在一个封闭的循环中。如果您的循环在类型 T 上运行,并且它具有成员 m1...mN,但在关键路径中仅使用 m1...m4,请考虑将 T 分解为包含 m1...m4 的 T1 和包含 m4 的 T2。 ..mN。您可能希望向 T1 添加一个指向 T2 的指针。尽量避免在缓存边界方面未对齐的对象(非常依赖于平台)。

    使用连续容器(C 中的普通旧数组,C++ 中的向量)并尝试管理迭代以向上或向下移动,但不要在整个容器中随机跳跃。链接列表是局部性的杀手,列表中的两个连续节点可能位于完全不同的随机位置。

    Java 中的对象容器(和泛型)也是一个杀手,而在 Vector 中,引用是连续的,而实际的对象则不是(有一个额外的间接级别)。在 Java 中有很多额外的变量(如果你 new 两个对象一个接一个,对象可能最终会位于几乎连续的内存位置,即使会有一些额外的信息(通常是两个或三个指针) ) 之间的对象管理数据。GC 将移动对象,但希望不会使事情比它运行之前更糟。

    如果您专注于 Java,请创建紧凑的数据结构,如果您有一个有位置的对象,并且要在紧密循环中访问,请考虑在您的对象而不是创建 Point 并持有对它的引用。需要更新引用类型,这意味着不同的分配、额外的间接性和更少的局部性。

    【讨论】:

    • 处理所有这些问题的 C/C++ 优化指南是“在 C++ 中优化软件” 完整部分是指优化引用的局部性。 www.agner.org/optimize/optimizing_cpp.pdf
    【解决方案3】:

    两种常见的技术包括:

    • 极简主义(数据大小和/或代码大小/路径)
    • 使用缓存遗忘技术

    极简主义示例: 在光线追踪(一种 3D 图形渲染范例)中,使用 8 字节 Kd-trees 存储静态场景数据是一种常见的方法。遍历算法只需要几行代码。然后,Kd 树通常以通过在树的顶部具有大的空节点(Havran 的“Surface Area Heuristics”)来最小化遍历步骤数的方式进行编译。

    错误预测的概率通常为 50%,但代价很小,因为缓存行中确实有很多节点(假设每个 KiB 有 128 个节点!),并且两个子节点中的一个始终是内存中的直接邻居。

    缓存遗忘技术示例: Morton array indexing,也称为 Z-order-curve-indexing。如果您通常以不可预测的方向访问附近的数组元素,则可能首选这种索引。这对于大型图像或体素数据可能很有价值,其中您可能有 32 甚至 64 字节的大像素,然后是数百万(典型的紧凑型相机测量为百万像素,对吗?)甚至数千亿用于科学模拟。

    然而,这两种技术有一个共同点:将最常访问的东西放在附近,不太频繁的东西可以放在更远的地方,跨越整个 L1 缓存范围,从主内存到硬盘,然后同一个房间、隔壁房间、同一个国家、全世界、其他星球上的其他计算机。

    【讨论】:

      【解决方案4】:

      我想到的一些随机技巧,以及我最近使用的一些技巧:

      重新考虑您的算法。例如,您有一个带有形状的图像和查找形状角的处理算法。无需直接对图像数据进行操作,您可以对其进行预处理,将所有形状的像素坐标保存在列表中,然后对列表进行操作。您避免在图像周围随意跳跃

      收缩数据类型。常规 int 将占用 4 个字节,如果您设法使用例如uint16_t 你将缓存 2 倍以上的东西

      有时您可以使用位图,我用它来处理二进制图像。我存储了每位像素,因此我可以在单个缓存行中容纳 8*32 像素。它真的提升了性能

      形成Java,你可以使用JNI(不难)并用C实现你的关键代码来控制内存

      【讨论】:

        最近更新 更多