2007/08/24(金)実数FFT/IFFT関数
長年理解するのを拒否していたFFTとここ何週間か格闘しています。楽々とアルゴリズムを導出・実装できる人はいいのでしょうが、FFTのようなアルゴリズムをきちんと理解して実装するのは思うほど容易ではありません。既に導出されたアルゴリズム(や雛形サンプル)を何も考えず実装すれば簡単ですが、そういうことが生理的にできない場合、改良とかを考え出して簡単には実装できなくなるんですよね。
前置きはともかく、今日やっと実数専用のFFT/IFFTが作成できました。実数専用にすることで理論的には通常のFFTの半分の時間で処理できます。
見ての通り、実数FFTの対称性を利用したものすごく面倒くさく複雑な作りになっているんですが、有名なFFTの概略と設計法のソースと速度比較をしたら倍近く遅い……。64K点、double型、VIA C3 500MHzで動作させて、自作が100ms、リンク先のソースが60ms。
いかに細かい最適化をしても実装アルゴリズムの時点で差があると非常に大きいですね……。あとでアルゴリズムの差を検証予定。
ソース解読メモ
大浦氏のFFTソース(fft4g.c)解読メモ。つまりリバースエンジニアリングです。
サブルーチンの機能
cdft() | 複素数FFT/IFFT。データサイズは N/2 |
rdft() | 実数FFT/IFFT。内部的に複素FFTを呼び出し |
makewt() | sin/cosテーブルを w[] に格納(ただし格納位置はビット反転) |
makect() | cosテーブルを w[nw~] に格納(ただし格納位置はビット反転) |
bitrv2() | ビット反転を実行 |
bitrv2conj() | ビット反転しつつ、読込データの複素共役を取る |
cftfsub() | 複素IFFT(f=forward, 回転子が正であるという意味) |
cftbsub() | 複素FFT(b=backward, 回転子が負であるという意味) |
rftfsub() | 実数IFFT |
rftbsub() | 実数FFT |
IFFTをFFTで代用する方法
なんだけども、複素共役を考えると
つまり前処理と後処理としてデータの複素共役を取ってあげれば、同じFFTルーチンを使い回すことができる(効率を考えて、初段部分と終段部分だけ個別特別に実装すれば、中段部分は使い回せる)。
その他メモ
- 一般的なFFT(DFT)と比べ虚数部の符号が逆になっている。*1
周波数間引きFFTである
やっとこのプログラムの要が理解出来ました。
先にデータをスクランブルして(並べ替えて)いるため「時間間引きFFT」に見えますが、実際には周波数間引きFFTです。sin/cosテーブルもわざわざスクランブル位置に格納しています。
こうすることでFFT実行時のデータアクセスをシーケンシャルに行え*2、それが功を奏して実行速度が飛躍的に速くなっています。またsin/cosテーブル、偶数番地に cos、奇数番地に sin を格納し、全体としても π/2 しか用意しないことで、データアクセス量を減らしキャッシュが効きやすくなっています。
256点の複素FFTを実行させたときの、cos/sinテーブル参照位置およびデータ参照位置は次のようになります。
call cftmdl(512, 8) k1=1 (0.923880,0.382683), k2=2 (0.707107,0.707107) 32 36 40 44 33 37 41 45 34 38 42 46 35 39 43 47 k1=2 (0.980785,0.195090), k2=4 (0.923880,0.382683) 64 68 72 76 65 69 73 77 66 70 74 78 67 71 75 79 k1=3 (0.831470,0.555570), k2=6 (0.382683,0.923880) 96 100 104 108 97 101 105 109 98 102 106 110 99 103 107 111 k1=4 (0.995185,0.098017), k2=8 (0.980785,0.195090) 128 132 136 140 129 133 137 141 130 134 138 142 131 135 139 143 k1=5 (0.881921,0.471397), k2=10 (0.555570,0.831470) 160 164 168 172 161 165 169 173 162 166 170 174 163 167 171 175 k1=6 (0.956940,0.290285), k2=12 (0.831470,0.555570) 192 196 200 204 193 197 201 205 194 198 202 206 195 199 203 207 k1=7 (0.773010,0.634393), k2=14 (0.195090,0.980785) 224 228 232 236 225 229 233 237 226 230 234 238 227 231 235 239 call cftmdl(512, 32) k1=1 (0.923880,0.382683), k2=2 (0.707107,0.707107) 128 144 160 176 129 145 161 177 (中略) 142 158 174 190 143 159 175 191
ほんと、この実装はすごいなぁ。