FFTW vs Matlab FFT

FFTW vs Matlab FFT

本文关键字:FFT Matlab vs FFTW      更新时间:2023-10-16

我在matlab central上发布了这篇文章,但没有得到任何回应,所以我想我会在这里重新发布。

我最近在Matlab中编写了一个简单的例程,在for循环中使用FFT;FFT主导了计算。我在mex中编写了相同的例程,只是为了实验目的,它调用FFTW 3.3库。事实证明,对于非常大的数组,matlab例程比mex例程运行得快(大约快两倍)。mex例程使用智慧和执行相同的FFT计算。我也知道matlab使用FFTW,但是否有可能他们的版本稍微优化?我甚至使用了fftw_详尽标志,但对于大型数组,它的速度仍然是MATLAB的两倍。此外,我确保我使用的matlab是单线程的"-singleCompThread"标志,我使用的mex文件不在调试模式。只是好奇如果这是情况-或者如果有一些优化matlab正在使用的引擎盖下,我不知道。谢谢。

这是mex部分:

void class_cg_toeplitz::analysis() {
// This method computes CG iterations using FFTs
    // Check for wisdom
    if(fftw_import_wisdom_from_filename("cd.wis") == 0) {
        mexPrintf("wisdom not loaded.n");
    } else {
        mexPrintf("wisdom loaded.n");
    }
    // Set FFTW Plan - use interleaved FFTW
    fftw_plan plan_forward_d_buffer;    
    fftw_plan plan_forward_A_vec;       
    fftw_plan plan_backward_Ad_buffer;
    fftw_complex *A_vec_fft;
    fftw_complex *d_buffer_fft;
    A_vec_fft = fftw_alloc_complex(n);
    d_buffer_fft = fftw_alloc_complex(n);
    // CREATE MASTER PLAN - Do this on an empty vector as creating a plane 
    // with FFTW_MEASURE will erase the contents; 
    // Use d_buffer
    // This is somewhat dangerous because Ad_buffer is a vector; but it does not
    // get resized so &Ad_buffer[0] should work
    plan_forward_d_buffer = fftw_plan_dft_r2c_1d(d_buffer.size(),&d_buffer[0],d_buffer_fft,FFTW_EXHAUSTIVE);
    plan_forward_A_vec = fftw_plan_dft_r2c_1d(A_vec.height,A_vec.value,A_vec_fft,FFTW_WISDOM_ONLY);
    // A_vec_fft.*d_buffer_fft will overwrite d_buffer_fft
    plan_backward_Ad_buffer = fftw_plan_dft_c2r_1d(Ad_buffer.size(),d_buffer_fft,&Ad_buffer[0],FFTW_EXHAUSTIVE);
    // Get A_vec_fft
    fftw_execute(plan_forward_A_vec);
    // Find initial direction - this is the initial residual
    for (int i=0;i<n;i++) {
        d_buffer[i] = b.value[i];
        r_buffer[i] = b.value[i];
    }    
    // Start CG iterations
    norm_ro = norm(r_buffer);
    double fft_reduction = (double)Ad_buffer.size(); // Must divide by size of vector because inverse FFT does not do this
    while (norm(r_buffer)/norm_ro > relativeresidual_cutoff) {        
        // Find Ad - use fft
        fftw_execute(plan_forward_d_buffer);    
        // Get A_vec_fft.*fft(d) - A_vec_fft is only real, but d_buffer_fft
        // has complex elements; Overwrite d_buffer_fft        
        for (int i=0;i<n;i++) {
            d_buffer_fft[i][0] = d_buffer_fft[i][0]*A_vec_fft[i][0]/fft_reduction;
            d_buffer_fft[i][1] = d_buffer_fft[i][1]*A_vec_fft[i][0]/fft_reduction;
        }        
        fftw_execute(plan_backward_Ad_buffer); 
        // Calculate r'*r
        rtr_buffer = 0;
        for (int i=0;i<n;i++) {
            rtr_buffer = rtr_buffer + r_buffer[i]*r_buffer[i];
        }    
        // Calculate alpha
        alpha = 0;
        for (int i=0;i<n;i++) {
            alpha = alpha + d_buffer[i]*Ad_buffer[i];
        }    
        alpha = rtr_buffer/alpha;
        // Calculate new x
        for (int i=0;i<n;i++) {
            x[i] = x[i] + alpha*d_buffer[i];
        }   
        // Calculate new residual
        for (int i=0;i<n;i++) {
            r_buffer[i] = r_buffer[i] - alpha*Ad_buffer[i];
        }   
        // Calculate beta
        beta = 0;
        for (int i=0;i<n;i++) {
            beta = beta + r_buffer[i]*r_buffer[i];
        }  
        beta = beta/rtr_buffer;
        // Calculate new direction vector
        for (int i=0;i<n;i++) {
            d_buffer[i] = r_buffer[i] + beta*d_buffer[i];
        }  
        *total_counter = *total_counter+1;
        if(*total_counter >= iteration_cutoff) {
            // Set total_counter to -1, this indicates failure
            *total_counter = -1;
            break;
        }
    }
    // Store Wisdom
    fftw_export_wisdom_to_filename("cd.wis");
    // Free fft alloc'd memory and plans
    fftw_destroy_plan(plan_forward_d_buffer);
    fftw_destroy_plan(plan_forward_A_vec);
    fftw_destroy_plan(plan_backward_Ad_buffer);
    fftw_free(A_vec_fft);
    fftw_free(d_buffer_fft);
};

