2016-11-16 13 views
7

x(n)K * Nであり、最初のN要素のみがゼロと異なると仮定します。私はN << Kと言って、例えば、N = 10K = 100000と言っています。私はそのようなシーケンスのFFTWでFFTを計算したいと思います。これは、長さがNで、ゼロパディングがK * Nになっていることに相当します。 NKは「大」である可能性があるため、大きなゼロ詰めがあります。明示的なゼロパディングを避けるために計算時間を節約できるかどうか検討しています。大量のゼロパディングを避けるためにFFTWプルーニングを高速化する

場合

K = 2は、私たちはケースK = 2を考慮することから始めましょう。 kがDFTのような値を算出することができることを意味しても、すなわちk = 2 * m、次いで

enter image description here

ある場合この場合、x(n)のDFTは

enter image description here

のように書くことができます。長さがNであり、K * NではないFFTである。

k場合は、DFTのような値が再び長NのシーケンスのFFTにより算出されずK * Nすることができることを意味する、すなわちk = 2 * m + 1、次いで

enter image description here

奇数です。

結論として、長さ2 * Nの単一のFFTを2の長さのNのFFTと交換することができます。

この場合、任意のK

の場合、我々はk = m * K + tを書くことに

enter image description here

を持って、我々は結論で、だから、

enter image description here

を持っています、私はexchaすることができます長さがK * Nの単一のFFTをKの長さのFFTに設定します。長さはNです。 FFTWにはfftw_plan_many_dftがあるので、1つのFFTの場合に対していくらかの利益を得ることができます。それを確認する

、私が開発したアプローチは、3つのステップから構成され、次のコード

#include <stdio.h> 
#include <stdlib.h>  /* srand, rand */ 
#include <time.h>  /* time */ 
#include <math.h> 
#include <fstream> 

#include <fftw3.h> 

#include "TimingCPU.h" 

#define PI_d   3.141592653589793 

void main() { 

    const int N = 10; 
    const int K = 100000; 

    fftw_plan plan_zp; 

    fftw_complex *h_x = (fftw_complex *)malloc(N  * sizeof(fftw_complex)); 
    fftw_complex *h_xzp = (fftw_complex *)calloc(N * K, sizeof(fftw_complex)); 
    fftw_complex *h_xpruning = (fftw_complex *)malloc(N * K * sizeof(fftw_complex)); 
    fftw_complex *h_xhatpruning = (fftw_complex *)malloc(N * K * sizeof(fftw_complex)); 
    fftw_complex *h_xhatpruning_temp = (fftw_complex *)malloc(N * K * sizeof(fftw_complex)); 
    fftw_complex *h_xhat = (fftw_complex *)malloc(N * K * sizeof(fftw_complex)); 

    // --- Random number generation of the data sequence 
    srand(time(NULL)); 
    for (int k = 0; k < N; k++) { 
     h_x[k][0] = (double)rand()/(double)RAND_MAX; 
     h_x[k][1] = (double)rand()/(double)RAND_MAX; 
    } 

    memcpy(h_xzp, h_x, N * sizeof(fftw_complex)); 

    plan_zp = fftw_plan_dft_1d(N * K, h_xzp, h_xhat, FFTW_FORWARD, FFTW_ESTIMATE); 
    fftw_plan plan_pruning = fftw_plan_many_dft(1, &N, K, h_xpruning, NULL, 1, N, h_xhatpruning_temp, NULL, 1, N, FFTW_FORWARD, FFTW_ESTIMATE); 

    TimingCPU timerCPU; 
    timerCPU.StartCounter(); 
    fftw_execute(plan_zp); 
    printf("Stadard %f\n", timerCPU.GetCounter()); 

    timerCPU.StartCounter(); 
    double factor = -2. * PI_d/(K * N); 
    for (int k = 0; k < K; k++) { 
     double arg1 = factor * k; 
     for (int n = 0; n < N; n++) { 
      double arg = arg1 * n; 
      double cosarg = cos(arg); 
      double sinarg = sin(arg); 
      h_xpruning[k * N + n][0] = h_x[n][0] * cosarg - h_x[n][1] * sinarg; 
      h_xpruning[k * N + n][1] = h_x[n][0] * sinarg + h_x[n][1] * cosarg; 
     } 
    } 
    printf("Optimized first step %f\n", timerCPU.GetCounter()); 

    timerCPU.StartCounter(); 
    fftw_execute(plan_pruning); 
    printf("Optimized second step %f\n", timerCPU.GetCounter()); 
    timerCPU.StartCounter(); 
    for (int k = 0; k < K; k++) { 
     for (int p = 0; p < N; p++) { 
      h_xhatpruning[p * K + k][0] = h_xhatpruning_temp[p + k * N][0]; 
      h_xhatpruning[p * K + k][1] = h_xhatpruning_temp[p + k * N][1]; 
     } 
    } 
    printf("Optimized third step %f\n", timerCPU.GetCounter()); 

    double rmserror = 0., norm = 0.; 
    for (int n = 0; n < N; n++) { 
     rmserror = rmserror + (h_xhatpruning[n][0] - h_xhat[n][0]) * (h_xhatpruning[n][0] - h_xhat[n][0]) + (h_xhatpruning[n][1] - h_xhat[n][1]) * (h_xhatpruning[n][1] - h_xhat[n][1]); 
     norm = norm + h_xhat[n][0] * h_xhat[n][0] + h_xhat[n][1] * h_xhat[n][1]; 
    } 
    printf("rmserror %f\n", 100. * sqrt(rmserror/norm)); 

    fftw_destroy_plan(plan_zp); 

} 

