减少FFT的处理时间

时间:2013-03-31 20:14:14

标签: java android performance fft

我目前正在开发适用于Android的Java。我尝试实现FFT以实现一种频率观察器。

实际上我能够做到,但显示器根本不流畅。 我添加了一些跟踪以检查代码的每个部分的处理时间,事实是FFT需要大约300ms才能应用于我的复杂阵列,它拥有4096个元素。我需要它不到100毫秒,因为我的线程(显示频率)每100毫秒刷新一次。我减少了初始数组,以便FFT结果只拥有1028个元素,并且它可以工作,但结果已被弃用。

有人有想法吗?

我使用了可在互联网上找到的默认fft.java和Complex.java类。

有关信息,我的计算FFT的代码如下:

int bytesPerSample = 2;
Complex[] x = new Complex[bufferSize/2] ;

for (int index = 0 ; index < bufferReadResult - bytesPerSample + 1; index += bytesPerSample)
{
// 16BITS = 2BYTES

    float asFloat = Float.intBitsToFloat(asInt);


    double sample = 0;
    for (int b = 0; b < bytesPerSample; b++) {
        int v = buffer[index + b];
        if (b < bytesPerSample - 1 || bytesPerSample == 1) {
                v &= 0xFF;
        }
                        sample += v << (b * 8);
     }

    double sample32 = 100 * (sample / 32768.0); // don't know the use of this compute...
    x[index/bytesPerSample] = new Complex(sample32, 0);
}


    Complex[] tx = new Complex[1024]; // size = 2048 

///// reduction of the size of the signal in order to improve the fft traitment time
for (int i = 0; i < x.length/4; i++)
{

    tx[i] = new Complex(x[i*4].re(), 0);

 }

// Signal retrieval thanks to the FFT
fftRes = FFT.fft(tx);

3 个答案:

答案 0 :(得分:2)

我不了解Java,但是你在输入数据和一系列复杂值之间进行转换似乎非常复杂。您正在构建两个复杂数据阵列,其中只需要一个。

它也闻起来像你的复杂的真实和想象的价值是双倍的。对于你需要的东西而言,这是最重要的,而且无论如何,ARM在双算术方面都很慢。是否存在基于单精度浮点数的复杂类?

第三,你通过用零填充复数的虚部来对真实数据执行复杂的fft。虽然结果是正确的,但是它的工作量是两倍(除非常规足够聪明地发现它,我怀疑)。如果可能的话,对您的数据执行真正的fft并节省一半的时间。

然后正如西蒙所说,存在避免垃圾收集和内存分配的整个问题。

看起来您的FFT没有预备步骤。这意味着例程FFT.fft()每次都在计算复指数。 FFT计算的最长部分是计算复指数,这是一种耻辱,因为对于任何给定的FFT长度,指数都是常数。它们根本不依赖于您的输入数据。在实时世界中,我们使用FFT例程,我们在程序开始时计算一次指数,然后实际的fft本身将该const数组作为其输入之一。不知道你的FFT类是否可以做类似的事情。

如果你最终去了像FFTW这样的东西,那么你将不得不习惯从Java中调用C代码。还要确保你得到的版本支持(我认为)NEON,ARM对SSE,AVX和Altivec的回答。值得仔细阅读他们的发行说明进行检查。另外,我强烈怀疑,如果要求它对单精度浮点数执行FFT而不是双精度,FFTW只能提供显着的加速。

Google运气!

- 编辑 -

我的意思当然是“祝你好运”。快速给我一个真正的键盘,这些触摸屏是不可靠的......

答案 1 :(得分:0)

首先,感谢您的所有答案。 我跟着他们做了两次测试:

  • 第一个,我用float替换我的Complex类中使用的double。结果只是好一点,但还不够。

  • 然后我重新编写了fft方法,以便不再使用Complex,而是使用二维float数组。对于此数组的每一行,第一列包含实部,第二列包含虚部。 我还更改了我的代码,以便只在onCreate方法上实例化一次float数组。

