目录
介绍
在这篇简短的文章中,我们将比较.NET平台的几个快速傅立叶变换(FFT)实现。参赛者是:
NAudio | ||||||||
版本: | 3.8.0 | 1.2 | 5.0 | 0.9.6 | 2.1 | 1.1 | (2017) | 3.3.9 |
许可证: | LGPL | BSD | MIT | MIT | MIT | - | MIT | GPL |
组件: | 3 | 1 | 1 | 1 | 1 | - | - | 1+1 |
大小: | 3.6MB | - | 1.6MB | 0.3MB | 0.2MB | - | - | 2.3MB |
Nuget: | 是 | 不 | 是 | 是 | 是 | 不 | 不 | 不 |
附加
- Accord.NET是一个结合了音频和图像处理的机器学习框架。它不再积极开发。
- Exocortex项目是在早期的.NET 1.0中创建的。本文提供的副本已更新为面向.NET标准2.0并使用System.Numerics命名空间的类型Complex。
- NAudio使用具有单精度实部和虚部的自定义Complex类型实现。
- DSPLib是一个简明的库,用于实值输入和频谱分析的FFT。未实现反向FFT
- FFTW是一种流行的本机FFT实现。它可能是互联网上可以找到的最快的开源实现,因此与托管代码进行比较有点不公平。不过,我认为看看代码如何竞争可能会很有趣。
FFTW二进制文件不随本文一起分发。如果您希望FFTW包含在基准测试、fftw3.dll和fftw3f.dll中则必须手动下载二进制文件。对于最新的构建,请尝试Conda或访问Github项目以获取更多选项:GitHub - wo80/SharpFFTW: SharpFFTW - A lightweight C# wrapper for native FFTW.。
基准
我对用于真实值输入(音频处理)的1D FFT特别感兴趣。以下接口用于所有测试。如果您有自己的FFT实现,则通过实现此接口并将其合并到基准测试中应该很容易,并在Util.LoadTests()方法中实例化测试。
interface ITest
{
/// <summary>
/// Gets the name of the test.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the FFT size of the test.
/// </summary>
int Size { get; }
/// <summary>
/// Gets or sets a value indicating whether the test should be run.
/// </summary>
bool Enabled { get; set; }
/// <summary>
/// Prepare the real valued data for FFT processing.
/// </summary>
/// <param name="data">The samples array.</param>
void Initialize(double[] data);
/// <summary>
/// Apply FFT to data.
/// </summary>
/// <param name="forward">If false, apply inverse FFT.</param>
void FFT(bool forward);
// Ignore for benchmark (used only for 'FFT Explorer', see next section)
double[] Spectrum(double[] input, bool scale);
}
看看fftbench.Common项目的fftbench.Benchmark命名空间中的不同测试,了解如何正确实现接口。
Exocortex,Lomont和FFTW对真实有价值的数据进行了专门的实现,预计代码的速度大约是标准复杂实现的两倍。
Accord.NET、Math.NET和FFTW支持任何大小的输入数组(即大小不必是2的幂)。由于Exocortex、NAudio、NWaves和Lomont仅支持基数 2,因此基准测试使用大小为2的数组。
结果
运行fftbench控制台应用程序,输出可能如下所示。第一列显示与以下各项相比Exocortex (real)的相对速度:
$ ./fftbench 10 200
FFT size: 1024
Repeat: 200
[14/14] Done
FFTWF (real): 0.2 [min: 1.29, max: 1.64, mean: 1.33, stddev: 0.03]
FFTW (real): 0.2 [min: 1.34, max: 1.60, mean: 1.43, stddev: 0.05]
FFTW: 0.5 [min: 2.86, max: 3.13, mean: 2.87, stddev: 0.03]
Exocortex (real): 1.0 [min: 5.72, max: 6.20, mean: 5.76, stddev: 0.05]
Lomont (real): 1.1 [min: 6.12, max: 8.04, mean: 6.26, stddev: 0.17]
NWaves (real): 1.5 [min: 8.44, max: 10.73, mean: 8.52, stddev: 0.24]
NWaves: 1.7 [min: 9.70, max: 11.90, mean: 9.79, stddev: 0.21]
Exocortex: 1.9 [min: 10.56, max: 12.93, mean: 10.71, stddev: 0.22]
Lomont: 1.9 [min: 10.58, max: 15.90, mean: 10.77, stddev: 0.38]
NAudio: 2.1 [min: 11.80, max: 14.17, mean: 12.03, stddev: 0.20]
AForge: 2.6 [min: 14.72, max: 15.90, mean: 14.93, stddev: 0.12]
DSPLib: 2.8 [min: 15.30, max: 22.10, mean: 15.91, stddev: 0.94]
Accord: 3.8 [min: 21.06, max: 29.19, mean: 21.69, stddev: 0.93]
Math.NET: 7.4 [min: 38.26, max: 73.53, mean: 42.74, stddev: 4.60]
Timing in microseconds.
在基准测试中,每个FFT实际上被调用了50 * 200次(从第二个命令行参数(200)中获取的重复值乘以默认值50次内部迭代)。FFT大小为2^10(第一个命令行参数)。该基准测试在AMD Ryzen 3600处理器上运行。
下图显示了大小为1024、2048 和4096的不同FFT的基准结果。fftbench-win应用程序使用的重复值为200:
解释FFT结果
fftbench-win应用程序(WinForms 项目仅包含在文章下载中——不在Github上)包含一个名为FFT Explorer 的实用程序。您可以通过单击基准测试窗口最左侧的图标来打开它。
FFT浏览器允许您选择FFT实现、输入信号和FFT大小。三个图形将显示输入信号、所选FFT计算的频谱和反向FFT计算的信号。
让我们看一下SignalGenerator类的示例信号。生成的信号是频率为1.0 Hz,振幅为20.0的简单正弦波:
public static double[] Sine(int n)
{
const int FS = 64; // sampling rate
return MathNet.Numerics.Generate.Sinusoidal(n, FS, 1.0, 20.0);
}
设FFT帧大小为n=256。在64 Hz 的采样率下,我们的周期信号将在所选窗口内精确重复四次。请注意,选择所有值时,信号周期、采样率和FFT大小之间都完全匹配。这样,我们就不必处理光谱泄漏问题。
FFT输出的每个箱由频率分辨率(采样率)/(FFT大小)间隔,在我们的例子中为64 / 256 = 0.25。因此,我们期望对应于我们的1.0 Hz信号的窥视位于箱号 4 中(因为1.0 = 4 * 0.25)。
由于DFT的性质,信号的频谱将被缩放n=256,因此如果不进行进一步缩放,我们预计该值为20.0 * 256 / 2 = 2560。我们除以2,因为振幅分布在两个箱中。第二个箱位于索引256 - 4=252处,并且具有相同的幅度,因为对于实值输入信号,FFT 输出是(共轭)对称的(跨越n/2,对应于奈奎斯特频率的箱)。
速览的实际值在FFT实现之间不一致,因为没有通用的缩放约定。如果FFT大小为n,则一些实现将FFT缩放1/n,一些实现将逆FFT缩放1/n,一些实现将两者缩放1/sqrt(n)。有些根本不可扩展(如FFTW)。
下表显示了上述示例的不同FFT计算的幅度:
Accord.NET | Exocaortex.DSP | Math.NET | NAudio | NWaves | Lomont | DSPLib | FFTW | |
值: | 2560 | 2560 | 160 | 10 | 2560 | 160 | 10 | 2560 |
您可以看到NAudio和DSPLib按1/n缩放,Math.NET和Lomont缩放1/sqrt(n)(Math.NET和Lomont都允许用户更改缩放约定;上面计算并在基准测试中使用的值表示默认设置)。
结论
显然,FFTW显然是赢家。因此,如果使用原生的GPL许可库是您的一种选择,那就去吧。查看托管代码,NWaves表现相当不错。Exocortex和Lomont对于较小尺寸的FFT都表现良好,但对于较大尺寸的FFT,复杂FFT的性能似乎有所下降。对于真实值的信号,Exocortex和Lomont都表现得很好——即使对于更大的尺寸也是如此。
https://www.codeproject.com/Articles/1095473/Comparison-of-FFT-Implementations-for-NET