を設定している:複素指数を「ひねり」により入力シーケンスを乗算

  1. fftw_manyを実行する。
  2. 結果を再編成する。

入力ポイントがK * Nの場合、fftw_manyは単一のFFTWよりも高速です。しかしながら、ステップ1及びステップ3は、このような利得を完全に破壊する。私はステップ#1と#3がステップ#2より計算上非常に軽いと期待します。

私の質問は以下のとおりです。

  1. どのようにそれは#1と#3ステップということも可能であるので、計算ステップ#2よりも厳しいですか?
  2. ステップ#1と#3を改善して、「標準」のアプローチに対して純利益を得るにはどうすればよいですか?

ありがとうございました。

EDIT

私は、Visual Studio 2013での作業とリリースモードでコンパイルしています。より高速に実行する

+1

あなたは、例えば、有効な最適化を使用してテスト・コードをコンパイルしたのです'gcc -O3 ...'? –

+0

@PaulRリリースモードでVisual Studio 2013を使用してコンパイルしています。私はそれに応じて投稿を編集しました。 – JackOLantern

答えて

2

for (int p = 0; p < N; p++) { 
    for (int k = 0; k < K; k++) { 
     h_xhatpruning[p * K + k][0] = h_xhatpruning_temp[p + k * N][0]; 
     h_xhatpruning[p * K + k][1] = h_xhatpruning_temp[p + k * N][1]; 
    } 
} 

それは店のアドレスはロードアドレスより連続して持つことが一般的により有益だから。

