FFTW介绍

FFTW由麻省理工学院计算机科学实验室超级计算技术组开发的一套离散傅里叶变换的计算库,开源,搞笑和标准。Intel的数学库和Scililb都使用FFTW做FFT计算。
1)它可计算一维或多维、实和复数据以及任意规模的
2)FFTW输入数据长度任意
3)FFTW支持任意多维数据
4)FFTW支持SSE、SSE2 Altivec和MIPS指令集
5)FFTW还包含对共享和分布式存储系统的并行变换

FFTW的基本结构

FFTW不是采用固定算法计算变换,而是根据具体硬件和变换参数来调整使用不同算法,以期望达到最佳效果。因此,变换被分成两个阶段。首先,FFTW规划针对目标计算机的最快的计算途径,并生成一个包含此信息的数据结构。然后对输入数据进行变换。该规划可以多次使用。在一个典型的高性能应用中,总是在执行相同参数条件的任务,因而,相对复杂但结果可被重复使用的初始化是值得的,另一方面,当你需要某一参数的单次变换,初始化就显得过于费时。FFTW的一个显著的特点就是对某一参数类型的单次变换优势不大,但对于参数相同的多次变换具有更快的平均速度。

FFTW为了加快用户的使用集成速度,提供了三种不同层次的接口。
1)连续数据的单一变换的基本接口
2)计算多重和步进阵列数据的高级接口
3)支持通用数据布局、多重和步进的顶级接口。

FFTW不仅提供了数据自适应,还提供了高级用户定制功能。例如,由于代码空间不在,可以去掉用户不需要的功能代码。相反,FFTW还可以拓展数据结构。

FFTW的数据类型

数据

任何调用FFTW的程序都要包含它的头文件fftw3.h及其库(在windows下共享库fftw-3.2.1.dll)。

复数类型

FFTW使用包含两个元素的double型数据来表示一个复数,第一个元素表示实部,第二个元素是虚部。

typedef double fftw_complex[2];

精度

FFTW默认使用双精度浮点进行运算,用户可以使用float和long double来改变运算精度,虽然更高精度的数据进行运算理论上会变得更高精度的结果,但是由于软件系统的硬件的限制往往适得其反,因此用户要结合软硬件系统来选择FFTW的数据类型。

分配存储空间

void *fftw_malloc(size_t n);
void fftw_free(void *p);

FFTW一般只是简单调用系统的函数来分配存储空间,通常也不必担心有重大的内存开销,但我们强烈建议使用该函数来为用户的数据分配存储空间。

FFTW基本接口介绍

一维复数 DFT

方案:选择最佳的变换算法获得变换结果
使用FFTW计算一个大小为N的一维DFT很简单,步骤如下:

#include <fftw3.h>
{
     fftw_complex *in, *out;
     fftw_plan p;
     ...
     in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*N);
     out = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*N);
     p = fftw_plan_dft_1d(N, in, out, FFTW_FORWARD, FFTW_ESTIMATE);
     ...
     fftw_execute(p);
     ...
     fftw_destroy_plan(p);
     fftw_free(in);
     fftw_free(out);
}

首先为输入输出数组分配空间,你可以使用任何方式进行分配,但我建议使用fftw_malloc,因之不但具有一般malloc功能还在支持SIMD指令的情况下能够提供数组对齐的功能。数据是一个fftw_complex型数组。分配空间后,就可以创建方案了,该方案包括了执行变换的所有需要准备的信息。下面的函数就是用来创建方案的:

fftw_plan fftw_plan_dft_1d(int n, fftw_complex *in, fftw_complex *out, int sign, unsigned flags);

第一个参数n是你要进行变换的数据大小,n可以是任何大小的正整数。接下来的两个参数是输入输出数组的指针。这两个指针可以相同,表示就地转换以节省空间。

第四个参数sign,可以是FFTW_FORWARD(-1)或FFTW_BACKWARD(+1),指出变换的方向,从技术角度讲,它是变换中的指数标志。

参数flags通常可以是 FFTW_MEASURE或 FFTW_ESTIMATE,FFTW_MEASURE指示FFTW计算几种FFT算法执行这个n大小的变换所花费的时间,从中找出最优算法。处理器所花费的时间决定于硬件性能和n大小。FFTW_ESTIMATE则相反,它只是建立一个 FFTW认为合理的方案(也许不是最优的)。说白了,如果你想使用同一大小相同类型的输入数据执行多次变换,那么初始化建立方案的时间被均摊而变得不那么重要了,选择FFTW_ MEASURE否则选择FFTW_ESTIMATE 在选择 FFTW_MEASUR创建方案的过程中,输入输出数据的数据将被覆盖,因此用户应该在初始化使用变换数据前完成变换方案的创建。