下面是matlab部分:

% Take FFT of A_vec.
A_vec_fft = fft(A_vec); % Take fft once
% Find initial direction - this is the initial residual 
x = zeros(n,1); % search direction
r = zeros(n,1); % residual
d = zeros(n+(n-2),1); % search direction; pad to allow FFT
for i = 1:n
    d(i) = b(i); 
    r(i) = b(i); 
end
% Enter CG iterations
total_counter = 0;
rtr_buffer = 0;
alpha = 0;
beta = 0;
Ad_buffer = zeros(n+(n-2),1); % This holds the product of A*d - calculate this once per iteration and using FFT; only 1:n is used
norm_ro = norm(r);
while(norm(r)/norm_ro > 10^-6)
    % Find Ad - use fft
    Ad_buffer = ifft(A_vec_fft.*fft(d)); 
    % Calculate rtr_buffer
    rtr_buffer = r'*r;
    % Calculate alpha    
    alpha = rtr_buffer/(d(1:n)'*Ad_buffer(1:n));
    % Calculate new x
    x = x + alpha*d(1:n);
    % Calculate new residual
    r = r - alpha*Ad_buffer(1:n);
    % Calculate beta
    beta = r'*r/(rtr_buffer);
    % Calculate new direction vector
    d(1:n) = r + beta*d(1:n);      
    % Update counter
    total_counter = total_counter+1; 
end

在时间方面,对于N = 50000, b = 1: N,用mex计算大约需要10.5秒,用matlab计算大约需要4.4秒。我用的是R2011b。谢谢

一些观察,而不是一个明确的答案,因为我不知道MATLAB FFT实现的任何细节:

  • 基于你的代码,我可以看到速度差异的两个解释:
    • 速度差异是由FFT优化水平的差异来解释的
    • MATLAB中的while循环执行的次数要少得多

我假设您已经研究了第二个问题,并且迭代的数量是可比较的。(如果不是,这很可能是一些准确性问题,值得进一步调查。)

现在,关于FFT速度比较:

  • 是的,理论是FFTW比其他高级FFT实现更快,但它只有在你比较苹果与苹果时才相关:这里你是在更低的层次上比较实现,在汇编级别,不仅是算法的选择,而且是对特定处理器的实际优化,并且由具有不同技能的软件开发人员来发挥作用
  • 在过去的一年中,我已经在许多处理器上优化或审查了优化的fft汇编(我在基准测试行业),优秀的算法只是故事的一部分。对于你正在编码的架构来说,有一些考虑是非常具体的(考虑延迟、指令调度、寄存器使用的优化、内存中数据的安排、考虑分支占用/未占用的延迟等),这些考虑与算法的选择一样重要。
  • 当N=500000时,我们也在谈论大的内存缓冲区:这是另一个优化的大门,可以快速地针对运行代码的平台进行更具体的优化:你如何很好地避免缓存丢失将不会由算法决定,而是由数据流如何以及软件开发人员可能使用的优化来有效地将数据导入和取出内存。
  • 虽然我不知道MATLAB FFT实现的细节,但我很确定一群DSP工程师已经(并且仍然)在优化它,因为它是许多设计的关键。这很可能意味着MATLAB有正确的开发人员组合来生成更快的FFT。

这是由于底层和特定于体系结构的优化而获得的典型性能增益。

Matlab使用FFT从英特尔MKL(数学内核库)二进制(MKL .dll)。这些是Intel针对Intel处理器优化的例程(在汇编级)。即使在AMD的处理器上,它似乎也能很好地提升性能。

FFTW看起来像一个没有经过优化的普通c库。因此,使用MKL可以获得性能增益。

我在MathWorks网站上发现了以下评论[1]:

关于2的大幂的注意事项:对于幂为的FFT维度2、在2^14和2^22之间,采用MATLAB软件专用预装在其内部数据库中的信息,以优化FFT计算。当FTT的维度是2的幂时,不执行调谐。除非您使用命令fftw('wisdom',[])清除数据库。

虽然它与2的幂有关,但它可能暗示MATLAB在使用FFTW用于某些(大)数组大小时使用自己的"特殊智慧"。考虑:2^16 = 65536。

[1] R2013b文档可从http://www.mathworks.de/de/help/matlab/ref/fftw.html(2013年10月29日访问)

EDIT: @wakjah对这个答案的回答是准确的:FFTW确实支持通过其Guru接口分割真实和虚构的内存存储。因此,我关于黑客攻击的说法并不准确,但如果FFTW的Guru界面没有被使用,则可以很好地适用——这是默认情况下的情况,所以仍然要小心!

首先,很抱歉迟到了一年。我不相信您看到的速度提高来自MKL或其他优化。FFTW和Matlab之间有一些非常根本的不同,那就是复杂的数据如何存储在内存中。

在Matlab中,复向量X的实部和虚部是单独的数组Xre[i]和Xim[i](在内存中是线性的,当分别对它们中的任何一个操作时都是有效的)。

在FFTW中,实部和虚部默认交错为双[2],即X[i][0]为实部,X[i][1]为虚部。

因此,要在mex文件中使用FFTW库,不能直接使用Matlab数组,而必须首先分配新的内存,然后将Matlab的输入打包为FFTW格式,然后将FFTW的输出解包为Matlab格式。例如

X = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * N);
Y = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * N);
然后

