安全,快速FFT(Safe and fast FFT)

2019-07-28 22:09发布

由Herb Sutter的引人注目的演讲启发不是你父亲的C ++ ,我决定再看看C的最新版本++使用微软的Visual Studio 2010中我是赫伯的说法,C ++是“安全,快速”,因为我写了很多特别感兴趣性能关键代码。

作为一个标杆,我决定尝试写同样简单的FFT算法在不同的语言。

我想出了下面的C ++代码11,使用内置的complex型和vector集合:

#include <complex>
#include <vector>

using namespace std;

// Must provide type or MSVC++ barfs with "ambiguous call to overloaded function"
double pi = 4 * atan(1.0);

void fft(int sign, vector<complex<double>> &zs) {
    unsigned int j=0;
    // Warning about signed vs unsigned comparison
    for(unsigned int i=0; i<zs.size()-1; ++i) {
        if (i < j) {
            auto t = zs.at(i);
            zs.at(i) = zs.at(j);
            zs.at(j) = t;
        }
        int m=zs.size()/2;
        j^=m;
        while ((j & m) == 0) { m/=2; j^=m; }
    }
    for(unsigned int j=1; j<zs.size(); j*=2)
        for(unsigned int m=0; m<j; ++m) {
            auto t = pi * sign * m / j;
            auto w = complex<double>(cos(t), sin(t));
            for(unsigned int i = m; i<zs.size(); i+=2*j) {
                complex<double> zi = zs.at(i), t = w * zs.at(i + j);
                zs.at(i) = zi + t;
                zs.at(i + j) = zi - t;
            }
        }
}

请注意,此功能仅适用于n -元素的向量,其中n是2的整数次幂。 任何人都希望快速FFT代码,对任何工作n应该看看FFTW 。

据我了解,传统的xs[i]从C语法用于索引一个vector不会做边界检查,因此,是不是内存安全的,可以是内存错误,如非确定性的腐败和内存访问冲突的来源。 所以我用xs.at(i)来代替。

现在,我想这个代码是“安全,快速”的,但我不是一个C ++ 11个专家,所以我想请为改善此代码,将使它更地道或有效率?

Answer 1:

我认为你是在你使用的过于“安全”()。 在大多数情况下,你使用的索引是由容器中的大小循环被约束平凡verifable。

  for(unsigned int i=0; i<zs.size()-1; ++i) { 
    ...
    auto t = zs.at(i); 

唯一的我要离开作为()s为第(i + J)秒。 它不是立即明显他们是否会始终受到限制(尽管如果我真的不确定我可能手动检查 - 但我不熟悉的FFT足有这种情况的意见)。

也有一些固定的计算被重复用于每个循环迭代:

int m=zs.size()/2;
pi * sign
2*j

和zs.at(I + J)被计算两次。

这有可能是优化器可以捕捉这些 - 但如果你是这当作性能的关键,你有你的计时器测试它,我会吊出来的循环(或者,在zs.at的情况下(I + J ),只需要在第一次使用的参考),看看是否能影响定时器。

谈起第二个猜测的优化器:我敢肯定的是,调用.size()将被内联为,至少一个内部成员变量直接调用 - 但考虑到有多少次你叫我也尝试与引入局部变量zs.size()和zs.size() - 1前期。 他们更有可能被放入寄存器,太。

我不知道有多大的差别(如果有的话),这一切会对你的总运行时间 - 其中一些可能已经由优化器捕获,并且相比所涉及的计算差异可能较小 - 但值得一射击。

至于是惯用我唯一的评论,真的,是大小()返回一个std ::为size_t(这通常是一个unsigned int一个typedef - 但它更地道使用该类型来代替)。 如果你确实想使用自动,但避免了警告,你可以尝试添加后缀UL在0 - 不知道我会说,是地道的,虽然。 我想你已经不是在这里不使用迭代器惯用少,但我可以看到你为什么不能这样做(容易)。

更新

我给我所有的建议一试,他们都有一个衡量的绩效改进 - 除了第i + j和2 *∫precalcs - 他们实际上造成了略有放缓! 我推测,他们要么阻止编译器优化或使用寄存器一些事情阻止它。

总的来说,我得到了> 10%PERF。 改进与建议。 我想,如果环路的第二块被重构一点,以避免跳跃更可以有 - 这样训练使SSE2指令集可以给出一个显著提升(我没有尝试,因为是和出现小幅下滑)。

我认为,重构,与使用类似MKL的COS和罪恶调用应该给予更大的一起,而不太脆,改进。 而且,无论这些事情是依赖于语言的(我知道,这原本被相比,F#实现)。

更新2

我忘了提,预先计算zs.size() 确实有所作为。

更新3

还忘了说了(直到经到OP评论@xeo提醒),块以下的我置于<J检查可以归结为一个std ::互换。 这是更地道,并至少为高性能 - 在书面最坏的情况下应内嵌到相同的代码。 事实上,当我做到了,我看到了在性能没有变化。 在其他情况下,它可能会导致性能增益,如果移动的构造函数可用。



文章来源: Safe and fast FFT