So-net無料ブログ作成

なんちゃってクラスクラスタの実装 [任意点数のFFT]

先週の金曜日の夜に仙台から横浜に帰った。せっかくの休みなのに今日家族は、皆それぞれ出かけて僕は留守番。丸一日ごろごろしていた。ということで話の続きを更新。

「なんちゃってクラスクラスタ」の実装

NSStringならいたるところで使われるので実装はパフォーマンスに注意しなければいけないが、アプリを実行するときにインスタンス生成が頻繁には行われないとか、極端な場合ひとつしか作られないようなオブジェクトをクラスクラスタにする場合には、NSStringほど厳密に考える必要はない。

つまりallocで抽象クラスのインスタンスを実際に作ってしまって、init...で具体的なサブクラスのインスタンスを作った後、抽象クラスのインスタンスは破棄してしまえばいい。一回余分にインスタンス生成のコストがかかることになるけど、それが無視できるほどしか呼ばれないならそれで十分である。C++の抽象クラスではこういった手は使えない。

これを僕は「なんちゃってクラスクラスタ」と呼んで、ときどきやっている。これまでもLifeGame光学薄膜計算の実装でやったばかりである。

これだとシングルトン生成の細かな面倒はなくなるし、本物のクラスクラスタと同じ動作が可能になって使い分けのコードを一カ所にまとめることができてすっきりする。ただし、本物のクラスクラスタと同じようなallocとinit...を1行に書く、と言った注意も必要になる。

抽象クラスの実装を示すと、まずインターフェイスは
@interface ASDFT1D : NSObject
- (id)initWithNumberOfPoints:(unsigned int)np andDirection:(int)dir;
- (void)performForComplexArray:(float complex *)dat withStride:(int)stride;
@end
ASDFT1Dが1次元のFFTのクラスクラスタの抽象クラスである。インスタンス変数は持たない。

init...では点数とFFTの向きを指定して、perform...メソッドで複素数の1次元配列に対してFFTを実行する。結果は上書きすることにする。

この実装は
@implementation ASDFT1D
- (id)initWithNumberOfPoints:(unsigned int)np andDirection:(int)dir
{
    id  temp;
    self = [super init];
    if (isPowerOf2(np))
        temp = [[ASRadix2FFT1D alloc] initWithLog2OfPoints:log2OfN(np)
                                              andDirection:dir];
    else if ([[self class] chirpZDesirableFor:np])
        temp = [[ASChirpzFFT1D alloc] initWithNumberOfPoints:np
                                                andDirection:dir];
    else if (primeQ(np))
        temp = [[ASIntactDFT1D alloc] initWithNumberOfPoints:np
                                                andDirection:dir];
    else
        temp = [[ASMixedRadixFFT1D alloc] initWithNumberOfPoints:np
                                                    andDirection:dir];
    [self release];    // (1)
    return temp;
}

- (void)performForComplexArray:(float complex *)dat withStride:(int)stride
{
    //  do nothing
}
@end
である。点数が2のベキか、素数か、とかを判定してそれに従って具体的なインスタンスを生成して、(1)で自分自身を破棄して具体的なインスタンスの方を返している。

ここではサブクラスの名前を直接指定しているけど、もう少し賢い方法もあるかもしれない。

ここで作られる具体的なインスタンスは抽象クラスのサブクラスにするのが簡単だけど抽象クラスと同じメソッドが実装されていればなんでもいい。そのメソッドをプロトコルにしてそれをアドプトすることにしてもいいし、極端な場合、全然違うクラスでたまたま同じメソッドが実装されているだけでもいい。こういういいかげんさはObjective-C特有で面白い。

ASDFT1Dは抽象クラスなのでperform...メソッドは何もしない。具体的なサブクラスでこのメソッドを上書きして実際に計算できるようにしないといけないのは当然で、C++では仮想関数にするのが普通である。

なんちゃってクラスクラスタを呼ぶ

具体的な個々の実装に入る前に、これを呼ぶ2次元FFTの方を書いてみる。 2次元版のインターフェイスは
#import "ASDFT1D.h"
@interface ASFFT2D : NSObject {
    unsigned int    rows;
    unsigned int    cols;
    ASDFT1D         *rowfft;
    ASDFT1D         *colfft;
}
- (id)initWithLengthOfRows:(unsigned int)rowlength
                   columns:(unsigned int)columnlength
              andDirection:(int)dir;
- (void)performForPackedComplexArray:(float complex *)dat;
@end
としよう。縦と横のサイズを指定して初期化するメソッドと、複素データの配列を渡してFFTを実行するメソッドを持っている。計算対象の複素データはrow側が先に並んでパディングのない複素数の1次元配列となっているとみなしている。

この実装は
@implementation ASFFT2D
- (id)initWithLengthOfRows:(unsigned int)rowlength
                   columns:(unsigned int)columnlength
              andDirection:(int)dir
{
    self = [super init];
    rows = rowlength;
    cols = columnlength;
    rowfft = [[ASDFT1D alloc] initWithNumberOfPoints:rows andDirection:dir];
    colfft = [[ASDFT1D alloc] initWithNumberOfPoints:cols andDirection:dir];
    return self;
}
- (void)performForPackedComplexArray:(float complex *)dat
{
    int r, c;
    for (r = 0 ; r < rows ; r ++)
        [colfft performForComplexArray:dat + r withStride:rows];
    for (c = 0 ; c < cols ; c ++)
        [rowfft performForComplexArray:dat + c * rows withStride:1];
}
@end
と書くことができる。単に縦横用の1次元DFTのインスタンスを生成して、それぞれFFTを実行するだけである。

インスタンスを作った時点で縦横それぞれのサイズから適当なサブクラスがインスタンス化されることになって、アルゴリズムの詳細にはまったくタッチしない。

実際にはデータが正方形だった場合とか、さらに2次元データそのものとしてFFTのループをほぐせば高速化することができるが今回の趣旨としてそこまでやらずに、簡単なままですます。

nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0