【问题标题】:Help optimizing integer math帮助优化整数数学
【发布时间】:2011-06-20 03:44:59
【问题描述】:

我需要一些帮助来优化一段数学繁重的代码。我已经完成了分析,并隔离了慢速方法。问题是单独的行并不慢,但是它们被调用了很多很多次,所以我需要在这里真正捏紧微秒。

此代码用于在执行 NTSC 过滤后转换像素数据。我在行旁边提供了分析数据占总运行时间的百分比,因此您可以看到需要做什么。这个功能总共占了我运行时间的一半左右(48% 自己,53% 有孩子)。

// byte[] ntscOutput;
// ushort[] filtered; - the pixels are ushort, because of the texture color depth

int f_i = 0;
int row = 0;
for (int i = 0; i < 269440; i++)                                  // 3.77 %
{
    int joined = (ntscOutput[f_i + 1] << 8) + ntscOutput[f_i];    // 6.6 %
    f_i += 2;                                                     // 1.88 %

    filtered[i] = (ushort)joined;                                 // 2.8 %

    ushort red = (ushort)(joined & 0xf800);                       // }
    ushort green = (ushort)(joined & 0x7e0);                      //  > 2.36 % each
    ushort blue = (ushort)(joined & 0x1f);                        // }

    red = (ushort)((red - (red >> 3)) & 0xf800);                  // }
    green = (ushort)((green - (green >> 3)) & 0x7e0);             //  > 4.24 % each
    blue = (ushort)((blue - (blue >> 3)) & 0x1f);                 // }

    filtered[i + 602] = (ushort)(red | green | blue);             // 5.65 %

    row++;
    if (row > 601)
    {
        row = 0;
        i += 602;
    }
}

我愿意接受任何优化方法。如果真的不可能改进实际的数学运算,也许一些带有不安全代码和指针的东西可以在操作数组时起作用,以防止这么多的强制转换?也许以某种方式改变我的数组类型,或者某种循环展开?我相信这是可能的,因为过滤操作本身是一个巨大的 C 库函数,其中包含大量循环和数学运算,整个过程总共占用了我运行时间的 1.35%。

【问题讨论】:

  • 如果演员确实扮演重要角色,也许您可​​以将joined 设为ushort?即使事实证明它对性能没有任何积极影响,它至少应该让你的代码不那么糟糕。
  • @zneak 我会,但是每个数学运算都会再次返回一个 int,所以我仍然需要所有这些演员表。这真的让我很恼火,所以我希望演员们不会为他们付出任何代价。
  • 嗯,我以为他们返回了使用的最大类型,但你是对的。在这种情况下,也许您可​​以在任何地方使用ints,直到您需要转换为filtered

标签: c# math optimization


【解决方案1】:

您要求进行微观优化,但您是否先尝试过宏观优化?

269440 可以被任何 2^n 整除,这意味着您可以轻松地将这段代码线程化到您拥有的处理器数量上,并且基本上具有 n 元组的速度。

但请确保不要在此代码中声明线程。

使用 unchecked 关键字的微优化可能可以在 rgb 块中通过未选中的 {} 包围所有内容来实现,但这可能不会有太大帮助。

真正的优化是:

对于joined (ushort) 的所有可能值,将filtered[i + 602] 的所有结果值存储到一个数组中,每个数组的索引都被(ushort) 连接,并且不使用计算,而是直接从数组中获取值。

然后跳过rgb部分,作为循环体使用:

filtered[i] = (ushort)joined;
filtered[i+602] = precalculatedValues[(ushort)joined];

您可以通过这种方式将joined 转换为ushort(在删除按位操作之后)。