一旦变换方案创建完成你就可以刷新使用你的输入/输出数组而执行多次变换,实际的变换计算工作由fftw_execute(plan)来完成:

void fftw_execute(const fftw_plan plan);

如果你想对相同大小不同数据值的数组进行变换,你可以使用fftw_plan_dft_1d创建一个新的变换方案,FFTW将会自动使用之前的方案数据以节约时间。完成变换后调用fftw_destroy_plan(plan)销毁方案:

void fftw_destroy_plan(fftw_plan plan);

使用fftw_malloc创建的数组需要调用fftw_free释放,而不是使用普通的内存释放函数(或者禁用、删除)。DFT结果存放在输出数组里,零频率直流响应放在out[0]中。如果输入和输出不是同一数组,则输出不会引起输入数据的修改(反之变换结果将覆盖输入数据)。

用户需要注意的是FFTW不是使用通常的 DFT算法。而是计算一个正向接着一个逆向变换(反之亦然)而得出结果。可以参考What FFTW Really Computes。如果你有一个支持 C99的编译器如GCC你可以包含#include <complex.h>,使用双精度复数类型来进行运算。另外,除了FFTW_ESTIMATE和l FFTW_MEASURE标志,还有许多其它的标志存在,例如,如果可以忍受更长的时间,你可以使用FFTW_PATIENT标志来生成一个更有效率的方案(见FFTWReference)。你甚至可以将方案保存起来留待以后的应用,见 Words of Wisdom-Saving Plans。

一维实数DFT

在许多应用场合,输入数据是实数序列,而输出满足“Hermitian”冗余:out[i]是 out[n-i]的共扼。可以利用这个特点来改善速度和内存使用量。

为了换取速度和空间的优势,用户需要牺牲一些简单的FFTW复数变换内容。首先,输入和输出的数组大小会不同:输入是大小为n的实数组,输出是n/2+1的复数组(去掉了冗余的输出);此外还要求输入数据的就地转化需要填充。第二点,逆变换(复数到实数)默认情况下会破坏输入数组。这些不便对于用户来说可能不是个严重的问题,但是,了解这些问题很有必要。

实数的变换程序和复数几乎是相同的:分配空间(最好使用fftw_malloc),创建一个fftw_plan并可使用fftw_execute(plan)多次执行该方案,使用fftw_destroy_plan(plan) (and fftw_free)清理和销毁方案。唯一的不同是输入的数据类型为双精度实数型,并且使用一个不同的程序来创建方案。一维:

fftw_plan fftw_plan_dft_r2c_1d(int n, double *in, fftw_complex *out, unsigned flags);
fftw_plan fftw_plan_dft_c2r_1d(int n, fftw_complex *in, double *out, unsigned flag);

实数输入复数输出(r2c)和复数输入实数输出(c2r)不同于复数DFT,没有sign参数。r2cDFTs总是FFTW_FORWARD而 c2r DFTs总是 FFTW_BACKWARD。这里n是逻辑大小,而不需要数组的物理大小。―需要特别说明的是实数组有n 个元素,复数组有n/2+1个元素。对于就地转换来说,输入和输出数组放在一起,―需要保证数组足够大,因此实数组的长度因该是2(n /2+1)。

综上,c2r变换即使在非就地转换的情况下也会破坏输入数组的数据内容,如果有必要可以使用FFTW_PRESERVE_INPU这个flag来避免,缺陷是会牺牲一定的性能。该标志目前还不支持多维实数DFT。

