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

知道为什么这个代码:

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 >= 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 >= 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  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://sofzh.miximages.com/c%23/fft.png

通过在我的应用程序中选择“Native DLL FFT”,我创建了在图中间看到的CPU下降:

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

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

无论如何,为什么我看到使用本机代码增加了20%的速度? 这看起来像是我以前的一些假设。

UPDATE

将函数转换为不安全的方法并修复long / int问题。 新的不安全方法实际上比本机方法运行得更快(非常酷)。

简介http://sofzh.miximages.com/c%23/profile.png

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

谢谢大家的帮助。

只看这段代码,我从我的经验中怀疑从C ++到C#的相当显着的减速。

你要在C#例程的一个天真端口中遇到的一个主要问题是C#将在这里添加对每个数组检查的边界检查。 由于您永远不会以一种优化的方式循环遍历数组( 详细信息请参阅此问题 ),几乎每个数组访问都将接收边界检查。

此外,这个端口非常接近来自C的1-> 1映射。如果你通过一个好的.NET分析器运行它,你可能会发现一些可以优化的好点,以使其恢复到接近C ++的速度。一两次调整(这几乎总是我在移植这样的例程时的经验)。

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


编辑:我看到一个更大的区别,这可能是你的C#不安全代码运行速度较慢的原因。

查看此页面有关C#与C ++的比较 ,特别是:

“long类型:在C#中,long类型是64位,而在C ++中,它是32位。”

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

此外,当您处于此状态时,您可能希望尝试将整个函数包装在未经检查的块中 。 C ++没有进行C#中的溢出检查。

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

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

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

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

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

我刚刚运行他用int发布的代码而不是很长时间并没有真正有所作为。 我知道其他人对.NET中的FFT运气更好,表明即使使用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 

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

因为C#.NET编译器不是生成高效代码的最佳选择。 而语言的整个逻辑阻止了这一点。 顺便说一句, F#在数学方面比C#有更好的性能