为什么我会看到使用本机代码增加约20%的速度?

时间:2009-05-19 16:04:03

标签: c# winforms

知道为什么这段代码:

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问题。新的不安全方法实际上比本机方法运行得更快(非常酷)。

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

很明显,数组绑定检查是这种FFT方法减速20%的原因。由于它的性质,这种方法中的for循环无法优化。

感谢大家的帮助。

7 个答案:

答案 0 :(得分:23)

只是看看这段代码,我怀疑从我的经验来看,从C ++开始有一个相当显着的减速 - &gt; 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,而不是长。在C#中,long是64位类型。这实际上可能对指针操作产生深远影响,因为我相信你无意中在每次指针调用时都添加了一个long-gt; int转换(带溢出检查)。

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

答案 1 :(得分:3)

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

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

答案 2 :(得分:3)

本机编译器可以比JIT编译器做更深入和更重的优化,如矢量化,过程间分析等。而FFT可以通过矢量化获得很大的加速。

答案 3 :(得分:1)

考虑到托管代码确实检查了每个数组访问的索引,非托管代码没有这样做,我会说差异小于我的预期。

如果您将数组更改为托管代码中的指针(因为这是非托管代码中的指针),我希望它们的执行大致相同。

答案 4 :(得分:1)

我刚刚运行了他用int发布的代码而不是很长时间,并没有真正有所作为。我知道其他人对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计算的平均值。根据这些值,我从标准偏差计算出标准误差。

     

结果以毫秒显示。奔腾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

答案 5 :(得分:0)

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

答案 6 :(得分:0)

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