熟悉实数DFT的读者知道,输出复数组的第0个元素(直流)和第n/2个元素(当n为偶数时叫“Nyquist"频率)完全是实数。因此一些实现为了使输入和输出数据长度一致,―将“Nyquist”元素放到了直流元素的虚部里。―然而这种实现不能够很好的概括多维变换,―而且节省下来的空间有限,因此FFTW不支持。

一维 r2c 和 c2r DFTs的另一种接口在 r2r 中体现(见:The Halfcomplex-format DFT),那种半复数格式的输出与输入数据的类型和长度相同。那种接口对于多维实数变换也不常用,某些情况下可能会产生较好的效果。

FFTW创建方案(计划)函数的flags参数说明

方案的flags参数

FFTW中所有的方案程序都支持flags参数,该参数采用位或的方法提供0到多参数的配置。这些参数严格(且有时效限制)的控制方案的生成过程,并可以加入和取消一些支持的算法限制。

注意:在创建方案之后一定要初始化输入数组,除非使用一个之前保存的方案,因为在方案的生成过程中,输入数组有被改写。唯一的例外是FFTW_ESTIMATE和FFTW_WISDOM_ONLY flag,下面会提到。

在所有情况下,如果之前保存过相同或者更大规模的方案,可以导入之。例如在FFTW_ESTIMATE模式下可以导入所有的已存方案,而在FFTW_PATIENT模式下只有patient或exhaustive模式下创建的方案可以使用。

方案设计方式 flags

FTW_ESTIMATE 规定根据输入数据长度等信息直接挑选一个算法(可能不是最优的)来,而不是去计算比较各种算法得到,因而方案创建的比较快,且不会覆盖输入输出阵列。

FFTW_MEASURE告诉FFTW通过几种算法计算输入阵列实际所用的时间来确定最优算法。根据你的机器硬件配置情况,可能需要点时间(通常为几秒钟),该参数是默认选项。

FFTW_PATIENT类似FFTW_MEASURE 但进行更广泛的算法筛选来确定更理想的方案(特别是对于大规模的变换任务),同时也会带来方案的创建时间成倍增加。

FFTW_EXHAUSTIVE类似FFTW_PATIENT但进行更为广泛的算法筛选,即使是通常认为并不理想的算法也不放过,其结果相较于后者可能更优秀但耗时无疑会更长。

FFTW_WISDOM_ONLY一个特殊的方案设计模式,―该模式的方案仅当任务支持wisdom时才会被创建,否则返回NULL。该模式通常与其他模式一起工作,如“FFTw_WISDOM_ONLY|FFTW_PATIENT”将会在支持wisdom 的情况下创建一个基于FFTW_PATIEN模式的方案。在用户需要判断任务是否支持wisdom时使用FFTw_WISDOM_ONLY 比如,用户的任务在不支持wisdom的情况下可以重新创建一个方案以避免用户的数据被覆盖。

算法限制 flags

FFTW_DESTROY_INPUT允许就地转换(out-of-place)的变换可以覆盖输入阵列为任意内容,该模式有时候会得到更优秀的方案。

FFTW_PRESERVE_INPUT规定就地转换(out-of-place)的变换不能更改输入阵列。通常情况下,特别是 r2c和 hc2r(复数到实数),FFTW_DESTROY_INPU是默认模式。其余的情况,将通过使用FFTW_PRESERVE_INPLO得变换方案放弃那些会破坏输入阵列的算法。对于二维c2r变换,方案将返回NULL即该任务不支持FFTW_PRESERVE_INPU模式。

FFTW_UNALIGNED 禁止算法发布任何对输入输出阵列进行对齐的请求
(SIMD 禁用)通常情况下该模式很少使用,因为方案会自动检测失调阵列。唯一使用此模式的是当你用一个老的方案去对新的不同长度的阵列进行变换时的用来对齐(用fftw_malloc分配空间的数据不必使用该模式)。

方案限时

extern void fftw_set_timelimit(double seconds);

该函数在方案中规定大约最大用时多少秒。如果参数(seconds==FFTW_NO_TIMELIMIT默认值,负数),则不设限时。反之,创建方案过程中会对不同的方案模式挨个测试,直到测试完成或超时,然后从中挑出最佳方案。例如,使用 FFTW_PATIEN模式,如果加上限时,FFTW会先测试FTW_ESTIMATE摸式,然后 FFTW_MEASURE模式,如果时间允许最后是
FFTW_PATIEN模式。如果设定的是FFTW_EXHAUSTIV膜式,FFTW最终还要测试FFTW_EXHAUSTIV模式。

注意,seconds参数只是一个粗略的限制;实际上,可能由于方案创建过程被放在了一个不能被打断的操作中间,导致花费更多的时间而产生超时。不过起码方案会工作在FFTW_ESTIMATE模式下(相当于超时时间设定为0)。

FFTW使用方案(创建计划)

所有的变换类型方案信息存放在fftw_plan (一个不透明的指针类型 ),该类型存放着包括输入输出数据指针的所有变换所需信息。

创建方案不仅仅收集数据信息,还包括对输入数据的分析,从多种不同特点的算法中选择最适合的一种或几种并进行数据预处理,―稍后的执行变换仅按照方案提供的信息执行变换即可,FFTW将变换分成方案和执行两部分对于相同类型方案,不同输入数据的多次变换,可以省略前面的步骤,重复调用执行变换即可,从而在不损失变换精度和速度的前提下令平均变换效率大大提高。

void fftw_execute(const fftw_plan plan);

在不改变 fftw_plan的前提下,上面的函数可以多次执行,因而可以处理连续数据。

fftw_execute是FFTW中唯一线程安全的函数。

void fftw_destroy_plan(fftw_plan plan);
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