いずれにしても、キャッシュ非友好的なアクセスパターンがあります。これを改善するためにブロックを使って作業することもできます(例: Nが4の倍数であると仮定すると:

for (int p = 0; p < N; p += 4) { 
    for (int k = 0; k < K; k++) { 
     for (int p0 = 0; p0 < 4; p0++) { 
      h_xhatpruning[(p + p0) * K + k][0] = h_xhatpruning_temp[(p + p0) + k * N][0]; 
      h_xhatpruning[(p + p0) * K + k][1] = h_xhatpruning_temp[(p + p0) + k * N][1]; 
     } 
    } 
} 

これは、キャッシュラインのチャーンを多少減らすのに役立ちます。そうであれば、「スイートスポット」があるかどうかを確認するために、4以外のブロックサイズを試してみてください。

+1

あなたの最初の提案は、第3ステップのタイミングを大幅に改善します。ループの展開は、結果をさらに改善するのには役立ちません。あなたの答えに多くの感謝。 – JackOLantern

+0

あなたが示唆した 'for'ループ交換が成功するためには、マルチスレッド化は結果を悪化させるだけです。 – JackOLantern

+1

最後に、自分のコードを改善し、あなたの提案にも従っています。私は以下の完全な答えを提供しました。ご関心をお寄せいただきありがとうございます。 – JackOLantern

5

いくつかのオプション:

  1. あなたが唯一のシングルスレッドで実行し、利用可能な複数のコアを持っている場合の実行は、マルチスレッド。

  2. FFTW知恵のファイルを作成して保存します。特にFFTのディメンションが事前にわかっている場合は、作成してください。 FFTW_EXHAUSTIVEを使用し、FFTWの知恵を毎回再計算するのではなくリロードします。結果が一貫するようにしたい場合は、これも重要です。 FFTWは、異なる計算された知恵とは異なるFFTを計算し、知恵の結果は必ずしも常に同じになるとは限らないため、両方の入力データが同じである場合、プロセスの実行が異なる場合があります。

  3. x86をお使いの場合は、64ビットを実行してください。 FFTWアルゴリズムは非常にレジスタ集約型であり、64ビットモードで動作しているx86 CPUは、32ビットモードで動作している場合よりも多くの汎用レジスタを使用できます。

  4. FFTWアルゴリズムは非常に集中的に登録されているので、私は良い成功プリフェッチの使用を防止し、関数の暗黙のインライン化を防ぐコンパイラオプションでFFTWをコンパイルすることにより、FFTWのパフォーマンスを向上させることがありました。あなたは、ループの順序を切り替えしようとする場合があります第三段階については

+1

ありがとうございます。しかし概念的には、私は '' fftw''ではなく '' for''ループを高速化することに主に関心を持っています。私は私の記事を編集して、2つの 'for'ループ用のOpenMPソリューションを追加しましたが、私は不公平な比較をしているように感じています:2つのFFTWは連続しており、' for'ループはマルチスレッドです。 – JackOLantern

+0

私はついにコードを改善することができました。私は完全な答えを投稿しました。ご関心をお寄せいただきありがとうございます。 – JackOLantern

1

また、Paul Rのコメントに続いて、コードを改善しました。さて、代替のアプローチは、標準(ゼロ詰め)より速いです。以下は完全なC++スクリプトです。ステップ#1と#3では、私は他の試したソリューションについてコメントしました。これは、コメントのないものほど遅くても高速であっても表示されています。私は、ネストしていないforループに特権を与えています。これは、将来のCUDA並列化の観点からもそうです。私はまだFFTWにマルチスレッドを使用していません。

#include <stdio.h> 
#include <stdlib.h>  /* srand, rand */ 
#include <time.h>  /* time */ 
#include <math.h> 
#include <fstream> 

#include <omp.h> 

#include <fftw3.h> 

#include "TimingCPU.h" 

#define PI_d   3.141592653589793 

/******************/ 
/* STEP #1 ON CPU */ 
/******************/ 
void step1CPU(fftw_complex * __restrict h_xpruning, const fftw_complex * __restrict h_x, const int N, const int K) { 

// double factor = -2. * PI_d/(K * N); 
// int n; 
// omp_set_nested(1); 
//#pragma omp parallel for private(n) num_threads(4) 
// for (int k = 0; k < K; k++) { 
//  double arg1 = factor * k; 
//#pragma omp parallel for num_threads(4) 
//  for (n = 0; n < N; n++) { 
//   double arg = arg1 * n; 
//   double cosarg = cos(arg); 
//   double sinarg = sin(arg); 
//   h_xpruning[k * N + n][0] = h_x[n][0] * cosarg - h_x[n][1] * sinarg; 
//   h_xpruning[k * N + n][1] = h_x[n][0] * sinarg + h_x[n][1] * cosarg; 
//  } 
// } 

    //double factor = -2. * PI_d/(K * N); 
    //int k; 
    //omp_set_nested(1); 
    //#pragma omp parallel for private(k) num_threads(4) 
    //for (int n = 0; n < N; n++) { 
    // double arg1 = factor * n; 
    // #pragma omp parallel for num_threads(4) 
    // for (k = 0; k < K; k++) { 
    //  double arg = arg1 * k; 
    //  double cosarg = cos(arg); 
    //  double sinarg = sin(arg); 
    //  h_xpruning[k * N + n][0] = h_x[n][0] * cosarg - h_x[n][1] * sinarg; 
    //  h_xpruning[k * N + n][1] = h_x[n][0] * sinarg + h_x[n][1] * cosarg; 
    // } 
    //} 

    //double factor = -2. * PI_d/(K * N); 
    //for (int k = 0; k < K; k++) { 
    // double arg1 = factor * k; 
    // for (int n = 0; n < N; n++) { 
    //  double arg = arg1 * n; 
    //  double cosarg = cos(arg); 
    //  double sinarg = sin(arg); 
    //  h_xpruning[k * N + n][0] = h_x[n][0] * cosarg - h_x[n][1] * sinarg; 
    //  h_xpruning[k * N + n][1] = h_x[n][0] * sinarg + h_x[n][1] * cosarg; 
    // } 
    //} 

    //double factor = -2. * PI_d/(K * N); 
    //for (int n = 0; n < N; n++) { 
    // double arg1 = factor * n; 
    // for (int k = 0; k < K; k++) { 
    //  double arg = arg1 * k; 
    //  double cosarg = cos(arg); 
    //  double sinarg = sin(arg); 
    //  h_xpruning[k * N + n][0] = h_x[n][0] * cosarg - h_x[n][1] * sinarg; 
    //  h_xpruning[k * N + n][1] = h_x[n][0] * sinarg + h_x[n][1] * cosarg; 
    // } 
    //} 

    double factor = -2. * PI_d/(K * N); 
    #pragma omp parallel for num_threads(8) 
    for (int n = 0; n < K * N; n++) { 
     int row = n/N; 
     int col = n % N; 
     double arg = factor * row * col; 
     double cosarg = cos(arg); 
     double sinarg = sin(arg); 
     h_xpruning[n][0] = h_x[col][0] * cosarg - h_x[col][1] * sinarg; 
     h_xpruning[n][1] = h_x[col][0] * sinarg + h_x[col][1] * cosarg; 
    } 
} 

/******************/ 
/* STEP #3 ON CPU */ 
/******************/ 
void step3CPU(fftw_complex * __restrict h_xhatpruning, const fftw_complex * __restrict h_xhatpruning_temp, const int N, const int K) { 

    //int k; 
    //omp_set_nested(1); 
    //#pragma omp parallel for private(k) num_threads(4) 
    //for (int p = 0; p < N; p++) { 
    // #pragma omp parallel for num_threads(4) 
    // for (k = 0; k < K; k++) { 
    //  h_xhatpruning[p * K + k][0] = h_xhatpruning_temp[p + k * N][0]; 
    //  h_xhatpruning[p * K + k][1] = h_xhatpruning_temp[p + k * N][1]; 
    // } 
    //} 

    //int p; 
    //omp_set_nested(1); 
    //#pragma omp parallel for private(p) num_threads(4) 
    //for (int k = 0; k < K; k++) { 
    // #pragma omp parallel for num_threads(4) 
    // for (p = 0; p < N; p++) { 
    //  h_xhatpruning[p * K + k][0] = h_xhatpruning_temp[p + k * N][0]; 
    //  h_xhatpruning[p * K + k][1] = h_xhatpruning_temp[p + k * N][1]; 
    // } 
    //} 

    //for (int p = 0; p < N; p++) { 
    // for (int k = 0; k < K; k++) { 
    //  h_xhatpruning[p * K + k][0] = h_xhatpruning_temp[p + k * N][0]; 
    //  h_xhatpruning[p * K + k][1] = h_xhatpruning_temp[p + k * N][1]; 
    // } 
    //} 

    //for (int k = 0; k < K; k++) { 
    // for (int p = 0; p < N; p++) { 
    //  h_xhatpruning[p * K + k][0] = h_xhatpruning_temp[p + k * N][0]; 
    //  h_xhatpruning[p * K + k][1] = h_xhatpruning_temp[p + k * N][1]; 
    // } 
    //} 

    #pragma omp parallel for num_threads(8) 
    for (int p = 0; p < K * N; p++) { 
     int col = p % N; 
     int row = p/K; 
     h_xhatpruning[col * K + row][0] = h_xhatpruning_temp[col + row * N][0]; 
     h_xhatpruning[col * K + row][1] = h_xhatpruning_temp[col + row * N][1]; 
    } 

    //for (int p = 0; p < N; p += 2) { 
    // for (int k = 0; k < K; k++) { 
    //  for (int p0 = 0; p0 < 2; p0++) { 
    //   h_xhatpruning[(p + p0) * K + k][0] = h_xhatpruning_temp[(p + p0) + k * N][0]; 
    //   h_xhatpruning[(p + p0) * K + k][1] = h_xhatpruning_temp[(p + p0) + k * N][1]; 
    //  } 
    // } 
    //} 

} 

/********/ 
/* MAIN */ 
/********/ 
void main() { 

    int N = 10; 
    int K = 100000; 

    // --- CPU memory allocations 
    fftw_complex *h_x = (fftw_complex *)malloc(N  * sizeof(fftw_complex)); 
    fftw_complex *h_xzp = (fftw_complex *)calloc(N * K, sizeof(fftw_complex)); 
    fftw_complex *h_xpruning = (fftw_complex *)malloc(N * K * sizeof(fftw_complex)); 
    fftw_complex *h_xhatpruning = (fftw_complex *)malloc(N * K * sizeof(fftw_complex)); 
    fftw_complex *h_xhatpruning_temp = (fftw_complex *)malloc(N * K * sizeof(fftw_complex)); 
    fftw_complex *h_xhat = (fftw_complex *)malloc(N * K * sizeof(fftw_complex)); 
    //double2  *h_xhatGPU = (double2 *)malloc(N * K * sizeof(double2)); 


    // --- Random number generation of the data sequence on the CPU - moving the data from CPU to GPU 
    srand(time(NULL)); 
    for (int k = 0; k < N; k++) { 
     h_x[k][0] = (double)rand()/(double)RAND_MAX; 
     h_x[k][1] = (double)rand()/(double)RAND_MAX; 
    } 
    //gpuErrchk(cudaMemcpy(d_x, h_x, N * sizeof(double2), cudaMemcpyHostToDevice)); 

    memcpy(h_xzp, h_x, N * sizeof(fftw_complex)); 

    // --- FFTW and cuFFT plans 
    fftw_plan h_plan_zp  = fftw_plan_dft_1d(N * K, h_xzp, h_xhat, FFTW_FORWARD, FFTW_ESTIMATE); 
    fftw_plan h_plan_pruning = fftw_plan_many_dft(1, &N, K, h_xpruning, NULL, 1, N, h_xhatpruning_temp, NULL, 1, N, FFTW_FORWARD, FFTW_ESTIMATE); 

    double totalTimeCPU = 0., totalTimeGPU = 0.; 
    double partialTimeCPU, partialTimeGPU; 

    /****************************/ 
    /* STANDARD APPROACH ON CPU */ 
    /****************************/ 
    printf("Number of processors available = %i\n", omp_get_num_procs()); 
    printf("Number of threads    = %i\n", omp_get_max_threads()); 

    TimingCPU timerCPU; 
    timerCPU.StartCounter(); 
    fftw_execute(h_plan_zp); 
    printf("\nStadard on CPU: \t \t %f\n", timerCPU.GetCounter()); 

    /******************/ 
    /* STEP #1 ON CPU */ 
    /******************/ 
    timerCPU.StartCounter(); 
    step1CPU(h_xpruning, h_x, N, K); 
    partialTimeCPU = timerCPU.GetCounter(); 
    totalTimeCPU = totalTimeCPU + partialTimeCPU; 
    printf("\nOptimized first step CPU: \t %f\n", totalTimeCPU); 

    /******************/ 
    /* STEP #2 ON CPU */ 
    /******************/ 
    timerCPU.StartCounter(); 
    fftw_execute(h_plan_pruning); 
    partialTimeCPU = timerCPU.GetCounter(); 
    totalTimeCPU = totalTimeCPU + partialTimeCPU; 
    printf("Optimized second step CPU: \t %f\n", timerCPU.GetCounter()); 

    /******************/ 
    /* STEP #3 ON CPU */ 
    /******************/ 
    timerCPU.StartCounter(); 
    step3CPU(h_xhatpruning, h_xhatpruning_temp, N, K); 
    partialTimeCPU = timerCPU.GetCounter(); 
    totalTimeCPU = totalTimeCPU + partialTimeCPU; 
    printf("Optimized third step CPU: \t %f\n", partialTimeCPU); 

    printf("Total time CPU: \t \t %f\n", totalTimeCPU); 

    double rmserror = 0., norm = 0.; 
    for (int n = 0; n < N; n++) { 
     rmserror = rmserror + (h_xhatpruning[n][0] - h_xhat[n][0]) * (h_xhatpruning[n][0] - h_xhat[n][0]) + (h_xhatpruning[n][1] - h_xhat[n][1]) * (h_xhatpruning[n][1] - h_xhat[n][1]); 
     norm = norm + h_xhat[n][0] * h_xhat[n][0] + h_xhat[n][1] * h_xhat[n][1]; 
    } 
    printf("\nrmserror %f\n", 100. * sqrt(rmserror/norm)); 

    fftw_destroy_plan(h_plan_zp); 

} 

場合について

N = 10 
K = 100000 

私のタイミングは、以下の

Stadard on CPU:     23.895417 

Optimized first step CPU:  4.472087 
Optimized second step CPU:  4.926603 
Optimized third step CPU:  2.394958 
Total time CPU:     11.793648 
関連する問題