【问题标题】:Help me optimize this average calculation snippet帮我优化这个平均计算片段
【发布时间】:2011-09-17 00:57:22
【问题描述】:

有没有可能加快这个sn-p?

firstSamplelastSample 是我对这次迭代感兴趣的数组的一部分。当这个间隔达到 > 3000 时,我会明显减速。 _average 数组可以包含 6-60 百万个 int 值。

minYma​​xY 是我在这个计算完成后使用的结果。

int minY = Int32.MaxValue;
int maxY = Int32.MinValue;
int Y = 0;
int sample = firstSample + 1;

while (sample <= lastSample)
{
       Y = _average[sample];
       minY = Math.Min(Y, minY);
       maxY = Math.Max(Y, maxY);
       sample++;
}

【问题讨论】:

  • 你能为 sn-p 提供一些上下文吗?例如。您要完成什么,如何获取输入数据等。也许可以更改代码流?
  • 我正在处理音频数据。完整说明请参见stackoverflow.com/questions/1035533/…

标签: c# .net algorithm optimization


【解决方案1】:

_average[sample] 表达式是一个巨大的瓶颈,因为它包含对每次迭代的隐式边界检查。使用指向“_average”数组的指针(和 unsafe 关键字)。然后避免调用任何函数,因此摆脱 Math.Min/Max 调用并自己进行检查。

我现在没有任何编译器,我认为它应该是这样的:

unsafe
{
    fixed ( int* paverage = _average )   
    {
        int* p = paverage + firstSample + 1;
        for ( int sample = firstSample+1 ; sample <= lastSample ; sample++ )   
        {
            if ( *p < minY )
                minY = *p;
            if ( *p > maxY )
                maxY = *p;
            p++;
        }
    }   
}

最后,由于“sample”实际上并没有在循环中使用,您可以将其更改为倒数到零的循环变量,以便针对常量(零)而不是变量进行循环终止检查.

【讨论】:

  • +1 以获得很好的答案。顺便说一句,如果您不想使用指针,可以将代码包装在未经检查的 {} 块中。它可能不会像带有指针的不安全/固定块那样高效,但如果不安全代码不是一个选项,它应该会节省一些时间。
【解决方案2】:

不安全代码将允许您使用指针来索引数组,因为在这种特殊情况下,JIT 编译器将无法删除边界检查。看看here 如何做到这一点。

您也可以尝试自己内联 Min/Max 调用,但 JIT 很可能已经为您这样做了。

最后,使用 .NET 4 的并行扩展(您可以使用 .NET 3.5 的 CTP)将其并行化是相当容易的。只要确保您不会同时从多个线程写入最小值/最大值。不过也不要锁定它,我会为每个线程设置一个最小值/最大值,并在所有线程完成后对每个线程/任务的最小值/最大值进行最终比较。

