旧文备份:FFTW介绍

1. FFTW介绍

FFTW由麻省理工学院计算机科学实验室超级计算技术组开发的一套离散傅立叶变换(DFT)的计算库,开源、高效和标准C语言编写的代码使其得到了非常广泛的应用,Intel的数学库和Scilib(类似于matlab的科学计算软件)都使用FFTW做FFT计算。

FFTW是计算离散Fourier变换(DFT)的快速C程序的一个完整集合。

l        它可计算一维或多维、实和复数据以及任意规模的DFT;甚至包括正弦/余弦变换和离散哈特莱变换(DHT)。

l        FFTW输入数据长度任意。

l        FFTW支持任意多维数据。

l        FFTW 支持 SSE, SSE2, Altivec,和 MIPS 指令集。

l        FFTW还包含对共享和分布式存储系统的并行变换。

FFTW的基本结构:

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

FFTW为了加快用户的使用集成速度,提供了三种不同层次的接口。

  1. 连续数据的单一变换的基本接口。
  2. 计算多重和步进阵列数据的高级接口。
  3. 支持通用数据布局、多重和步进的顶级接口。

大部分的用户使用基本接口就可以满足需要,顶级接口需要更小心使用以避免出错,因此需要花更多的时间去理解掌握。FFTW不仅提供了数据自适应,还提供了高级用户定制功能。例如,由于代码空间不足,可以去掉用户不需要的功能代码。相反,FFTW还可以拓展数据结构。

2. 数据

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

2.1.类型

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

typedef double fftw_complex[2];

2.2 精度

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

2.3. 分配存储空间

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

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

3. 使用方案

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

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

void fftw_execute(const fftw_plan plan);

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

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

void fftw_destroy_plan(fftw_plan plan);

上面的函数用来释放方案包括其相关的数据。

4. 基本接口

4.1. 一维复数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); /* repeat as needed */

...

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 ) ,指出变换的方向,从技术角度讲,它是变换中的指数标志。

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

一旦变换方案创建完成你就可以刷新使用你的输入/输出数组而执行多次变换,实际的变换计算工作由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和FFTW_MEASURE标志,还有许多其它的标志存在,例如,如果可以忍受更长的时间,你可以使用FFTW_PATIENT标志来生成一个更有效率的方案(见FFTW Reference)。你甚至可以将方案保存起来留待以后的应用,见Words of Wisdom-Saving Plans

4.2. 一维实数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 flags);

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

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

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

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

5.方案的flag参数

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

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

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

5.1方案设计方式flag

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

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

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

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

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

5.2算法限制flag

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

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

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

5.3方案限时

       extern void fftw_set_timelimit(double seconds);

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

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

(以上内容,翻译自www.fftw.org,本人水平有限,可能不够准确,欢迎批评指正!)

(2009.7.9)

时间: 2024-10-31 08:15:48

旧文备份:FFTW介绍的相关文章

旧文备份:rtlinux安装手册

前段时间接触了几天RTLinux,折腾了好几天才总算把它安装上,得益于Prof. Chang-Gun Lee的安装建议,觉得该文档可能会对准备尝试安装RTLinux的朋友们有帮助,本人英语很烂,也比较懒,好在也没几页,就试着翻译了一下,有需要的朋友可以将就着看看,英语好的可以去看原文. 总体感觉,RTLinux的硬件兼容性实在不敢恭维,同样的内核版本,同样的配置在有的平台上就跑不起来,反正我试了一个Intel845G主板的台式兼容机和一个SIS主板的神州移动PC,那个兼容机一加载RTLinux模

旧文备份:怎样利用好单片机上的存储器资源来实现OD的存储与访问

我们知道OD(对象字典)是CANopen的核心,所有功能都是围绕它开展的,是协议栈的数据中心,良好的OD实现是协议栈高效稳定运行的基础,而OD的实现最基本的一点就是怎么去保存它.因为OD的内容比较杂,读写属性上,有只读数据.只写数据.可读写数据:保存要求上有非易失和掉电丢失两种类型:数据类型上有字符型.整型.长整型等等:存储格式上有8位.16位.32位等.其它的不管,本文现只讨论怎么利用单片机的资源去尽量满足OD的存储需求. 有人会以为这还要讨论么?只读的就放在只读存储器中,可写的就放在RAM中

