【问题标题】:Performing efficient local average over a 2D array在二维数组上执行有效的局部平均
【发布时间】:2016-01-28 16:17:49
【问题描述】:

我有一个大约 250、250 维度的二维数组自定义向量类。 Vector 类只存储向量的 x 和 y 浮点分量。我的项目要求我对数组执行平滑函数,以便通过获取数组中每个向量周围 i 索引的局部平均值来创建一个新数组。我的问题是我当前的解决方案计算速度不够快,想知道是否有更好的计算方法。

我当前解决方案的伪代码如下所示。我在 C# 中实现这个,任何帮助将不胜感激。我的实际解决方案使用一维数组来加快速度,但我没有在此处包含它。

function smoothVectorArray(Vector[,] myVectorArray, int averagingDistance) {

    newVectorArray = new Vector[250,250];

    for (x = 0; x < 250; x++)
    {
        for (y = 0; y < 250; y++)
        {
            vectorCount = 0;
            vectorXTotal = 0;
            vectorYTotal = 0;

            for (i = -averageDistance; i < averagingDistance+ 1; i++)
            {
                for (j = -averageDistance; j < averagingDistance+ 1; j++)
                {

                    tempX = x + i;
                    tempY = y + j;

                    if (inArrayBounds(tempX, tempY)) {
                        vectorCount++;
                        vectorXTotal += myVectorArray[tempX, tempY].x;
                        vectorYTotal += myVectorArray[tempX, tempY].y;
                    }

                }
            }

            newVectorArray[x, y] = new Vector(vectorXTotal / vectorCount, vectorYTotal / vectorCount);

        }
    }

    return newVectorArray;

}

【问题讨论】:

  • 我唯一能看到的是你正在访问 myVectorArray[tempX, tempY] 2 次,你应该将它存储在一个局部变量中以便更快地访问。
  • 靠近边缘时可能会减少内部循环for(tempX = Math.Max(x-averageDistance, 0); tempX &lt; Math.Min(x + averageDistance, 249); tempX++)
  • 您的代码包含拼写错误(averageDistance vs averagingDistance)。发布代码时使用复制/粘贴,不要重新输入。
  • inArrayBounds 是不必要的,修改内部循环以防止尝试转到数组之外(提示:在设置循环开始和结束时使用maxmin)。

标签: c# arrays performance 2d average


【解决方案1】:

你的内部循环所做的是计算矩形面积的总和:

for (i = -averageDistance; i < averagingDistance+ 1; i++)
   for (j = -averageDistance; j < averagingDistance+ 1; j++)

您可以在 O(n^2) 中有效地预先计算这些。让我们介绍数组 S[N][N](在您的情况下 N = 250)。

为了简单起见,我假设只有一个坐标。您可以通过构建 2 个数组轻松地将其调整为对 (x, y)。

S[i, j] - 将是子矩形 (0, 0)-(i, j) 的总和

我们可以高效地构建这个数组:

S[0, 0] = myVectorArray[0, 0]; //rectangle (0, 0)-(0,0) has only one cell (0, 0)
for (int i = 1; i < N; ++i){
  S[0, i] = S[0, i-1] + myVectorArray[0, i];  //rectangle (0, 0)-(0, i) is calculated based on previous rectangle (0,0)-(0,i-1) and new cell (0, i)
  S[i, 0] = S[i - 1, 0] + myVectorArray[i, 0]; //same for (0, 0)-(i, 0)
}

for (int i = 1; i < N; ++i){
  var currentRowSum = myVectorArray[i, 0];
  for (int j = 1; j < N; ++j){
     currentRowSum += myVectorArray[i, j]; //keep track of sum in current row
     S[i, j] = S[i - 1, j] + currentRowSum; //rectangle (0,0)-(i,j) sum constrcuted as //rectanle (0, 0) - (i-1, j) which is current rectagnle without current row which is already calculated + current row sum
  }
 }

一旦我们计算了这个部分和数组,我们就可以在 O(1) 中得到子矩形和。假设我们要在矩形 (a, b)-(c,d) 中求和

为了得到它,我们从大矩形 (0, 0)-(c, d) 开始,我们需要从中减去 (0, 0)-(a-1, d-1) 和 (0, 0)- (c-1, b-1) 并添加回加矩形 (0, 0)-(a-1, b-1),因为它被减去了两次。

这样你就可以摆脱你的内在循环。

https://en.wikipedia.org/wiki/Summed_area_table

【讨论】:

  • 这很优雅。谢谢,这样会更快!
【解决方案2】:

您肯定希望在解决方案中利用 CPU 缓存,听起来您在使用一维阵列解决方案时已经考虑到了这一点。尝试安排算法一次处理连续的内存块,而不是在数组中跳跃。至此,您应该使用 Vector 结构而不是类,或者使用两个浮点数组,一个用于 x 值,一个用于 y 值。通过使用类,您的数组存储指向堆中各个位置的指针。因此,即使您按顺序遍历数组,当您跳到 Vector 对象的位置时,您仍然会一直丢失缓存。每个缓存未命中都会浪费大约 200 个 CPU 周期。这将是首先解决的主要问题。

之后,您可以考虑一些微优化

  • 在 inArrayBounds 方法上使用内联提示:[MethodImpl(MethodImplOptions.AggressiveInlining)]

  • 使用不安全模式并使用指针算法进行迭代以避免数组边界检查开销

最后两个想法可能会或可能不会有任何重大影响,您应该进行测试。

【讨论】:

  • 关于使用结构而不是类的观点很好,它适用于@serhiyb 的答案。我也会尝试微优化。谢谢你的回答。
  • 是的,他有很棒的算法技巧。结合缓存位置,事情会尖叫!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-02-19
  • 2013-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-27
  • 1970-01-01
相关资源
最近更新 更多