【讨论】:

    【解决方案2】:

    我想知道,因为您循环了 269440 次(好吧,使用 row 变量更少),并且过滤后的变量结果只有 2^16 种可能性,您是否考虑过 查找表?我不确定 2^16 长的数组在 C# 中的表现如何,但可能值得一试。

    【讨论】:

    • 请给我们您的时间:)
    • @BenVoigt:有没有办法查看实际的编辑时间,而不仅仅是 X 小时前(直到它变得足够老以至于它显示时间戳)?
    • @MPelletier:是的,将鼠标移到“几小时前”上,你会得到一个工具提示。至少在 Chrome 中是这样。这就是我知道答案间隔 30 分钟发布的方式,即使页面只显示“7 小时前”和“8 小时前”。 (注意,由于忍者编辑的可能性,实际上是 30+/-5 分钟)
    • @BenVoigt:好的,我最初的输入只是一个查找表,在 04:28,而 Marino 的编辑 #4 添加他的查找表解决方案是在 04:44,如果我我没看错。
    • 我后来添加了预先计算好的部分。如果这是真正的解决方案并且 MPelletier 提出了几乎相同的建议(可查找),那么功劳归于他,我给他奖励 +1,但是我们不应该抱怨谁是接受者,因为我们真的来这里是为了帮助不要收集积分:)
    【解决方案3】:

    无需拆分连接的 ushort 并将结果重新组合在一起。 您可以直接使用计算输出

     filtered[i + 602] = (ushort)(joined - ((joined >> 3) & 0x18e3));            
    

    让我解释一下: 在您的示例中,蓝色使用 0-4 位,绿色使用 5-10,红色使用 11-15。加入的位是(按大端顺序)

    rrrr rggg gggb bbbb

    joined >> 3 将所有组件(红色、绿色、蓝色)移动 3。即加入 >> 3 具有格式

    000r rrrr gggg ggbb

    我们想要屏蔽从一个组件溢出到另一个组件的位。这个完成了 使用 0x18e3。 IE。 (joined >> 3) & 0x18e3 的格式为

    000r r000 ggg0 00bb

    接下来你做减法

    joined - ((joined >> 3) & 0x18e3)
    

    这确实会一步从原始中减去移位的 rgb 分量。但是,您必须考虑下溢。 IE。您不希望绿色组件中的下溢传播到红色组件中。稍加思考就表明这不是问题。所有移位的分量都小于它们的原始值,因此不会出现下溢。换句话说,上面的单个语句应该可以正常工作。 (当然,您仍然需要仔细测试)。

    为了提高速度,可以在多个像素上并行执行此操作。 IE。如果您将 4 个像素打包在一个 uint64 中,那么一次对所有 4 个像素执行此操作同样容易。 (但我不确定是否可以轻松地将 ushort 数组类型转换为 uint64 数组)。

    【讨论】:

    • 这正是我要说的。他还可以将像素放在一个结构中,该结构覆盖字节数组和短裤数组,因此不需要显式连接。或者按照您的建议与更大类型的数组联合以增加并行度。它的 SIMD 是普通的旧 C。
    【解决方案4】:

    你可以同时做红色和蓝色,因为中间的绿色空间会妨碍交互:

    ushort redblue = (ushort)(joined & 0xf81f);
    ushort green = (ushort)(joined & 0x7e0);
    
    redblue = (ushort)((redblue - (redblue >> 3)) & 0xf81f);
    green = (ushort)((green - (green >> 3)) & 0x7e0);
    
    filtered[i + 602] = (ushort)(redblue | green);
    

    为了提高可读性,您应该为所有这些幻数定义常量。

    【讨论】:

      【解决方案5】:

      我在此页面上遵循了一些技巧: http://developer.amd.com/documentation/articles/pages/7162004127.aspx

      也许其他人会发现该页面的一些额外点?欢迎在下方评论。

      uint f_i = 0;
      uint row = 0;
      ushort red, green, blue;
      ushort joined;
      for (uint i = 0; i < 269440; i++)                                  // 3.77 %
      {
          joined = (ntscOutput[f_i + 1] << 8) + ntscOutput[f_i];    // 6.6 %
          f_i += 2;                                                     // 1.88 %
      
          filtered[i] = joined;                                 // 2.8 %
      
          red = (joined & 0xf800);                       // }
          green = (joined & 0x7e0);                      //  > 2.36 % each
          blue = (joined & 0x1f);                        // }
      
          red = (ushort)((red - (red >> 3)) & 0xf800);                  // }
          green = (ushort)((green - (green >> 3)) & 0x7e0);             //  > 4.24 % each
          blue = (ushort)((blue - (blue >> 3)) & 0x1f);                 // }
      
          filtered[i + 602] = (ushort)(red | green | blue);             // 5.65 %
      
          row++;
          if (row > 601)
          {
              row = 0;
              i += 602;
          }
      }
      

      【讨论】:

      • 他正在使用 C#。嗯,除了演员,你还脱掉了什么?
      • 虽然他可能在 C# 中工作,但这段代码并没有限制它。这段代码在 C 和 C++ 中可以正常工作
      • 是的,但是您的解决方案必须在 C# 中运行;并且您删除的强制转换在 C# 中是强制性的(否则您会得到 error CS0266: Cannot implicitly convert type int' 到 short'. An explicit conversion exists (are you missing a cast?))。
      • 在循环外移动红色、绿色和蓝色变量的实例化是一个不错的选择。从理论上讲,大多数编译器都对其进行了优化(我认为),但在实践中,最好在外部实例化一次。
      • 红色 (0xf800)、绿色 (0x7e0) 和蓝色 (0x1f) 的过滤器也可以声明为 ushort 常量一次,减少这种方式的转换。
      【解决方案6】:

      (这应该与其他答案叠加,例如使用查找表)

      是的,您可以使用 unsafe 实现相当大的加速。线设置joined没有理由要占6%,循环条件也可以改善。试试:

      int row = 0;
      int remaining = 269440/2;
      fixed (ushort* p = (ushort*)&ntscOutput[0])
          do {
              int joined = *p;
              p++;
      
              // ...
      
          } while (--remaining > 0);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-04-12
        • 1970-01-01
        • 1970-01-01
        • 2012-11-04
        • 1970-01-01
        • 2011-07-06
        • 1970-01-01
        相关资源
        最近更新 更多