【讨论】:

    【解决方案3】:

    您在评论中写道:

    我没有排序。只找到一个区间的最大值和最小值。并且间隔每20ms移动一次

    看来您实际上想要一个移动最小值移动最大值

    我相信这可以比每次重新搜索整个区间更有效,假设区间仅在一个方向移动,并且后续区间之间存在显着重叠。

    一种方法是保留一个特殊的队列,其中每个新元素将其值复制到队列中较大的每个元素(对于移动最小值),例如:

    (5 8 4 7 7 0 7 0 4 4 3 4 0 9 7 9 5 4 2 0);这是数组 (4 4 4 4) ;间隔为 4 个元素长,并初始化为最小值 ;前 4 个元素 (4 4 4 7) ;下一步,注意当前最小值始终是第一个元素 (4 7 7 0) ;现在发生了一些事情,因为 0 小于之前的值 (4 7 0 0) ;还有更小的值... (4 0 0 0) ;还是 ... (0 0 0 0) ;为本次迭代完成 (0 0 0 7) (0 0 0 0) ; 0 再次覆盖之前的胖子 (0 0 0 4) (0 0 4 4) (0 3 3 3) ; 3比之前的4s小, ;请注意,覆盖可以被缩短,只要 ;发现不大于新的值 (3 3 3 4) (0 0 0 0) ;等等...

    如果您每次移动超过 1 个元素,您可以先计算所有新值的最小值并将其用于回覆。

    该算法的最坏情况是当数组降序排序时,它是 O(nm),其中 m 是区间长度,n 是数组长度。最好的情况是降序排序,然后是 O(n)。对于一般情况,我推测 O(n log(m))。

    【讨论】:

      【解决方案4】:

      如果你有 3.5+ 框架,你可以使用 FOR 比 while 更快或使用 Parallel

      【讨论】:

      • 当迭代依赖于前一个时,并行工作会更快吗?
      • @pho3nix:“FOR 比 while 更快”[需要引用]
      • 从集合中获取最大值或最小值不必像这样连续。如果它被实现为reduce操作,它可以尽可能多地并行化。
      • 如果上述评论对您没有意义,我将其改写为“集合的最大值等于(前半部分的最大值)和(最大值下半场)”。或者你喜欢的任何分数。该算法可以/应该几乎完美地并行化。但是,如果事实证明瓶颈实际上是缓存未命中而不是 CPU,那么额外的线程可能根本没有帮助。
      【解决方案5】:

      我会伸出我的脖子说不,我认为没有任何方法可以显着加快速度(除非内联对 Min 和 Max 的调用会有所帮助,但我希望优化器会处理那个)。

      也就是说,如果您对同一数据执行多次此操作,那么对数据(或您每次处理的数据块)进行排序可能会使整个过程更快。

      排序比找到最小值要慢,但排序一次比找到最小值一千次要快。

      (如果我在这里教你吸鸡蛋,请原谅我。8-)

      【讨论】:

      • 我没有排序。只找到一个区间的最大值和最小值。并且间隔每20ms移动一次
      【解决方案6】:

      一方面,我会将其重写为一个简单的for 循环,并避免使用帕斯卡大小写的局部变量,包括一个超出其需要范围的变量:

      int minY = int.MaxValue;
      int maxY = int.MinValue;
      
      for (int sample = firstSample + 1; sample <= lastSample; sample++)
      {
          int y = _average[sample];
          minY = Math.Min(y, minY);
          maxY = Math.Max(y, maxY);
      }
      

      这只是为了让它更加熟悉和传统。 JIT 知道在某些情况下循环遍历数组,但我不知道它在这种情况下是否有帮助 - 它可以只检查 firstSample &gt;= -1 &amp;&amp; lastSample &lt; _average.length 然后消除边界检查,但我不知道知道是不是。现在,已经在当前最小/最大范围内的样本不需要任何副作用,所以让我们摆脱这种情况下的分配:

      for (int sample = firstSample + 1; sample <= lastSample; sample++)
      {
          int y = _average[sample];
          if (y < minY)
          {
              minY = y;
          }
          if (y > maxY)
          {
              maxY = y;
          }
      }
      

      我不知道这是否有帮助 - 我怀疑它不会,但它可能值得一试......

      (正如另一个答案所说,这是一个非常容易并行化的操作 - 它应该几乎与 CPU 数量成线性关系地提高速度,即 2 个处理器 ~= 速度提高两倍等,除了缓存未命中等)

      【讨论】:

        【解决方案7】:

        您可以按照其他人的建议尝试 for 循环。

        它需要分析,但您也可以尝试消除方法调用和分支:

           Y = _average[sample];
           minY = minY + ((Y-minY) & (Y-minY)>>31);
           maxY = maxX - ((X-maxX) & (X-maxX)>>31);
           sample++;
        

        仅当性能提升对您来说非常重要时才进行这些更改,因为代码的可维护性会因类似的构造而降低。

        【讨论】:

          【解决方案8】:

          如其他人所说,使用 for 循环,但将其设置为比较为零。在大多数实现中,这是一个更快的比较。

          【讨论】:

            【解决方案9】:

            在找到新最小值的情况下,您可以摆脱重复比较。如果您将最小值/最大值都设置为第一个值,那么如果您找到一个新的最小值,则没有理由检查它是否也是一个新的最大值。这基本上是 @Skeet 的代码,带有初始化和额外的“else”语句。

            int firstIndex = firstSample + 1;
            if (firstIndex <= lastSample)
            {
                minY = _average[firstIndex];
                maxY = minY;
            
                for (int sample = firstIndex + 1; sample <= lastSample; sample++)
                {
                    int y = _average[sample];
                    if (y < minY)
                    {
                        minY = y;
                    }
                    else if (y > maxY)
                    {
                        maxY = y;
                    }
                }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-11-25
              • 2019-08-28
              • 2014-05-26
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-07-19
              相关资源
              最近更新 更多