fftw的使用

获取方法

FFTW首页:http://www.fftw.org/

据说FFTW是世界上最快的FFT。

下载地址:http://www.fftw.org/download.html

功能介绍

ps:以下部分来自于fftw官方网页的翻译。

  FFTW软件包是由麻省理工学院的Matteo Frigo和Steven G. Johnson开发的。
  FFTW是一个C子例程库,用于计算一个或多个维度的离散傅里叶变换(DFT)、任意输入大小、实数或者复数,(以及离散余弦/正弦变换或DCT/DST)。FFTW作为一个免费软件,将会成为大多数应用程序的FFT库。
  在各种平台上执行的基准测试表明,FFTW的性能通常优于其他公开可用的FFT软件,甚至与供应商调优的代码具有竞争力。然而,与供应商调优的代码相比,FFTW的性能是可移植的:同样的程序不需要修改就可以在大多数架构上执行得很好。“FFTW”这个名字,它代表着“西方最快的傅里叶变换”这个有点古怪的头衔。
fftw的功能特点:

  • 速度。(支持SSE/SSE2/Altivec,从3.0版开始。版本3.3.1支持AVX和ARM Neon。)
  • 可以转换任意维度的数据,一维、多维变换。
  • 数据长度任意,不用局限于2的整数次幂长度。(带有小素数因子的大小是最好的,但是FFTW甚至对素数大小也使用O(N log N)算法。)
  • 纯实数的输入或输出数据的快速转换。
  • 可实现离散余弦变换(DCT)和离散正弦变换(DST),类型I-IV。(3.0或更高版本。)
  • 对多个跨跃转换的有效处理。(这允许同时转换多个数组、转换多维数组的一个维度或转换多组件数组的一个字段。)
  • 并行转换:使用带有一些线程(例如POSIX)或OpenMP的SMP机器的平台的并行代码。用于分布式内存转换的MPI版本在FFTW 3.3中也可用。
  • 可移植到任何平台与C编译器。
  • 有HTML和其他格式的说明文档。
  • C和Fortran接口。
  • 免费软件,根据GNU通用公共许可证(GPL,参见FFTW许可证)发布。(对于不希望自己的程序受GPL保护的用户,也可以从MIT购买非免费的许可证。)

版本说明
  如果你还在使用FFTW 2.x,请注意FFTW 2.x最后一次更新是在1999年,现已过时。请升级到FFTW 3.x。FFTW 3.x的AP与FFTW 2.x是不兼容的。由于性能和通用性的原因(请参阅FAQ或手册)。所以本文用fftw3来表示。

程序使用方法

  加载一个程序库通常使用动态链接的方式。所以我们需要对应的.h头文件、lib和dll链接库文件。


步骤1:下载

地址:http://www.fftw.org/install/windows.html
在这里插入图片描述
解压后发现里面只有dll文件和h头文件,没有lib文件,官方网站的说明如下:
这些dll由我们创建,使用MinGW从GNU/Linux交叉编译;得益于mingw-w64项目,64位版本成为可能。您应该能够从任何编译器调用它们。为了从Visual c++中链接到它们,您需要使用vc++中包含的lib.exe程序创建.lib“导入库”。运行:
lib /def:libfftw3-3.def
lib /def:libfftw3f-3.def
lib /def:libfftw3l-3.def
在64位模式的Visual Studio 2008上,可能在其他情况下,您可能需要显式地指定机器:
lib /machine:x64 /def:libfftw3l-3.def

问题1:为什么dll文件要区分32bit还是64bit?

在这里插入图片描述
不同bit的源代码需要的编译器是不同的,lib文件和dll文件在编译之后,都属于二进制文件,所以是需要区分编译环境的。

问题2:为什么lib文件需要自己生成?

根据官网说明,lib文件是需要自己生成的,为什么lib文件要自己生成?为什么不直接给出?原因为:
DLL也有很多优点,例如不同的语言都可以调用VC++编写的DLL(当然要遵守一定规则),而静态链接库一般只有VC++才能使用。当多个程序调用同一个函数时,使用DLL比使用静态链接库节省系统资源,所以WinAPI都是使用DLL方式的。
也就是说,fftw的dll可以用在任意的编译器上,如果想用在vs上,就需要用lib了,所以lib文件和你的编译平台有关,需要自己用编译平台实现。

