是否有“保留”缓存分数的解决方法?

时间:2015-04-26 18:20:37

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

假设我必须编写一个C或C ++计算密集型函数,它有2个数组作为输入,1个数组作为输出。如果计算使用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来获取数据。然而,这可能会产生不必要的流量加上我需要非常小心何时引入它们;
  • 尝试对较小的数据块进行处理。但是,只有在问题允许的情况下,这才有效;
  • 禁用硬件预取程序,以便降低不必要的驱逐率。

除此之外,有没有优雅的解决方案?

4 个答案:

答案 0 :(得分:1)

英特尔CPU有一种称为无驱逐模式(NEM)的东西,但我怀疑这是你需要的。

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

答案 1 :(得分:0)

我正在努力更好地理解这个问题:

如果确实'output'数组严格用于输出,并且你从不做像

这样的事情
output[i] = Foo(newVal, output[i]);

然后,output []中的所有元素都是严格写入的。如果是这样,你需要“保留”的只是一个缓存行。这不正确吗?

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

您是否希望缓存线'输出'上限可以消耗,而不是保留一定数量的线。

答案 2 :(得分:0)

我看到两个选项,根据您所定位的CPU以及精确的程序流程,可能会也可能不会起作用:

  1. 如果output仅写入但未读取,则可以使用流存储,即带有无读取提示的写入指令,因此不会将其提取到缓存中。

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

答案 3 :(得分:0)

我猜这个解决方案隐藏在里面,使用的算法和L1缓存大小和缓存行大小。 虽然我不确定我们会看到多少性能提升。

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

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

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