for (size_t i=0; i<N; ++i) {
    X[i][0] = Xre[i];
    X[i][1] = Xim[i];
}
然后

for (size_t i=0; i<N; ++i) {
    Yre[i] = Y[i][0];
    Yim[i] = Y[i][1];
}

因此,这需要2倍的内存分配+ 4x的读+ 4x的写——所有的大小都是N.这在大型问题上确实会造成速度上的损失。

我有一种预感,Mathworks可能已经破解了FFTW3代码,允许它直接以Matlab格式读取输入向量,从而避免了上述所有问题。

在这种情况下,只能分配X并使用X代替Y来运行FFTW(作为fftw_plan_*(N, X, X, ...)而不是fftw_plan_*(N, X, Y, ...)),因为它将被复制到Yre和Yim Matlab向量,除非应用程序需要/受益于保持X和Y分开。

EDIT:在运行Matlab的fft2()和基于fftw3库的代码时实时查看内存消耗,它表明Matlab只分配一个额外的复杂数组(输出),而我的代码需要两个这样的数组(*fftw_complex缓冲区加上Matlab输出)。Matlab和fftw格式之间的就地转换是不可能的,因为Matlab的实数组和虚数组在内存中不是连续的。这表明Mathworks破解了fftw3库,使用Matlab格式读取/写入数据。

针对多个调用的另一个优化是持久化分配(使用mexMakeMemoryPersistent())。我不确定Matlab实现是否也能做到这一点。

欢呼。

注。作为旁注,Matlab复杂数据存储格式对于分别操作实向量或虚向量更有效。在FFTW的格式中,你必须做++2内存读取。