快速FFT位反转,我可以向后倒计数位反转吗?

时间:2012-03-20 19:21:35

标签: optimization fft reverse bit

我正在使用FFT进行音频处理,我想出了一些可能非常快速的方法来进行所需的位反转,这可能对其他人有用,但由于我的FFT的大小(8192),我正在尝试减少内存使用/缓存刷新对查找表或代码的大小,并提高性能。我见过很多聪明的反转程序;它们都允许你用任意值提供它们并得到一点反转输出,但FFT不需要那种灵活性,因为它们以可预测的顺序进行。首先让我说明我尝试和/或想出的内容,因为它可能是迄今为止最快的,你可以看到问题,然后我会问这个问题。

1)我编写了一个程序,用于生成直通,未循环的x86源代码,可以粘贴到我的FFT代码中,该代码读取音频样本,将其乘以窗口值(即查找表本身),然后只需将结果值放在x86寻址模式中的绝对值的正确位反转排序位置,如:movlps [edi + 1876],xmm0。对于较小的FFT尺寸,这是绝对最快的方法。问题是当我直接编写代码来处理8192值时,代码增长超出L1指令缓存大小并且性能下降。当然,相比之下,32K位反转查找表与32K窗口表以及其他东西混合在一起,也太大而不适合L1数据缓存,性能下降,但这就是我目前的做法。

2)我在位反转序列中发现了可用于减少查找表大小的模式,例如使用4位数(0..15)作为示例,位反转序列如下所示:0, 8,4,12,2,10,6,14 | 1,5,9,13,3,11,7,15。首先可以看到的是,最后8个数字与前8个数字相同,所以我可以砍掉我的LUT一半。如果我看一下数字之间的区别,那么冗余会更多,所以如果我从寄存器中的零开始并想要为它添加值以获得下一位反转的数字,它们将是:+ 0,+ 8,-4 ,+ 8,-10,+ 8,-4,+ 8和后半部分相同。可以看出,我可以有一个只有0和-10的查找表,因为+ 8和-4总是以可预测的方式显示。代码将展开以处理每个循环的4个值:一个是查找表读取,另一个是+ 8,-4,+ 8的直接代码,然后再循环。然后第二个循环可以处理1,5,9,13,3,11,7,15序列。这很好,因为我现在可以将查找表减去另一个因子4.这对于8192大小的FFT来说同样的方式。我现在可以使用4K大小的LUT而不是32K。我可以利用相同的模式,并将我的代码的大小加倍,并再次将LUT切断另一半,无论我想走多远。但是为了完全消除LUT,我又回到了令人望而却步的代码大小。

对于大的FFT大小,我相信这个#2解决方案是迄今为止最绝对的,因为需要完成相对较小百分比的查找表读取,而我目前在网络上找到的每个算法都需要太多串行/依赖计算无法进行矢量化。

问题是,是否存在可以递增数字的算法,因此MSB的行为类似于LSB,依此类推?换句话说(二进制):0000,1000,0100,1100,1010等...我试图想一想,到目前为止,缺少一堆嵌套循环,我似乎无法找到一个一种快速简单算法的方法,该算法是简单地将1添加到数字的LSB的镜像。但似乎应该有办法。

3 个答案:

答案 0 :(得分:1)

另一种需要考虑的方法:采用众所周知的位反转算法 - 通常是几个掩码,移位和OR - 然后使用SSE实现这一点,这样就可以实现8 x 16位反转,价格为1。对于16位,您需要5 * log2(N)= 20条指令,因此总吞吐量将是每位反转2.5条指令。

答案 1 :(得分:1)

这是最简单直接的解决方案(在C中):

void BitReversedIncrement(unsigned *var, int bit)
{
  unsigned c, one = 1u << bit;
  do {
    c = *var & one;
    (*var) ^= one;
    one >>= 1;
  } while (one && c);
}

主要问题是条件分支,它在现代CPU上通常很昂贵。每位有一个条件分支。

您可以通过一次处理几个位来执行反向增量,例如3如果整数是32位:

void BitReversedIncrement2(unsigned *var, int bit)
{
  unsigned r = *var, t = 0;

  while (bit >= 2 && !t)
  {
    unsigned tt = (r >> (bit - 2)) & 7;
    t = (07351624 >> (tt * 3)) & 7;
    r ^= ((tt ^ t) << (bit - 2));
    bit -= 3;
  }

  if (bit >= 0 && !t)
  {
    t = r & ((1 << (bit + 1)) - 1);
    r ^= t;
    t <<= 2 - bit;
    t = (07351624 >> (t * 3)) & 7;
    t >>= 2 - bit;
    r |= t;
  }

  *var = r;
}

这样更好,每3位只有1个条件分支。

如果您的CPU支持64位整数,您可以一次处理4位:

void BitReversedIncrement3(unsigned *var, int bit)
{
  unsigned r = *var, t = 0;

  while (bit >= 3 && !t)
  {
    unsigned tt = (r >> (bit - 3)) & 0xF;
    t = (0xF7B3D591E6A2C48ULL >> (tt * 4)) & 0xF;
    r ^= ((tt ^ t) << (bit - 3));
    bit -= 4;
  }

  if (bit >= 0 && !t)
  {
    t = r & ((1 << (bit + 1)) - 1);
    r ^= t;
    t <<= 3 - bit;
    t = (0xF7B3D591E6A2C48ULL >> (t * 4)) & 0xF;
    t >>= 3 - bit;
    r |= t;
  }

  *var = r;
}

哪个更好。唯一的查找表(07351624或0xF7B3D591E6A2C48)很小,很可能被编码为立即指令操作数。

如果反转“1”的位位置是已知常量,则可以进一步改进代码。只需将while循环展开为嵌套ifs,替换反转的一位位置常量。

答案 2 :(得分:1)

对于较大的FFT,注意高速缓存阻塞(最小化总未覆盖的高速缓存未命中周期)对性能的影响远大于通过索引位反转所采用的循环计数的优化。确保在优化较小效果的同时不要通过更大的循环次数去除更大的效果。对于小FFT,一切都适合缓存,LUT可以是一个很好的解决方案,只要你注意任何负载使用的危险,确保事情是或可以适当流水线。