结果......最糟糕!!现在它需要超过500毫秒而不是300毫秒。 我现在不知道该怎么做。

你可以在下面找到最初的fft功能,然后是我重新改写的功能。 谢谢你的帮助。

// compute the FFT of x[], assuming its length is a power of 2
public static Complex[] fft(Complex[] x) {
    int N = x.length;

    // base case
    if (N == 1) return new Complex[] { x[0] };

    // radix 2 Cooley-Tukey FFT
    if (N % 2 != 0) { throw new RuntimeException("N is not a power of 2 : " + N); }

    // fft of even terms
    Complex[] even = new Complex[N/2];
    for (int k = 0; k < N/2; k++) {
        even[k] = x[2*k];
    }
    Complex[] q = fft(even);

    // fft of odd terms
    Complex[] odd  = even;  // reuse the array
    for (int k = 0; k < N/2; k++) {
        odd[k] = x[2*k + 1];
    }
    Complex[] r = fft(odd);

    // combine
    Complex[] y = new Complex[N];
    for (int k = 0; k < N/2; k++) {
        double kth = -2 * k * Math.PI / N;
        Complex wk = new Complex(Math.cos(kth), Math.sin(kth));
        y[k]       = q[k].plus(wk.times(r[k]));
        y[k + N/2] = q[k].minus(wk.times(r[k]));
    }

    return y;
}

public static float[][] fftf(float[][] x) {
    /**
     *  x[][0] = real part
     *  x[][1] = imaginary part
     */

    int N = x.length;

    // base case
    if (N == 1) return new float[][] { x[0] };

    // radix 2 Cooley-Tukey FFT
    if (N % 2 != 0) { throw new RuntimeException("N is not a power of 2 : " + N); }

    // fft of even terms
    float[][] even = new float[N/2][2];
    for (int k = 0; k < N/2; k++) {
        even[k] = x[2*k];
    }
    float[][] q = fftf(even);

    // fft of odd terms
    float[][] odd  = even;  // reuse the array
    for (int k = 0; k < N/2; k++) {
        odd[k] = x[2*k + 1];
    }
    float[][] r = fftf(odd);

    // combine
    float[][] y = new float[N][2];
    double kth, wkcos, wksin    ;
    for (int k = 0; k < N/2; k++) {
        kth = -2 * k * Math.PI / N;
        //Complex wk = new Complex(Math.cos(kth), Math.sin(kth));
        wkcos = Math.cos(kth)   ;   // real part
        wksin = Math.sin(kth)   ;   // imaginary part

        //  y[k]       = q[k].plus(wk.times(r[k]));
        y[k][0] = (float) (q[k][0] + wkcos * r[k][0] - wksin * r[k][1]);
        y[k][1] = (float) (q[k][1] + wkcos * r[k][1] + wksin * r[k][0]);

        //  y[k + N/2] = q[k].minus(wk.times(r[k]));
        y[k + N/2][0] = (float) (q[k][0] - (wkcos * r[k][0] - wksin * r[k][1]));
        y[k + N/2][1] = (float) (q[k][1] - (wkcos * r[k][1] + wksin * r[k][0]));
    }

    return y;
}

答案 2 :(得分:0)

实际上我认为我并不了解一切。

  • 首先,关于Math.cos和Math.sin:你希望我每次都不计算它?你的意思是我应该只将整个值实例化一次(例如将它存储在数组中)并将它们用于每个计算?
  • 其次,关于N%2,确实它不是很有用,我可以在调用函数之前进行测试。
  • 第三,关于西蒙的建议:我把他说的和你说的混合在一起,这就是为什么我用二维浮点[] []代替了复合体。如果这不是他的建议,那么它是什么?
  • 至少,我不是FFT专家,所以你做一个“真正的FFT”是什么意思?你的意思是我想象中的部分没用吗?如果是这样,我不确定,因为稍后在我的代码中,我计算每个频率的大小,所以sqrt(real [i] * real [i] + imag [i] * imag [i])。我认为我想象中的部分不等于零...

谢谢!