问题3:def文件是什么?

.def文件中定义dll的输出函数,.def文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。
建立新的工程时选择dll自动就能生成一个.def文件,不用自己建的
.def文件的规则为:
  (1)LIBRARY语句说明.def文件相应的DLL;
  (2)EXPORTS语句后列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号将发挥其作用);
  (3).def 文件中的注释由每个注释行开始处的分号 (😉 指定,且注释不能与语句共享一行。
所以在vs上想生成dll的lib导入文件,需要使用vc++中包含的lib.exe程序创建.lib“导入库”。


步骤2:生成lib导入库文件

首先打开vs2015的命令提示符,不是cmd命令窗口。vs2015的命令提示符又分为x86和x64。(x86指的是一种cpu的架构,x86架构的特点是cpu的寄存器是32位的,因此也叫32位cpu。)
在这里插入图片描述

  • 步骤一:输入lib.exe回车。
  • 步骤二:
    32bit的在x86命令行提示符输入lib/machine:x86 /def:XXXXX.def
    64bit的在x64命令行提示符输入lib/machine:x64 /def:XXXXX.def
    其中XXXXX.def应该是绝对路径,也是最终lib和exp文件生成的位置。
  • 拷贝到目标文件就ok了。

步骤3:配置库文件

  要使用一个库,除了要include其头文件以外(附加包含目录),还要在链接过程中把lib加进去(附加库目录、附加依赖项)。

添加方法:

  • 附加包含目录—添加工程的头文件目录:
    项目->属性->配置属性->C/C+±>常规->附加包含目录:加上头文件的存放目录;
  • 附加库目录—添加文件引用的lib静态库路径:
    项目->属性->配置属性->链接器->常规->附加库目录:加上lib文件的存放目录;
  • 附加依赖项—添加工程引用的lib文件名:
    项目->属性->配置属性->链接器->输入->附加依赖项:加上lib文件名。

步骤4:编程使用

测试程序

#include<stdio.h>
#include<stdlib.h>
#include "fftw3.h"
#define N 5

int main()
{
	int i;
	fftw_complex *din, *out;
	fftw_plan p;
	din = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * N);
	out = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * N);
	if ((din == NULL) || (out == NULL))
	{
		printf("Error:insufficient available memory\n");
	}
	else
	{
		for (i = 0; i<N; i++)/*测试数据*/
		{
			din[i][0] = i + 1;
			din[i][1] = 0;
		}
	}
	p = fftw_plan_dft_1d(N, din, out, FFTW_FORWARD, FFTW_ESTIMATE);
	fftw_execute(p); /* repeat as needed */
	fftw_destroy_plan(p);
	fftw_cleanup();
	for (i = 0; i<N; i++)/*OUTPUT*/
	{
		printf("%f,%fi\n", din[i][0], din[i][1]);
	}
	printf("\n");
	for (i = 0; i<N; i++)/*OUTPUT*/
	{
		printf("%f,%fi\n", out[i][0], out[i][1]);
	}

	if (din != NULL) fftw_free(din);
	if (out != NULL) fftw_free(out);

	system("pause");
	return 0;
}

在这里插入图片描述
结果和matlab计算结果完全相同。

分析

整体步骤:

  • fftw_malloc分配输入输出内存
  • 输入数据赋值
  • 创建变换方案(fftw_plan)
  • 执行变换(fftw_execute)
  • 释放资源

创建完方案函数原型为:(仅仅是创建方案)

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

说明如下:

