优化此代码块

时间:2012-05-16 12:19:51

标签: c++ optimization loops

for (int i = 0; i < 5000; i++)
   for (int j = 0; j < 5000; j++)
   {
      for (int ii = 0; ii < 20; ii++)
          for (int jj = 0; jj < 20; jj++)
           {
               int num = matBigger[i+ii][j+jj];
               // Extract range from this.
               int low = num & 0xff;
               int high = num >> 8;
               if (low < matSmaller[ii][jj] && matSmaller[ii][jj] > high)
                  // match found
           }
   }

该机器是x86_64,32kb L1 cahce,256 Kb L2缓存。

关于如何优化此代码的任何指示?

编辑原始问题的一些背景知识:Fastest way to Find a m x n submatrix in M X N matrix

6 个答案:

答案 0 :(得分:4)

我尝试的第一件事是将iijj循环移到ij循环之外。这样你就可以matSmaller使用相同的i元素进行jfor (int ii = 0; ii < 20; ii++) for (int jj = 0; jj < 20; jj++) int smaller = matSmaller[ii][jj]; for (int i = 0; i < 5000; i++) for (int j = 0; j < 5000; j++) { int num = matBigger[i+ii][j+jj]; int low = num & 0xff; if (low < smaller && smaller > (num >> 8)) { // match found } } 循环的2500万次迭代,这意味着你(或编译器,如果你是幸运的话) )可以在这些循环之外提升对它们的访问:

matSmaller

这可能会更快(感谢对matBigger数组的访问权限较少),或者速度可能较慢(因为我已经更改了对ii数组的访问模式,并且&# #39;我可能认为它对缓存不太友好)。类似的替代方法是将i循环移到jmatSmaller[ii]之外并提升jj,但将jj循环留在里面。经验法则是,与早期索引相比,它在内循环中递增多维数组的 last 索引的缓存更友好。所以我们更开心了#34;修改jii,而不是修改imatBigger

我要尝试的第二件事 - int的类型是什么?看起来其中的值仅为16位,因此请将其作为(u)int16_tint进行尝试。前者可能更快,因为对齐smaller访问速度很快。后者可能更快,因为任何时候更多的数组都适合缓存。

对于0的早期分析,您可以考虑一些更高级别的事情:例如,如果它matBigger那么您不需要检查ii对于jjnum & 0xff < 0的值,因为{{1}}始终为false。

做得比做出更好的事情,然后看看他们是否更快或更快。&#34;你需要知道哪些线路最热,这意味着你需要一个分析器。

答案 1 :(得分:2)

一些基本建议:

  1. 对其进行描述,以便了解热点的位置。
  2. 考虑缓存位置以及循环顺序产生的地址。
  3. 在最里面的范围内使用更多const,以便向编译器提示更多内容。
  4. 尝试分解,以便在high测试失败时不计算low
  5. 尝试明确地将偏移量保持为matBiggermatSmaller,最内层步入简单的增量。

答案 2 :(得分:1)

最好的事情是了解代码应该做什么,然后检查是否存在针对此问题的其他算法。

除此之外:

  • 如果您只是感兴趣,如果存在匹配的条目,请确保在// match found的位置中突破所有3个循环。
  • 确保以最佳方式存储数据。这一切都取决于你的问题,但是只有一个大小为5000 * 5000 * 20且过载operator()(int,int,int)的数组可以更有效地访问元素。

答案 3 :(得分:0)

什么是matSmallermatBigger? 尝试将其更改为matBigger[i+ii * COL_COUNT + j+jj]

答案 4 :(得分:0)

我同意史蒂夫关于重新安排你的循环以获得更高的计数作为内循环。由于您的代码只进行加载和比较,我相信很大一部分时间用于指针算术。尝试一个实验来改变史蒂夫的答案:

for (int ii = 0; ii < 20; ii++)
  {
  for (int jj = 0; jj < 20; jj++)
    {
    int smaller = matSmaller[ii][jj];
    for (int i = 0; i < 5000; i++)
      {
      int *pI = &matBigger[i+ii][jj];
      for (int j = 0; j < 5000; j++)
        {
        int num = *pI++;
        int low = num & 0xff;
        if (low < smaller && smaller > (num >> 8)) {
          // match found
        } // for j
      } // for i
    } // for jj
  } // for ii

即使在64位模式下,C编译器也不一定能很好地将所有内容保存在寄存器中。通过将数组访问更改为简单的指针增量,您将使编译器的工作更容易生成有效的代码。

编辑:我刚注意到@unwind建议基本相同的事情。另一个需要考虑的问题是您的比较统计数据。低或高比较更可能吗?安排条件陈述,以便首先进行较不可能的测试。

答案 5 :(得分:0)

看起来这里有很多重复。一种优化是减少重复工作量。使用笔和纸,我显示matBigger“i”索引迭代为:

[0 + 0], [0 + 1], [0 + 2], ..., [0 + 19],
         [1 + 0], [1 + 1], ..., [1 + 18], [1 + 19]
                  [2 + 0], ..., [2 + 17], [2 + 18], [2 + 19]

正如您所看到的,有多次访问的位置。 此外,乘以迭代计数表示访问内部内容:20 * 20 * 5000 * 5000,或10000000000(10E + 9)次。那太多了!

因此,不要试图加速执行10E9指令(例如执行(管道)缓存或数据缓存优化),而是尝试减少迭代次数。

代码在矩阵中搜索一个范围内的数字:大于最小值且小于最大范围值。

基于此,尝试不同的方法:

  1. 查找并记住搜索值较大的所有坐标 比低值。我们称之为锚点。
  2. 对于每个锚点,找到后面第一个值的坐标 超出范围的锚点。
  3. 目标是减少重复访问次数。锚点允许一次扫描并允许其他决定,例如查找范围或确定包含锚值的MxN矩阵。

    另一个想法是创建包含matBiggermatSmaller的新数据结构,这些数据结构更适合搜索。

    例如,为matSmaller中的每个唯一值创建一个{value,coordinate list}条目:

      Value    coordinate list
        26 -> (2,3), (6,5), ..., (1007, 75)
        31 -> (4,7), (2634, 5), ...
    

    现在,您可以使用此数据结构在matSmaller中查找值并立即了解其位置。因此,您可以在matBigger中搜索此数据结构中的每个唯一值。这再次减少了对矩阵的访问次数。