web-dev-qa-db-ja.com

FFTW対Matlab FFT

私はこれをMatlab Centralに投稿しましたが、応答がなかったので、ここに再投稿すると思いました。

最近、MatlabでforループでFFTを使用する簡単なルーチンを書きました。 FFTが計算を支配します。実験の目的でmexで同じルーチンを作成し、FFTW 3.3ライブラリを呼び出します。非常に大きな配列の場合、matlabルーチンはmexルーチンよりも高速に実行されます(約2倍の速度)。 mexルーチンは知恵を使用し、同じFFT計算を実行します。 matlabがFFTWを使用していることも知っていますが、バージョンが少し最適化されている可能性はありますか? FFTW_EXHAUSTIVEフラグを使用しましたが、大規模な配列の場合、MATLABのフラグよりも約2倍遅くなります。さらに、使用したmatlabが「-singleCompThread」フラグを使用してシングルスレッドであり、使用したmexファイルがデバッグモードではないことを確認しました。これが事実であるか、または私が知らない内部でMATLABが使用している最適化があるかどうかに興味があります。ありがとう。

Mexの部分は次のとおりです。

void class_cg_toeplitz::analysis() {
// This method computes CG iterations using FFTs
    // Check for wisdom
    if(fftw_import_wisdom_from_filename("cd.wis") == 0) {
        mexPrintf("wisdom not loaded.\n");
    } else {
        mexPrintf("wisdom loaded.\n");
    }

    // Set FFTW Plan - use interleaved FFTW
    fftw_plan plan_forward_d_buffer;    
    fftw_plan plan_forward_A_vec;       
    fftw_plan plan_backward_Ad_buffer;
    fftw_complex *A_vec_fft;
    fftw_complex *d_buffer_fft;
    A_vec_fft = fftw_alloc_complex(n);
    d_buffer_fft = fftw_alloc_complex(n);

    // CREATE MASTER PLAN - Do this on an empty vector as creating a plane 
    // with FFTW_MEASURE will erase the contents; 
    // Use d_buffer
    // This is somewhat dangerous because Ad_buffer is a vector; but it does not
    // get resized so &Ad_buffer[0] should work
    plan_forward_d_buffer = fftw_plan_dft_r2c_1d(d_buffer.size(),&d_buffer[0],d_buffer_fft,FFTW_EXHAUSTIVE);
    plan_forward_A_vec = fftw_plan_dft_r2c_1d(A_vec.height,A_vec.value,A_vec_fft,FFTW_WISDOM_ONLY);
    // A_vec_fft.*d_buffer_fft will overwrite d_buffer_fft
    plan_backward_Ad_buffer = fftw_plan_dft_c2r_1d(Ad_buffer.size(),d_buffer_fft,&Ad_buffer[0],FFTW_EXHAUSTIVE);

    // Get A_vec_fft
    fftw_execute(plan_forward_A_vec);

    // Find initial direction - this is the initial residual
    for (int i=0;i<n;i++) {
        d_buffer[i] = b.value[i];
        r_buffer[i] = b.value[i];
    }    

    // Start CG iterations
    norm_ro = norm(r_buffer);
    double fft_reduction = (double)Ad_buffer.size(); // Must divide by size of vector because inverse FFT does not do this
    while (norm(r_buffer)/norm_ro > relativeresidual_cutoff) {        
        // Find Ad - use fft
        fftw_execute(plan_forward_d_buffer);    
        // Get A_vec_fft.*fft(d) - A_vec_fft is only real, but d_buffer_fft
        // has complex elements; Overwrite d_buffer_fft        
        for (int i=0;i<n;i++) {
            d_buffer_fft[i][0] = d_buffer_fft[i][0]*A_vec_fft[i][0]/fft_reduction;
            d_buffer_fft[i][1] = d_buffer_fft[i][1]*A_vec_fft[i][0]/fft_reduction;
        }        
        fftw_execute(plan_backward_Ad_buffer); 

        // Calculate r'*r
        rtr_buffer = 0;
        for (int i=0;i<n;i++) {
            rtr_buffer = rtr_buffer + r_buffer[i]*r_buffer[i];
        }    

        // Calculate alpha
        alpha = 0;
        for (int i=0;i<n;i++) {
            alpha = alpha + d_buffer[i]*Ad_buffer[i];
        }    
        alpha = rtr_buffer/alpha;

        // Calculate new x
        for (int i=0;i<n;i++) {
            x[i] = x[i] + alpha*d_buffer[i];
        }   

        // Calculate new residual
        for (int i=0;i<n;i++) {
            r_buffer[i] = r_buffer[i] - alpha*Ad_buffer[i];
        }   

        // Calculate beta
        beta = 0;
        for (int i=0;i<n;i++) {
            beta = beta + r_buffer[i]*r_buffer[i];
        }  
        beta = beta/rtr_buffer;

        // Calculate new direction vector
        for (int i=0;i<n;i++) {
            d_buffer[i] = r_buffer[i] + beta*d_buffer[i];
        }  

        *total_counter = *total_counter+1;
        if(*total_counter >= iteration_cutoff) {
            // Set total_counter to -1, this indicates failure
            *total_counter = -1;
            break;
        }
    }

    // Store Wisdom
    fftw_export_wisdom_to_filename("cd.wis");

    // Free fft alloc'd memory and plans
    fftw_destroy_plan(plan_forward_d_buffer);
    fftw_destroy_plan(plan_forward_A_vec);
    fftw_destroy_plan(plan_backward_Ad_buffer);
    fftw_free(A_vec_fft);
    fftw_free(d_buffer_fft);
};

