【问题标题】:Why would I see ~20% speed increase using native code?为什么我会看到使用本机代码的速度提高了约 20%?
【发布时间】:2009-05-19 16:04:03
【问题描述】:

知道为什么这个代码:

extern "C" __declspec(dllexport) void Transform(double x[], double y[], int iterations, bool forward)
{
    long n, i, i1, j, k, i2, l, l1, l2;
    double c1, c2, tx, ty, t1, t2, u1, u2, z;

    /* Calculate the number of points */
    n = (long)pow((double)2, (double)iterations);

    /* Do the bit reversal */
    i2 = n >> 1;
    j = 0;
    for (i = 0; i < n - 1; ++i)
    {
        if (i < j)
        {
            tx = x[i];
            ty = y[i];
            x[i] = x[j];
            y[i] = y[j];
            x[j] = tx;
            y[j] = ty;
        }
        k = i2;
        while (k <= j)
        {
            j -= k;
            k >>= 1;
        }
        j += k;
    }

    /* Compute the FFT */
    c1 = -1.0; 
    c2 = 0.0;
    l2 = 1;
    for (l = 0; l < iterations; ++l)
    {
        l1 = l2;
        l2 <<= 1;
        u1 = 1; 
        u2 = 0;
        for (j = 0; j < l1; j++) 
        {
            for (i = j; i < n; i += l2) 
            {
                i1 = i + l1;
                t1 = u1 * x[i1] - u2 * y[i1];
                t2 = u1 * y[i1] + u2 * x[i1];
                x[i1] = x[i] - t1; 
                y[i1] = y[i] - t2;
                x[i] += t1;
                y[i] += t2;
            }
            z = u1 * c1 - u2 * c2;
            u2 = u1 * c2 + u2 * c1;
            u1 = z;
        }
        c2 = sqrt((1.0 - c1) / 2.0);
        if (forward) 
            c2 = -c2;
        c1 = sqrt((1.0 + c1) / 2.0);
    }

    /* Scaling for forward transform */
    if (forward)
    {
        for (i = 0; i < n; ++i)
        {
            x[i] /= n;
            y[i] /= n;
        }
    }
}

运行速度比这段代码快 20%?

public static void Transform(DataSet data, Direction direction)
{
    double[] x = data.Real;
    double[] y = data.Imag;
    data.Direction = direction;
    data.ExtremeImag = 0.0;
    data.ExtremeReal = 0.0;
    data.IndexExtremeImag = 0;
    data.IndexExtremeReal = 0;

    long n, i, i1, j, k, i2, l, l1, l2;
    double c1, c2, tx, ty, t1, t2, u1, u2, z;

    /* Calculate the number of points */
    n = (long)Math.Pow(2, data.Iterations);

    /* Do the bit reversal */
    i2 = n >> 1;
    j = 0;
    for (i = 0; i < n - 1; ++i)
    {
        if (i < j)
        {
            tx = x[i];
            ty = y[i];
            x[i] = x[j];
            y[i] = y[j];
            x[j] = tx;
            y[j] = ty;
        }
        k = i2;
        while (k <= j)
        {
            j -= k;
            k >>= 1;
        }
        j += k;
    }

    /* Compute the FFT */
    c1 = -1.0; 
    c2 = 0.0;
    l2 = 1;
    for (l = 0; l < data.Iterations; ++l)
    {
        l1 = l2;
        l2 <<= 1;
        u1 = 1; 
        u2 = 0;
        for (j = 0; j < l1; j++) 
        {
            for (i = j; i < n; i += l2) 
            {
                i1 = i + l1;
                t1 = u1 * x[i1] - u2 * y[i1];
                t2 = u1 * y[i1] + u2 * x[i1];
                x[i1] = x[i] - t1; 
                y[i1] = y[i] - t2;
                x[i] += t1;
                y[i] += t2;
            }
            z = u1 * c1 - u2 * c2;
            u2 = u1 * c2 + u2 * c1;
            u1 = z;
        }
        c2 = Math.Sqrt((1.0 - c1) / 2.0);
        if (direction == Direction.Forward) 
            c2 = -c2;
        c1 = Math.Sqrt((1.0 + c1) / 2.0);
    }

    /* Scaling for forward transform */
    if (direction == Direction.Forward)
    {
        for (i = 0; i < n; ++i)
        {
            x[i] /= n;
            y[i] /= n;
            if (Math.Abs(x[i]) > data.ExtremeReal)
            {
                data.ExtremeReal = x[i];
                data.IndexExtremeReal = (int)i;
            }
            if (Math.Abs(y[i]) > data.ExtremeImag)
            {
                data.ExtremeImag = y[i];
                data.IndexExtremeImag = (int)i;
            }
        }
    }
}

