【问题标题】:Cache Optimization of C++ CodeC++代码的缓存优化
【发布时间】:2011-05-19 10:29:48
【问题描述】:

这似乎有点开放式,但我在为多个处理器和缓存优化一段 C++ 代码时遇到了麻烦。

比多处理器更重要的是缓存:我正在迭代 2 个嵌套循环

for(int i=0; i<n; i++){
  //do a little something here with a single array
  for(int j=0; j<whoaAnotherArray[n].size(); j++){
    * access array[i][j] and otherArray[i][j] and store in a variable
       - an example is: "int x = array[i][j] + otherArray[i][j]"
    * compare variable to some other array[index calculated from i and j]
       - an example is: "if (x < yetAnotherArray[i*n+j]){ //do something to yetAnotherArray }"
  }
}

我的数组(数组和其他数组)的大小非常大。 n 是它们的大小。

有没有办法让缓存更友好?我已经从使用链表切换了,这对缓存来说很糟糕。我在某处读到我的访问顺序 [i][j] 也是缓存高效的。

FWIW,这是负权循环检测算法的一部分。

我在想也许是因为我的数组太大了(顺便说一句,它们是整数数组),最好将它们分解一下以便它们更好地放入缓存中?但我不确定这是否正确,或者是否正确,如何去做。

我也开始使用 openmp。我一直在做的唯一一件事就是添加

#pragma omp parallel for

在正确的 for 循环之前,我得到了不错的利用。我想学习如何更好地使用并行性,但除了代码中的 for 循环之外,我不确定我能做什么。并且一直:我正在尝试对缓存友好。

【问题讨论】:

  • 你到底想在这里实现什么?我很确定有比 O(N²) 更有效的解决方案。
  • 通常是一个好的编译器(尤其是当你明确要求他这样做时)在循环中插入 precache 指令。
  • @elusive 抱歉,不是 O(N^2) 而是 O(N),因为我的内部循环不是 N。我会解决这个问题...@ruslik 我正在使用 g++,如何我应该这样做吗?
  • @ruslik:我一直认为编译器是女性。
  • @Akusete:当英语不是母语时会发生这种情况:)

标签: c++ caching optimization openmp


【解决方案1】:

【讨论】:

    【解决方案2】:

    缓存使用改进的一种可能性是修改您对arrayotherArray 的访问模式。当您阅读array[i][j] 时,您的机器当然会将内存“行”移动到缓存中。当您阅读otherArray[i][j] 时,您的机器当然会将内存“行”移动到缓存中。有可能要读取第二个“行”,必须将第一行从缓存中刷新到 RAM 中。然后你通过读取yetAnotherArray 的值使情况变得更糟(可能)。

    实际发生的情况在很大程度上取决于同时发生的其他事情、缓存中的其他内容以及正在执行的任何其他操作。这可能很难弄清楚。

    如果您的(主要)数组访问模式是同时要求两个(或所有 3 个)数组中的 element[i][j],那么您希望安排事务,使它们位于同一内存“行”中,即读。一种方法是将 3 个数组合并为一个 m*n*3 数组,其中 superArray[i][j][1] 位于 superArray[i][j][2] 旁边,而 superArray[i][j][2] 位于 superArray[i][j][3] 旁边,其中数组的 3 个平面分别代表一个原始数组。当然,这只有在我的索引排序正确的情况下才有效,所以要多考虑一下。

    最后:

    1. 这可能会改变你的优雅 程序变成一团意大利面条——但是 这是一个很小的代价 提高速度!

    2. 'line' 我指的是任何块 您的平台从 RAM 加载到 一口气缓存。

    3. Google 四处寻找循环平铺露天开采。编译器是 在这方面还不是很擅长 你能提供的任何帮助都应该 在改进执行中获得奖励 速度。

    【讨论】:

    • 非常感谢标记。我接受了你的建议,他们肯定加快了我的代码。即我取了两个数组并将它们组合起来以获得更多的空间局部性。我还确保我的嵌套循环以正确的顺序访问,以获得更多的空间局部性。
    【解决方案3】:

    有一个名为Cachegrind(Valgrind 插件)的程序可以帮助您分析代码在虚拟缓存中的执行情况。我将使用它来查看您的代码如何针对您的 CPU 缓存执行操作。 (我已经有一段时间没有使用它了,所以我不记得它是否可以自动检测您的 CPU 的缓存属性。您可能需要为它提供准确的 CPU 缓存参数。)

    您还可以尝试一些优化,理想情况下,您的编译器正在做或应该做的优化:

    1) 替换这一行:

    for(int j=0; j<whoaAnotherArray[n].size(); j++){
    

    与:

      int len = whoaAnotherArray[n].size();
      for(int j=0; j<len; j++){
    

    2) 在外部循环中创建指向数组的指针:

    int* pArray = array[i] - 1;
    int* pOtherArray = pOtherArray[j] - 1;
    

    并在循环中的第一个指针访问上使用预增量:

    int x = *(++pArray) + *(++pOtherArray);
    

    (是的,我知道这很难看。我知道编译器应该为你做这个。但就在几个月前,我发现这确实使与 linux 上的 gcc 4.3(?) 的区别。YMMV。)

    3) 如果有任何方法可以重组代码,以便您在一次循环中循环通过array,然后在第二次循环中循环通过otherArray,然后尝试这样做。在你的情况下似乎不太可能,但我不知道。关键是,您希望一次将内存访问尽可能集中在一个数组上。

    祝你好运。

    【讨论】:

    • 你的指针数学错了,你要减1,而不是sizeof(int)(可能是4或更多)
    猜你喜欢
    • 2014-05-04
    • 2012-11-13
    • 1970-01-01
    • 2013-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-09
    • 1970-01-01
    相关资源
    最近更新 更多