これがMATLABの部分です:

% Take FFT of A_vec.
A_vec_fft = fft(A_vec); % Take fft once

% Find initial direction - this is the initial residual 
x = zeros(n,1); % search direction
r = zeros(n,1); % residual
d = zeros(n+(n-2),1); % search direction; pad to allow FFT
for i = 1:n
    d(i) = b(i); 
    r(i) = b(i); 
end

% Enter CG iterations
total_counter = 0;
rtr_buffer = 0;
alpha = 0;
beta = 0;
Ad_buffer = zeros(n+(n-2),1); % This holds the product of A*d - calculate this once per iteration and using FFT; only 1:n is used
norm_ro = norm(r);

while(norm(r)/norm_ro > 10^-6)
    % Find Ad - use fft
    Ad_buffer = ifft(A_vec_fft.*fft(d)); 

    % Calculate rtr_buffer
    rtr_buffer = r'*r;

    % Calculate alpha    
    alpha = rtr_buffer/(d(1:n)'*Ad_buffer(1:n));

    % Calculate new x
    x = x + alpha*d(1:n);

    % Calculate new residual
    r = r - alpha*Ad_buffer(1:n);

    % Calculate beta
    beta = r'*r/(rtr_buffer);

    % Calculate new direction vector
    d(1:n) = r + beta*d(1:n);      

    % Update counter
    total_counter = total_counter+1; 
end

時間に関しては、N = 50000およびb = 1:nの場合、mexでは約10.5秒、Matlabでは4.4秒かかります。 R2011bを使用しています。ありがとう

36
Justin

MATLAB FFT実装の詳細がわからないので、明確な答えではなくいくつかの観察:

  • あなたが持っているコードに基づいて、私は速度の違いについて2つの説明を見ることができます:
    • 速度の違いは、FFTの最適化レベルの違いによって説明されます
    • mATLABのwhileループの実行回数が大幅に少ない

すでに2番目の問題を検討しており、反復回数は同等であると想定します。 (そうでない場合、これはいくつかの精度の問題である可能性が最も高く、さらに調査する価値があります。)

ここで、FFT速度の比較について:

  • はい、理論はFFTWが他の高レベルのFFT実装よりも高速であるということですが、これは、リンゴをリンゴと比較する場合にのみ関連します。ここでは、実装をさらに下のレベルで、アセンブリレベルで比較しています。アルゴリズムの選択、ただし特定のプロセッサーに対するさまざまなスキルを持つソフトウェア開発者による実際の最適化
  • 私はアセンブリで最適化されたFFTを年間で多くのプロセッサで最適化またはレビューしました(私はベンチマーク業界にいました)。優れたアルゴリズムは物語の一部にすぎません。あなたがコーディングしているアーキテクチャに非常に特有の考慮事項(レイテンシのアカウンティング、命令のスケジューリング、レジスタ使用の最適化、メモリ内のデータの配置、分岐成立/非成立のレイテンシのアカウンティングなど)があり、違いがあります。アルゴリズムの選択と同じくらい重要です。
  • N = 500000の場合は、大容量メモリバッファについても話します。コードを実行するプラットフォームにかなり固有のものになる可能性のある、より多くの最適化のためのもう1つの扉:キャッシュミスを回避するためにどれほどうまく管理できるかは、アルゴリズムは、データの流れ方や、ソフトウェア開発者がデータをメモリに効率的に出し入れするためにどのような最適化を行ったかによって異なります。
  • MATLAB FFT実装の詳細はわかりませんが、DSPエンジニアの軍隊が非常に多くの設計の鍵であるため、最適化に注力している(そして現在もそうである)と確信しています。これは、MATLABがはるかに高速なFFTを生成する開発者の適切な組み合わせを持っていたことを非常によく意味します。
14
Lolo

これは、低レベルのアーキテクチャ固有の最適化により、従来のパフォーマンスが向上しています。

MatlabはIntelのFFTを使用します [〜#〜] mkl [〜#〜] (Math Kernel Library)バイナリ(mkl.dll)。これらは、インテルによってインテルプロセッサー用に(アセンブリーレベルで)最適化されたルーチンです。 AMDでも、Niceパフォーマンスが向上するようです。

FFTWは、最適化されていない通常のCライブラリのようです。したがって、MKLを使用するとパフォーマンスが向上します。

8

MathWorks Webサイト[1]で次のコメントを見つけました。

2のべき乗に注意:2のべき乗であるFFT次元が2 ^ 14から2 ^ 22の場合、MATLABソフトウェアは内部データベースに事前に読み込まれた特別な情報を使用してFFT計算を最適化します。コマンドfftw( 'wisdom'、[])を使用してデータベースをクリアしない限り、FTTの次元が2の累乗である場合、チューニングは実行されません。

これは2の累乗に関係しますが、特定の(大きな)配列サイズにFFTWを使用する場合、MATLABは独自の「特別な知識」を使用することを示唆している場合があります。考慮してください:2 ^ 16 = 65536。

[1] R2013bドキュメントは http://www.mathworks.de/de/help/matlab/ref/fftw.html から入手できます(2013年10月29日にアクセス)

3
ObeyTheDiode

編集:この回答に対する@wakjahの回答は正確です。FFTWは、Guruインターフェースを介して実メモリと虚メモリの分割をサポートしています。したがって、ハッキングについての私の主張は正確ではありませんが、FFTWのGuruインターフェースが使用されていない場合には非常によく当てはまります。これはデフォルトのケースですので、注意してください。

まず、一年遅れてすみません。表示される速度の向上がMKLまたはその他の最適化によるものであるとは思いません。 FFTWとMatlabの間には根本的にかなり異なる点があり、それがメモリに複雑なデータが格納される方法です。

Matlabでは、複素数ベクトルXの実数部と虚数部は別々の配列Xre [i]とXim [i]です(メモリ内で線形、これらのいずれかを別々に操作する場合に効率的)。

FFTWでは、実数部と虚数部はデフォルトでdouble [2]としてインターレースされます。つまり、X [i] [0]は実数部であり、X [i] [1]は虚数部です。

したがって、mexファイルでFFTWライブラリを使用するには、Matlab配列を直接使用することはできませんが、最初に新しいメモリを割り当ててから、Matlabからの入力をFFTW形式にパックし、次にFFTWからの出力をMatlab形式にアンパックする必要があります。つまり.

_X = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * N);
Y = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * N);
_

