【问题标题】:Is there any workaround to "reserve" a cache fraction?是否有任何解决方法来“保留”缓存部分?
【发布时间】:2015-07-05 01:43:03
【问题描述】:

假设我必须编写一个 C 或 C++ 计算密集型函数,该函数有 2 个数组作为输入,一个数组作为输出。如果计算使用 2 个输入数组的频率高于它更新输出数组的频率,我最终会遇到输出数组很少被缓存的情况,因为它被逐出以获取 2 个输入数组。

我想为输出数组保留一部分缓存,并以某种方式强制这些行在获取后不会被驱逐,以便始终将部分结果写入缓存

Update1(output[]) // Output gets cached
DoCompute1(input1[]); // Input 1 gets cached
DoCompute2(input2[]); // Input 2 gets cached
Update2(output[]); // Output is not in the cache anymore and has to get cached again
...

我知道有一些机制可以帮助驱逐:clflush、clevict、_mm_clevict 等。有没有相反的机制?

我正在考虑 3 种可能的解决方案:

  • 如果数据已被驱逐,不时使用 _mm_prefetch 取回数据。但是,这可能会产生不必要的流量,而且我需要非常小心何时引入它们;
  • 尝试对较小的数据块进行处理。但是,这只有在问题允许的情况下才有效;
  • 在可能的情况下禁用硬件预取器以降低不必要的驱逐率。

除此之外,还有什么优雅的解决方案吗?

【问题讨论】:

  • 当我遇到类似情况时,偶尔的预取指令已经产生了可衡量的好处。诀窍是对何时保持聪明。
  • 通常是这样,尤其是当行已被逐出但如果我对已经在缓存中的行进行预取,它会更新缓存替换算法中的重用距离吗?
  • 您确定这会实际上提高性能吗?我建议采取相反的方式:未缓存/非临时/写入组合存储。
  • 驱逐控制可能就是您所需要的。如果input1 不在缓存中,那么input2 应该适合而不必替换output

标签: c++ c caching memory-management optimization


【解决方案1】:

Intel CPU 有一种叫做 No Eviction Mode (NEM) 的东西,但我怀疑这是你需要的。

当您尝试优化输出 [] 的第二次(不必要的)获取时,您是否考虑过使用 SSE2/3/4 寄存器来存储中间输出值,在必要时更新它们,并仅在与该部分 output[] 相关的所有更新都完成了吗? 我在计算 FFT(快速傅里叶变换)时做了类似的事情,其中​​部分输出在寄存器中,并且只有在知道它们将不再被访问时才将它们移出(到内存)。在那之前,所有更新都发生在寄存器上。您需要引入内联汇编来有效地使用 SSE* 寄存器。当然,这种优化很大程度上取决于算法的性质和数据放置。

【讨论】:

    【解决方案2】:

    我试图更好地理解这个问题:

    如果“输出”数组确实是严格用于输出的,并且您永远不会做类似的事情

    output[i] = Foo(newVal, output[i]);
    

    那么,output[] 中的所有元素都是严格写的。如果是这样,您需要“保留”的只是一个缓存行。对不对?

    在这种情况下,所有对“输出”的写入都会产生缓存填充,并可能与“输入”数组所需的缓存线竞争。

    您不希望缓存行“输出”可以消耗而不是保留一定数量的行。

    【讨论】:

    • 如果你有一个全关联缓存,你只需要一个缓存行,但没有人有全关联缓存。
    • 问题是我需要使用 output[] 来存储计算的中间结果。此外,为了写入 output[],CPU 核心首先读取/获取缓存行。我只是想避免这种抓取。
    • 我认为避免在写入之前发生的 read-for-ownership 的唯一方法是使用非临时存储。 movntdqa 等等。这不会给您想要的结果,因为写入将绕过缓存(仅使用存储缓冲区将多个 16 或 32B 写入组合成单个 64B 传输)。由于您确实需要在写入临时结果后再次读取output[],因此这是不可行的。
    【解决方案3】:

    我看到了两个选项,根据您的目标 CPU 和您的精确程序流程,它们可能有效也可能无效:

    1. 如果output 只被写入而不被读取,您可以使用流式存储,即带有不可读取提示的写入指令,因此它不会被提取到缓存中。

    2. 您可以对input 使用带有非时间对齐 (NTA) 提示的预取。我不知道这通常是如何实现的,但我确信在某些 Intel CPU(例如 Xeon Phi)上,每个硬件线程都使用特定的 NTA 数据缓存方式,即使用 8 路缓存每个线程 1/8。

    【讨论】:

      【解决方案4】:

      我猜这个问题的解决方案隐藏在内部,所采用的算法以及 L1 缓存大小和缓存行大小。 虽然我不确定我们会看到多少性能提升。

      我们可能会引入人工读取,它可以巧妙地避开编译器,并且在执行时也不会损害计算。单次人工读取应填充缓存行以容纳一页所需的数量。因此,应该修改算法以计算输出数组的块。类似于在巨大矩阵的矩阵乘法中使用的那些,使用 GPU 完成。他们使用矩阵块进行计算和写入结果。

      如前所述,写入输出数组应该发生在流中。

      为了引入人工读取,我们应该在编译时在正确的位置初始化输出数组,在每个块中初始化一次,可能是 0 或 1。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-05-17
        • 2011-02-06
        • 1970-01-01
        • 1970-01-01
        • 2012-01-06
        • 2022-10-13
        • 1970-01-01
        相关资源
        最近更新 更多