FFT http://www.rghware.com/fft.png

我通过在我的应用中选择“Native DLL FFT”来创建图表中间的 CPU 下降:

http://www.rghware.com/InstrumentTuner.zip(源代码)

我认为这将在大多数 PC 上运行。您需要安装 DirectX。我在使用某些硬件的捕获设置时遇到了一些问题。捕获设置应该是可配置的,但这个有趣的发现使应用程序的开发偏离了方向。

无论如何,为什么我看到使用本机代码的速度提高了 20%?这似乎与我之前的一些假设背道而驰。

更新

在将函数转换为不安全的方法并修复 long/int 问题后。新的 unsafe 方法实际上比原生方法运行得更快(很酷)。

Profile http://www.rghware.com/profile.png

很明显,数组边界检查是这种 FFT 方法速度降低 20% 的原因。由于其性质,此方法中的 for 循环无法优化。

感谢大家的帮助。

【问题讨论】:

  • 我再次上传了源代码(这次是使用类库。
  • 你的速度比较测试怎么样?您是否在调试器之外的版本中运行(重要),以及多次运行(以确保您没有遇到任何 JIT 问题)?
  • 我在 IDE 之外的发行版中运行它。我正在使用 System.Diagnostics.Stopwatch 来测试这些功能的速度。我将结果放在表单上,​​这样我就可以观看它们并通过单选按钮来回切换。该功能基本上以半秒为间隔对进入声卡的数据连续执行。我已经运行了很多次测试。
  • 我安装了一个分析器(喷气机大脑)。虽然很高兴快速查看是什么占用了我的 CPU,但它并没有揭示我通过玩 System.Diagnostics.Stopwatch 尚未学到的任何东西。这些这个FFT函数就是瓶颈。不安全标志让我感到不安,但我想我会检查一下,看看会发生什么。
  • @Robert Hamilton:尝试将 JetBrains 切换为使用跟踪分析器而不是采样分析器。它的运行速度会慢得多,但您可以获得关于瓶颈的非常不同级别的信息。 AQTime 是用于此类工作的另一个仅跟踪分析器(实际上我更喜欢它)。另外,我最后的建议(见我的回答)有帮助吗?

标签: c# winforms


【解决方案1】:

仅看这段代码,根据我的经验,我怀疑从 C++ -> C# 的速度相当显着。

在将此类例程幼稚地移植到 C# 中时,您将面临的一个主要问题是 C# 将在此处对每个数组检查添加边界检查。由于您永远不会以优化的方式遍历数组 (see this question for details),因此几乎每个数组访问都会接受边界检查。

此外,此端口非常接近于 C 的 1->1 映射。如果您通过良好的 .NET 分析器运行此端口,您可能会发现一些可以优化的好点,以使其回到附近通过一两个调整来提高 C++ 速度(这几乎一直是我移植此类例程的经验)。

但是,如果您想让它以几乎相同的速度运行,您可能需要将其转换为不安全代码并使用指针操作而不是直接设置数组。这将消除所有边界检查问题,并恢复您的速度。


编辑:我看到另一个巨大的差异,这可能是您的 C# 不安全代码运行速度较慢的原因。

查看this page about C# compared to C++,尤其是:

"long 类型:在 C# 中,long 类型为 64 位,而在 C++ 中,为 32 位。"

您应该将 C# 版本转换为使用 int,而不是 long。在 C# 中,long 是 64 位类型。这实际上可能会对您的指针操作产生深远的影响,因为我相信您无意中在每个指针调用上添加了一个 long->int 转换(带有溢出检查)。

此外,当您使用它时,您可能想尝试将整个函数包装在 unchecked block 中。 C++ 没有做你在 C# 中得到的溢出检查。

【讨论】:

  • Rico Mariani 关于边界检查:blogs.msdn.com/ricom/archive/2006/07/12/663642.aspx
  • @David:我喜欢 Rico Mariani 的博客,但在这种情况下,这不是一个公平的比较。他是在比较典型 GUI 应用程序的速度降低,而不是纯粹的数字运算情况。即便如此,他还是看到数组边界检查总体下降了 3%。不过,该例程几乎都是数组访问,因此该数字不会被其他计算缓冲。我预计比 3% 的下降幅度要高得多(如果去掉最低的 10%,则为 0.6%)
  • 我将我的代码转换为一种不安全的方法,该方法使用指针来操纵数据(请参阅我在问题正文中的最新编辑。)结果不是我所期望的。我的不安全实施有问题吗?如果需要,我可以发布新代码。
  • @Robert Hamilton:查看我的编辑。我相信“长”声明正在改变这里的行为。
  • @Reed Copsey:好电话!通过长整数进行指针操作是问题所在。我将不安全的方法更改为整数,现在它运行得非常快。我用新的分析器结果更新了帖子 - 令人印象深刻。
【解决方案2】:

这很可能是由于 JIT 编译器生成的代码不如本地编译器生成的代码高效。

如果您关心性能下降 20%,则分析代码可能应该是您的下一步,或者您可以考虑使用现成的优化库。

【讨论】:

    【解决方案3】:

    与 JIT 编译器相比,本机编译器可以进行更深入、更重的优化,例如矢量化、过程间分析等。FFT 可以通过矢量化获得极大的加速。

    【讨论】:

      【解决方案4】:

      考虑到托管代码会对每个数组访问的索引进行边界检查,而非托管代码不会这样做,我会说差异比我预期的要小。

      如果您也将数组更改为托管代码中的指针(因为它们在非托管代码中实际上是这样),我希望它们的性能大致相同。

      【讨论】:

      • 请注意,在某些情况下可以优化数组边界检查(例如限制显式
      【解决方案5】:

      我只是用 int 而不是 long 运行了他发布的代码,它并没有真正产生影响。我知道其他人对FFT in .NET 的运气更好,这表明即使使用 FFT 数学,.NET 也可以达到或超过 C++ 的性能。

      所以我的回答是,要么发帖人的代码比链接中的代码更优化(针对 C),要么针对 C# 的优化不如我链接的文章中的代码。

      我在两台使用 .NET 2.0 的机器上执行了两组测试。一台机器有 XPSP2,有一个处理器,850MHz Pentium III,有 512Mb 的 RAM。另一台机器构建了 5321 的 Vista 并具有单个处理器,2 GHz Mobile Pentium 4,具有 1Gb 的 RAM。在每种情况下,我计算了 217 (131072) 个数据值的 100 次单独 FFT 计算的平均值。根据这些值,我根据标准偏差计算了标准误差。

      结果以毫秒为单位。 Pentium III 机器的结果是:

        Not Optimized   Optimized For Space Optimized For Speed
      Unmanaged   92.88 ± 0.09    88.23 ± 0.09    68.48 ± 0.03
      Managed C++ 72.89 ± 0.03    72.26 ± 0.04    71.35 ± 0.06
      C++/CLI 73.00 ± 0.05    72.32 ± 0.03    71.44 ± 0.04
      C# Managed  72.21 ± 0.04    69.97 ± 0.08
      

      Mobile Pentium 4 的结果是:

                Not Optimized   Optimized For Space Optimized For Speed
      Unmanaged   45.2 ± 0.1  30.04 ± 0.04    23.06 ± 0.04
      Managed C++ 23.5 ± 0.1  23.17 ± 0.08    23.36 ± 0.07
      C++/CLI 23.5 ± 0.1  23.11 ± 0.07    23.80 ± 0.05
      C# Managed  23.7 ± 0.1  22.78 ± 0.03
      

      【讨论】:

      • 我似乎无法复制这个:本机代码的平均值:0.007396 秒 +/- 0.000017 托管 C# 代码的平均值:0.008422 秒 +/- 0.000027 我仍然获得了 %18-19 的速度提升在我的机器上使用本机代码(他不是我的)。我要去我的笔记本电脑上看看有什么不同。
      • 让我想知道操作系统和处理器设计对这些数字有什么改进。
      【解决方案6】:

      您是否使用像AQTime 这样的分析器来查看瓶颈在哪里?有时将本机代码转换为托管代码是一件微不足道的事情。另一方面,由于在某些情况下托管代码比本机代码慢,您可能想尝试使用不安全代码。

      【讨论】:

        【解决方案7】:

        因为 C# .NET 编译器在生成高效代码方面并不是最好的。语言的整个逻辑阻止了这一点。顺便说一句,F# has much better performance than C# in Math

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多