int nn为数据个数,可以为任意正整数,但如果为一些小因子的乘积计算起来可以更有效,不过即使n为素数算法仍然能够达到O(nlogn)的复杂度。
fftw_complex *in如果in和out指针相同为原位运算,否则为非原位运算。
int signsign可以为正变换FFTW_FORWARD(-1),也可以为逆变换FFTW_BACKWORD(+1),实际上就是变换公式中指数项的符号。需注意FFTW的逆变换没有除以N,即数据正变换再反变换后是原始数据的N倍。
unsigned flagsflags参数一般情况下为FFTW_MEASURE 或 FFTW_ESTIMATE。FFTW_MEASURE表示FFTW会先计算一些FFT并测量所用的时间,以便为大小为n的变换寻找最优的计算方法。依据 机器配置和变换的大小(n),这个过程耗费约数秒(时钟clock精度)。FFTW_ESTIMATE则相反,它直接构造一个合理的但可能是次最优的方 案。总体来说,如果你的程序需要进行大量相同大小的FFT,并且初始化时间不重要,可以使用FFTW_MEASURE,否则应使用 FFTW_ESTIMATE。FFTW_MEASURE模式下in和out数组中的值会被覆盖,所以该方式应该在用户初始化输入数据in之前完成。

  创建完方案(fftw_plan)后,就可以用fftw_execute对指定的 数据in/out做任意次变换。如果想变换一个相同大小(N相等)但数据不同的另外一个数组in,可以创建一个新方案,FFTW会自动重用上次方案的信 息。这一点其实是非常好的,比如你首先用FFTW_MEASURE模式创建了一个最优的变换方案,只要变换数据的大小不变,你可以用 fftw_plan_dft_1d创建新的方案以对新数据执行变换,同时新变换仍然是最优的。一个fftw_plan只能对固定的in/out进行变换, 但可以在变换后改变in的内容(大小不变)以用同一个方案执行新的变换。

为什么 in/out要用fftw_malloc函数动态分配

  使用 ANSI C 或者 C++语言中的 malloc,new 等动态分配,由于不同的编译器内存分配策略 的不同,可能使分配的数组并没有内存对齐,而内存没有对齐,对 FFTW 性能上的影响将是显著的。当然,你可以使用一些语言的特性让内存对齐,但是,FFTW用了更好的办法, FFTW 提供的 fftw_malloc 函数动态分配。
  为什么需要内存对齐需要看我的另一篇博文,动态内存的分配也会涉及内存对齐?,还是不要看了,因为我现在还没有彻底明白怎么回事,先用起来再说吧。。。

使用扩展

plan的种类

FFTW 中,针对不同的情况,提供了下面一些生成策略(plan)的函数接口。

fftw_plan fftw_plan_dft_1d(int n, fftw_complex *in,
fftw_complex *out,int sign, unsigned flags);//一维复数据的DFT、IDFT

fftw_plan fftw_plan_dft_2d(int n0, int n1,fftw_complex
*in, fftw_complex *out,int sign, unsigned flags);//二维复数据DFT、IDFT

fftw_plan fftw_plan_dft_3d(int n0, int n1, int
n2,fftw_complex *in, fftw_complex *out,int sign, unsigned
flags);//三维复数据DFT、IDFT

fftw_plan fftw_plan_dft(int rank, const int
*n,fftw_complex *in, fftw_complex *out,int sign, unsigned
flags);//rank维复数据DFT、IDFT

fftw_plan fftw_plan_dft_r2c_1d(int n, double *in,
fftw_complex *out,
unsigned flags);//一维实数DFT

fftw_plan fftw_plan_dft_c2r_1d(int n, fftw_complex *in,
double *out,
unsigned flags);//一维实数IDFT

fftw_plan fftw_plan_dft_r2c_2d(int n0, int n1,double *in,
fftw_complex *out,
unsigned flags);//二维实数DFT

fftw_plan fftw_plan_dft_r2c_3d(int n0, int n1, int
n2,double *in, fftw_complex *out,unsigned flags);//三维实数DFT

fftw_plan fftw_plan_dft_r2c(int rank, const int *n,double
*in, fftw_complex *out,unsigned flags);//rank维实数DFT

对 plan 的重用

生成一个 plan费时间多,那么,我们可
不可以生成一个 plan,然后在其他的情况下重用这个 plan 呢?答案当然是肯定的。
当然,重用也是有条件的:

  • 输入输出数据的大小相等。
  • 输入输出的数据对齐不变。按照前面所说,都是用 fftw_malloc()就不会有问题了。
  • 变换类型、是否原位变换不变。
    具体函数接口如下所列。
