【问题标题】:Fastest way to propgate through a 2d Array C++通过 2d Array C++ 传播的最快方法
【发布时间】:2013-04-21 22:31:58
【问题描述】:

我有 2 个 100s*100s 的大型二维数组。它有一个大循环可以多次执行操作。里面有3个循环;在arr1 中的第一个循环存储arr2 中每个单元格的总和乘以数字,第二个循环将两个数组流式传输到一个文件,第三个循环在arr2 中存储两个数组的总和除以数字。

代码解释得更好:

for(int i=1;i<x+1;i++) {//initialize
    for(int j=1;j<y+1;j++) {
        arr1[i][j]=i*j*5.5;
        arr2[i][j]=0.;
    }
}

for (int i=0;i<x+2;i++) {//padding
    vi[i][0]=5;
    vi[i][y+1]=-5;
}

for (int j=0;j<y+2;j++) {//padding
    vi[0][j]=10.;
    vi[x+1][j]=-10.;
}

for(int t=0;t<times;++t) {
    for(int i=1;i<x+1;++i) {
        for(int j=1;j<y+1;j++) {
            arr2[i][j]=(arr1[i+1][j]+arr1[i-1][j]+arr1[i][j-1]+arr1[i][j+1])*1.5;
        }
    }

    arr2[1][1]=arr2[1][y]=arr2[x][1]=arr2[x][y]=0.;

    for(int i=1;i<x+1;++i) {
        for(int j=1;j<y+1;j++) {
            arr1[i][j]=(arr1[i][j]+arr2[i][j])*0.5;

            if(arr2[i][j]+arr1[i][j]>5.)
                cout<<"\n"<<t<<"  "<<i-1<<" "<<j-1<<" "<<arr1[i][j]<<" "<<arr2[i][j];
        }
    }
}

整个代码的工作时间超过 14 秒。我应该如何优化代码以尽可能快地工作。

【问题讨论】:

  • ij 的初始循环迭代中为零时,您会告诉我们像arr2[i-1] 这样的表达式有多好。当i = (maxi-1) 和您取消引用arr2[i+1] 时,同样在另一端。当j 最初为零并且您使用arr2[i][j-1] 之类的表达式时,您会遇到类似的问题。有些事情告诉我计算时间是你最不担心的。
  • 我已经编辑了代码。数组具有填充行和列,以避免边缘的特殊情况。
  • 那很好,因为正如现在所写的那样,arr1[0][0] 值将被使用,但从不改变。
  • 我现在对代码进行评价。

标签: c++ algorithm optimization multidimensional-array


【解决方案1】:

您可以使用第三个数组来临时存储 arr2 的数组值以供下次运行。 第一个循环完成后,您使用临时数组覆盖 arr2 - 像这样您不需要第二个循环。您将节省一半的时间。

for (n=0;n<x;n++)
{
   for (i=0;i<maxi;i++)
   {
      for (j=0;j<maxj;j++)
      {
         arr1[i][j]=(arr2[i+1][j]+arr2[i-1][j]+arr2[i][j+1]+arr2[i][j-1])*1.5;
         arr_tmp[i][j] = (arr1[i][j]+arr2[i][j])*0.5;
      }
   }
   arr2 = arr_tmp;
}

【讨论】:

  • 代码可能会说话。请注意,arr[2] 永远不会写入此代码的最大时间消耗者:三重循环,所以我不确定节省在哪里。也许给我们看看?
  • 据我理解代码,两个部分有一个主循环。每个部分由两个嵌套循环组成。第 1 部分和第 2 部分消耗的时间相同...
  • 我做了你的建议,但代码仍然很慢。我认为是因为第一个循环消耗了更多时间。我认为是因为缓存问题。我怎样才能让它缓存友好?
  • +1 我相信你是对的。正是代码的格式让我摆脱了困境。我不确定结果是否相同,特别是因为当ij 为零时寻址[i-1][j-1] 数组时的未定义行为,但不超过OP 的原始损坏代码。我认为这会奏效。
  • 如果是缓存问题,您可以尝试交换 ij 循环 - 一个方向将沿着缓存行运行,另一个不会(因此会导致更多缓存未命中) .看看哪个跑得更快
【解决方案2】:

注意:OP 的代码已经发生了巨大的变化,以响应有关填充等的 cmets。原始代码并没有任何问题——这就是我的答案所依据的。

假设您的 2D 数组被索引row-major(第一个索引是行,第二个索引是列),您的内存访问已经按照最佳缓存利用率的正确顺序(随着您的进步,您正在访问附近的元素)。您的最新代码质疑此假设,因为您似乎已将“maxi”重命名为“x”,这表明您正在索引一个 column-major 二维数组(这对于 C /C++)。

没有具体说明您如何声明 2D 数组,这可能会有所不同,但通过将您的实现转换为使用原始指针,我得到了很大的改进。我还通过组合操作并为每次迭代交替方向来消除第二个循环(来自您的原始帖子)。我更改了加权系数,使它们加起来为 1.0,这样我就可以更轻松地进行测试(通过生成图像输出)。

typedef std::vector< std::vector<double> > Array2D;

void run( int x, Array2D & arr2 )
{

   Array2D temp = arr2; // easy way to create temporary array of the correct size

   int maxi=arr2.size(), maxj=arr2[0].size();

   for (int n=0;n<x;n++)
   {
      Array2D const & src = (n&1)?temp:arr2; // alternate direction
      Array2D & dst = (n&1)?arr2:temp;
      for (int i=1;i<maxi-1;i++)
      {
         double const * sp0=&src[i-1][1], * sp1=&src[i][1], * sp2=&src[i+1][1];
         double * dp=&dst[i][1];
         for (int j=1;j<maxj-1;j++)
         {
            dp[0]=(sp0[0]+sp1[-1]+4*sp1[0]+sp1[+1]+sp2[0])*0.125;
            dp++, sp0++, sp1++, sp2++;
         }
      }
   }

   if ( (x&1) ) arr2=temp; // copy the result back if the iteration count was odd
} /**/

您可以研究的其他内容(在某种程度上取决于平台):

  • restrict 指针关键字(非标准 C++)
  • 预取请求——一种减少内存访问延迟的编译器/处理器特定方法
  • 确保在编译时启用了优化
  • 根据数组的大小,您可能会发现对算法进行列化以更好地利用可用缓存是有利的

利用可用的计算资源(非常依赖于平台):

  • 创建基于SIMD 的实现
  • 利用您的多核 CPU -- OpenMP
  • 充分利用您的 GPU -- OpenCL

【讨论】:

  • 我认为 arr2[i]=sum(arr1[i + x] .* w[x]) 的系数在中期为 0.5,其他为 0.75。
  • @AkiSuihkonen:我选择了自己的系数,因为我希望它们的总和为 1.0,这样平均值就不会在每次迭代中发生变化。
猜你喜欢
  • 1970-01-01
  • 2021-09-16
  • 2011-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-29
  • 1970-01-01
  • 2015-02-07
相关资源
最近更新 更多