その後

_for (size_t i=0; i<N; ++i) {
    X[i][0] = Xre[i];
    X[i][1] = Xim[i];
}
_

その後

_for (size_t i=0; i<N; ++i) {
    Yre[i] = Y[i][0];
    Yim[i] = Y[i][1];
}
_

したがって、これには2倍のメモリ割り当て+ 4倍の読み取り+ 4倍の書き込み-サイズNのすべてが必要です。これは、大規模な問題では速度的に非常に時間がかかります。

MathworksがFFTW3コードをハッキングして、Matlab形式で入力ベクトルを直接読み取れるようにして、上記のすべてを回避しているのではないかと思います。

このシナリオでは、Xを割り当て、YにXを使用してFFTWをインプレースで実行できます(fftw_plan_*(N, X, X, ...)ではなくfftw_plan_*(N, X, Y, ...)として)。これは、Yreにコピーされ、 Yim Matlabベクトル。ただし、アプリケーションがXとYを別々にしておく必要がある、またはメリットがない場合。

[〜#〜] edit [〜#〜]:Matlabのfft2()およびfftw3ライブラリに基づいた私のコードを実行しているときにリアルタイムでメモリ消費量を確認すると、Matlabは割り当てのみ1つの追加の複雑な配列(出力)ですが、私のコードには2つの配列(_*fftw_complex_バッファーとMatlab出力)が必要です。 Matlabの実数配列と虚数配列はメモリ内で連続していないため、Matlab形式とfftw形式間のインプレース変換は不可能です。これは、Mathworksがfftw3ライブラリをハッキングして、Matlab形式を使用してデータを読み書きしたことを示唆しています。

複数の呼び出しのもう1つの最適化は、永続的に割り当てることです(mexMakeMemoryPersistent()を使用)。 Matlabの実装でもこれが可能かどうかはわかりません。

乾杯。

pS補足として、Matlabの複雑なデータストレージ形式は、実数ベクトルまたは虚数ベクトルを個別に操作する場合により効率的です。 FFTWのフォーマットでは、++ 2のメモリ読み取りを行う必要があります。

3
Normadize