void fftw_execute_dft(const fftw_plan p, fftw_complex *in, fftw_complex *out);
void fftw_execute_dft_r2c(const fftw_plan p, double *in, fftw_complex *out);
void fftw_execute_dft_c2r(const fftw_plan p, fftw_complex*in, double *out);

利用wisdom保存plan信息

wisdom 的大体思路就是把生成好的策略相关的配置信息存储在磁盘里,然后在下次重
新运行程序的时候,把策略相关的配置信息重新载入到内存中,这样在重新生成 plan 的时候就可以节约大量的时间。

如何使用 wisdom?
由于FFTW起初是在linux环境下开发的,当我们需要在windows下使用wisdom的时候,我们需要在头文件 fftw3.h 中添加以下代码。

static void my_fftw_write_char(char c, void *f)
{ fputc(c, (FILE *)f);}

#define fftw_export_wisdom_to_file(f) fftw_export_wisdom(my_fftw_write_char, (void*)(f))

static int my_fftw_read_char(void *f)
{ return fgetc((FILE *)f);}

#define fftw_import_wisdom_from_file(f) fftw_import_wisdom(my_fftw_read_char, (void*)(f))

如果您需要使用其他的精度,则添加代码的方法类似。
wisdom 存储起来的不是 plan 本身,而是和 plan 相关的配置信息,例如内存、寄存器等。所以我们在把 wisdom 载入到内存中后,我们还是需要调用生成 plan 的函数的。使用 wisdom的基本代码框架如下所示。

/* 导出wisdom */
fftw_complex *in = NULL, *out = NULL;
fftw_plan p;
in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * row * col);
out = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * row * col);
p = fftw_plan_dft_2d(row, col,in, out, FFTW_FORWARD, FFTW_MEASURE);
FILE *fp = fopen("fftw_plan_dft_2d.txt", "w");
fftw_export_wisdom_to_file(fp);
fclose(fp);
fftw_destroy_plan(p);
fftw_free(in);
fftw_free(out);
/* 导入wisdom */
Fftw_comlex *in = NULL, *out = NULL;
fftw_plan p;
in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * row * col);
out = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * row * col);
FILE *fp = fopen("fftw_plan_dft_2d.txt", "r");
fftw_import_wisdom_from_file(fp);
fclose(fp);
p = fftw_plan_dft_2d(row, col,in, out, FFTW_FORWARD, FFTW_MEASURE);
for(int i=0; i<row*col; i++)
{
	in[i][0] = (double)i*i + 1;
	in[i][1] = (double)i/3;
}
fftw_execute(p);
fftw_destroy_plan(p);
fftw_free(in);
fftw_free(out);

在使用 wisdom 的时候需要尤其注意的几点:

  • 不管是 fftw_import_wisdom_from_file(FILE *fp)还是 ftw_export_wisdom_to_file(FILE *fp),里面的文件指针 fp 在调用函数前应该是打开的,在调用函数之后也是打开的,需要我们调用函数将它关闭。
  • 每一个wisdom都是针对某一个确定的processor而言的。每次更换了运行的硬件环境,都应该重新生成 wisdom。
  • 每一个 wisdom 都是针对某一个确定的程序而言的。每次修改了程序,即最后的二进制文件不同,都需要重新生成 wisdom。如果不重新生成 wisdom,相对于硬件环境的改变引起的效率下降,这里的效率下降没有那么明显。
  • 对相同的 processor 和相同的 program binary,在每次运行的时候,也会因为虚拟内存使用的不同而导致程序执行效率的改变。这一条对于性能要求特别严格的情况,开发者需要考虑。

参考链接

本篇blog主要是参考以下网页链接,感谢各位作者们的工作。
cyh706510441博主的文章“FFTW的使用”
lib/dll/h文件的关系
https://bbs.csdn.net/topics/40434186
FFTW使用中文参考
https://www.docin.com/p-1247883763.html

  • 14
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值