旧文备份: 怎样实现SDO服务

SDO是CANopen协议中最复杂的一部分,带有应答机制,有多种传输方式,并且完整的SDO功能节点需提供1个SDO server和多个SDO client,因此SDO的实现异常困难,协议多种传输方式的解析处理还有迹可循,多个SDO client服务和多个对SDO server的访问的协调就不容易了,这里介绍一种方法——SDO线程来解决. 注意,这里的线程可不是操作系统提供的多线程技术,况且为保证协议栈良好的移植性,在CANopen协议栈核心代码里中也不好去调与操作系统相关的库函数.我们这里的SD

旧文备份:利用一个定时器实现多个虚拟定时器的两种方法

固定周期法 使用一个硬件定时器进行固定周期(比如1ms)定时,用一个结构体数组作为软定时器描述表,数组的结构体数就是最大虚拟定时器的数量,每个结构体的成员都包括虚拟定时器状态(空闲.激活.运行.超时触发.周期触发).定时值(换算成定时周期数,例如1ms的硬件定时周期,现进行125ms的定时,定时值就是125).标识ID和回调函数等:用一个变量作为定时周期计数器,每次进入定时中断,重置定时器,扫描结构体数组中的每个成员结构体,对定时值做减一操作,然后判断该定时值是否为0,是则判定该值对应的虚拟定时

旧文备份:CANopen协议中SDO服务

SDO是服务数据对象接口(Service Data Obiect)的缩写,顾名思义提供服务数据的访问接口,服务数据就是一些实时性要求不高的数据,一般是指节点配置参数,因此,SDO一般用来配置和获得节点的配置参数.其优先级只比心跳(Heartbeat)高. SDO既然称之为服务,那就要有服务的提供者和使用者,提供者就是SDO server,使用者就是SDO client,在CANopen网络中每个节点都要有一个SDO server,因为每个节点的对象字典大部分对象都是通过SDO来访问的,对象字典的

旧文备份:Python国际化支持

Python通过gettext模块支持国际化(i18n),可以实现程序的多语言界面的支持,下面是我的多语言支持实现: 在python安装目录下的./Tools/i18n/(windows下例 D:\Program Files\Python25\Tools\i18n)目录中找到pygettext.py运行之,生成翻译文件模版messages.pot,内容大概是这个样子: # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FI

旧文备份:安装cygwin及在console下输入和显示中文

1.下载Cygwin.exe文件,双击安装,首先在"Choose A Download Source"的时候选择"Download Without Installing",Next>. 2.选择本地包路径"Select Local Package Directory",即是选择将要下载的软件包的存放路径.默认在Cygwin.exe目录下.Next> 3.选择一个下载源"Choose A Download Site"

旧文备份:AVR读写EEPROM分析

由于AVR的EEPROM写周期比较长(一般为毫秒级),因此在编程使用过程中要特别注意.对于读EEPROM没什么好说的,读一个字节的数据要耗费4个时钟周期,可以忍受,写就比较麻烦了,虽然放在EEPROM的数据都不是频繁访问的;虽然可以用读-比较-写的机制降低EEPROM的写操作频度,但在写入过程中,过长的写入周期还是会造成一些问题,下面就分析一下几种方式的EEPROM写操作. 循环查询式 将地址和数据写入EEPROM相关的寄存器,置写标志后就循环不断查询写完成标志,直到写完成,退出循环,顺序执行其

旧文备份:对象字典0x1005和0x1006的理解

SYNC不一定由主站产生,因此,产生SYNC的节点,0x1005对象的值一般是0x40000080,第30位为1表示本节点产生 SYNC,而本节点的0x1006对象就是产生同步周期值了;而接收SYNC的节点0x1005对象值一般是0x80,第30位是0表示本身不产生 SYNC,而接收COB-ID为0x80的报文作为同步帧,该节点0x1006一般置0,没什么用处了.(于2009.2.13)