2009-06-07 9 views
5

バッファをデインタリーブする最も速い方法を探しています。 具体的には、私はオーディオデータを扱っているので、チャンネルとFFTバッファの分割/合成に費やす時間を最適化しようとしています。C/C++でデ・インターリーブ配列を高速に実行する

現在、私は、各配列に2つのインデックス変数を持つforループを使用しています。したがって、操作だけですが、すべてのマネージド配列チェックはCポインタメソッドと比較されません。

私はBuffer.BlockCopyとArray.Copyメソッドが好きです。これはチャンネルを処理するのに時間がかかるのですが、配列にカスタムインデクサーを持たせる方法はありません。

私は配列マスクを作る方法を見つけようとしていましたが、カスタムのインデクサーを持つ偽の配列になりますが、それはFFT操作で使用すると2倍遅くなることが分かります。私は、配列に直接アクセスするときにコンパイラが引き出すことができる多くの最適化トリックがあると思うが、クラスインデクサを介してアクセスすることは最適化できない。

このタイプの操作を最適化する唯一の方法は、見た目からは、安全ではないソリューションが欲しいというわけではありません。

ありがとうございました。何の配列インデックスを使用すると、あなたが考える可能性が最速の操作で、それを行うための機能に組み込まれてはありませんとして

private float[][] DeInterleave(float[] buffer, int channels) 
{ 
    float[][] tempbuf = new float[channels][]; 
    int length = buffer.Length/channels; 
    for (int c = 0; c < channels; c++) 
    { 
     tempbuf[c] = new float[length]; 
     for (int i = 0, offset = c; i < tempbuf[c].Length; i++, offset += channels) 
      tempbuf[c][i] = buffer[offset]; 
    } 
    return tempbuf; 
} 
+0

あなたがしようとしているコードの断片を提供できますか?あなたが達成しようとしていることの具体的なサンプルを手助けする方がずっと簡単です。 – jerryjvl

答えて

5

私は試験コードいくつかのテストを実行し、ここで:

delegate(float[] inout) 
{ // My Original Code 
    float[][] tempbuf = new float[2][]; 
    int length = inout.Length/2; 
    for (int c = 0; c < 2; c++) 
    { 
     tempbuf[c] = new float[length]; 
     for (int i = 0, offset = c; i < tempbuf[c].Length; i++, offset += 2) 
      tempbuf[c][i] = inout[offset]; 
    } 
} 
delegate(float[] inout) 
{ // jerryjvl's recommendation: loop unrolling 
    float[][] tempbuf = new float[2][]; 
    int length = inout.Length/2; 
    for (int c = 0; c < 2; c++) 
     tempbuf[c] = new float[length]; 
    for (int ix = 0, i = 0; ix < length; ix++) 
    { 
     tempbuf[0][ix] = inout[i++]; 
     tempbuf[1][ix] = inout[i++]; 
    } 

} 
delegate(float[] inout) 
{ // Unsafe Code 
    unsafe 
    { 
     float[][] tempbuf = new float[2][]; 
     int length = inout.Length/2; 
     fixed (float* buffer = inout) 
      for (int c = 0; c < 2; c++) 
      { 
       tempbuf[c] = new float[length]; 
       float* offset = buffer + c; 
       fixed (float* buffer2 = tempbuf[c]) 
       { 
        float* p = buffer2; 
        for (int i = 0; i < length; i++, offset += 2) 
         *p++ = *offset; 
       } 
      } 
    } 
} 
delegate(float[] inout) 
{ // Modifying my original code to see if the compiler is not as smart as i think it is. 
    float[][] tempbuf = new float[2][]; 
    int length = inout.Length/2; 
    for (int c = 0; c < 2; c++) 
    { 
     float[] buf = tempbuf[c] = new float[length]; 
     for (int i = 0, offset = c; i < buf.Length; i++, offset += 2) 
      buf[i] = inout[offset]; 
    } 
} 

及び結果:(バッファサイズ= 2^17、テスト= 200あたりのタイミング数回の反復)

Average for test #1:  0.001286 seconds +/- 0.000026 
Average for test #2:  0.001193 seconds +/- 0.000025 
Average for test #3:  0.000686 seconds +/- 0.000009 
Average for test #4:  0.000847 seconds +/- 0.000008 

Average for test #1:  0.001210 seconds +/- 0.000012 
Average for test #2:  0.001048 seconds +/- 0.000012 
Average for test #3:  0.000690 seconds +/- 0.000009 
Average for test #4:  0.000883 seconds +/- 0.000011 

Average for test #1:  0.001209 seconds +/- 0.000015 
Average for test #2:  0.001060 seconds +/- 0.000013 
Average for test #3:  0.000695 seconds +/- 0.000010 
Average for test #4:  0.000861 seconds +/- 0.000009 

をIテストごとに同様の結果が得られました。明らかに安全でないコードが最も速いですが、ぎざぎざの配列を扱うときにインデックスチェックを削除できるということをCLSが理解できなかったことに驚きました。たぶん誰かが私のテストを最適化するためのより多くの方法を考えることができます。

編集: 安全でないコードでループアンローリングを試みましたが、効果がありませんでした。

delegate(float[] inout) 
{ 
    float[][] tempbuf = new float[2][]; 
    int length = inout.Length/2; 
    float[] tempbuf0 = tempbuf[0] = new float[length]; 
    float[] tempbuf1 = tempbuf[1] = new float[length]; 

    for (int ix = 0, i = 0; ix < length; ix++) 
    { 
     tempbuf0[ix] = inout[i++]; 
     tempbuf1[ix] = inout[i++]; 
    } 
} 

結果はまた、1%の差でヒットミス比較試験#4である: Iはまた、ループ展開方法を最適化しようとしました。テスト#4はこれまでのところ私が行く最良の方法です。私は第2のチェックを追加するのでjerryjvl、問題は、ないインデックス入力バッファをチェックするためにCLSを取得していると語ったと

は、それが遅くなります...

編集(& &は< inout.Lengthオフセット)2 : 私はここに、IDEで前にテストを実行した結果が外部にある:ループ展開が良好ではないよう

2^17 items, repeated 200 times 
****************************************** 
Average for test #1:  0.000533 seconds +/- 0.000017 
Average for test #2:  0.000527 seconds +/- 0.000016 
Average for test #3:  0.000407 seconds +/- 0.000008 
Average for test #4:  0.000374 seconds +/- 0.000008 
Average for test #5:  0.000424 seconds +/- 0.000009 

2^17 items, repeated 200 times 
****************************************** 
Average for test #1:  0.000547 seconds +/- 0.000016 
Average for test #2:  0.000732 seconds +/- 0.000020 
Average for test #3:  0.000423 seconds +/- 0.000009 
Average for test #4:  0.000360 seconds +/- 0.000008 
Average for test #5:  0.000406 seconds +/- 0.000008 


2^18 items, repeated 200 times 
****************************************** 
Average for test #1:  0.001295 seconds +/- 0.000036 
Average for test #2:  0.001283 seconds +/- 0.000020 
Average for test #3:  0.001085 seconds +/- 0.000027 
Average for test #4:  0.001035 seconds +/- 0.000025 
Average for test #5:  0.001130 seconds +/- 0.000025 

2^18 items, repeated 200 times 
****************************************** 
Average for test #1:  0.0seconds +/- 0.000026 
Average for test #2:  0.001319 seconds +/- 0.000023 
Average for test #3:  0.001309 seconds +/- 0.000025 
Average for test #4:  0.001191 seconds +/- 0.000026 
Average for test #5:  0.001196 seconds +/- 0.000022 

Test#1 = My Original Code 
Test#2 = Optimized safe loop unrolling 
Test#3 = Unsafe code - loop unrolling 
Test#4 = Unsafe code 
Test#5 = My Optimized Code 

が見えます。私の最適化されたコードは、安全でないコードと比較してわずか10%の違いがあります。私がコンパイラに(i < buf.Length)(オフセット< inout.Length)を意味するだけであれば、それはチェック(inout [offset])を落とし、基本的に安全でないパフォーマンスを得るでしょう。

+0

私はこの段階で質問が「十分に速い」に戻ると思う); ... perfが現在のニーズに対応していれば、最もクリーンで、最も簡単な実装を選択し、それはコメントの中で...またはその逆です。 – jerryjvl

+0

私の元のコードは十分速かった。私はパフォーマンスヒットを見なかった。異なるサンプルレート、リサンプリング、ミキシング、winmmとopenalへの送信で3つのmp3ファイルを(オンザフライで)デコードします。しかし、私は基数2の計算ではなくビット単位の推論を開始し、Buffer.BlockCopyで可能なものすべてを置き換え始めたので、この問題に対処する最善の方法を知っていると、あまり強力でないマシン(Windowsのモバイルデバイスなど) – MaXKilleR

+0

+1、あなた自身の質問に答えるのではなく、この有用な運動の結果を世界中に分かち合うために。 – ja72

1

:ここ

は、私が今やっている事のタイプです。インデクサとそのようなソリューションは、メソッド呼び出しを導入し、JITオプティマイザがバインドされたチェックを最適化できないようにするだけで、状況を悪化させます。

とにかく、あなたの現在の方法は、使用可能な最速の非unsafeソリューションだと思います。もしパフォーマンスが本当にあなたにとって問題であれば(通常は信号処理アプリケーションで)、unsafe C#(これは十分速く、おそらくCと匹敵します)ですべてを行い、あなたの金庫から呼び出す方法で包み込みますメソッド。

0

私は、多くの読者がなぜオーディオ処理のようなものに対して安全でないソリューションを望まないのかと疑問に思います。それは熱血最適化を頼むもののタイプであり、VMによって強制されていることを知っていると私は幸いにも不幸になります。

+0

安全なコードにはVMが含まれていません。 –

+0

JITがコンパイルされています。問題は解釈ではなく、配列境界チェックです。 –

+0

私は安全なコードを信じており、システムに依存しない最適化によって危険なコードにマッチすることがあります。あなたが安全でない瞬間、特定のシステムのために最適化されており、それはC#を使用して全体のポイントを破壊します。安全でないコードが必要だった場合、私はC++を使用していましたが、移植性と速度を同時に望みます。 基本的には、信号処理のようなものが管理された言語で速く動作することを証明しようとしています。 – MaXKilleR

1

私はあなたのパフォーマンスを大幅に向上させることはできませんが(私はおおよそマシンで20%を測定しました)、一般的なケースではいくつかのループ展開を検討することができます。限り、あなたはそれが任意の数のチャネルを処理しますが、それならば、あなたは、スピードブーストを取得しますが、一般的なフォールバックバリアントを残すよう

static private float[][] Alternative(float[] buffer, int channels) 
{ 
    float[][] result = new float[channels][]; 
    int length = buffer.Length/channels; 
    for (int c = 0; c < channels; c++) 
     result[c] = new float[length]; 

    int i = 0; 
    if (channels == 8) 
    { 
     for (int ix = 0; ix < length; ix++) 
     { 
      result[0][ix] = buffer[i++]; 
      result[1][ix] = buffer[i++]; 
      result[2][ix] = buffer[i++]; 
      result[3][ix] = buffer[i++]; 
      result[4][ix] = buffer[i++]; 
      result[5][ix] = buffer[i++]; 
      result[6][ix] = buffer[i++]; 
      result[7][ix] = buffer[i++]; 
     } 
    } 
    else 
     for (int ix = 0; ix < length; ix++) 
      for (int ch = 0; ch < channels; ch++) 
       result[ch][ix] = buffer[i++]; 


    return result; 
} 

:時間のほとんどは、あなたは、チャネルの比較的数が限られている場合展開されている亜種の1つです。

+0

これらの行に沿って、動的にアンロールされたバージョンを生成することができるかもしれません.... – Dolphin

+0

私は反復ごとに1つの配列にアクセスしているので、CLSはセクションをリロードする必要はないので、私が知る限り、配列からセクションをロードするので、次の操作で次の要素にアクセスする方が高速になります。 – MaXKilleR

+0

私のクイックテストでは、私が例として使っている8チャンネルとかなり大きなバッファーで約20%のゲインが得られていることがわかりました。地球は壊れていませんが、私は何かを推測するのに役立ちますか? ...シナリオのパフォーマンスを向上させるために、実際に行っていることに基づいていくつかの現実的なテストを実行することができます。 – jerryjvl

1

たぶん、あなた自身の最高の答えではいくつかの展開:

delegate(float[] inout) 
{ 
    unsafe 
    { 
     float[][] tempbuf = new float[2][]; 
     int length = inout.Length/2; 

     fixed (float* buffer = inout) 
     { 
      float* pbuffer = buffer; 

      tempbuf[0] = new float[length]; 
      tempbuf[1] = new float[length]; 

      fixed (float* buffer0 = tempbuf[0]) 
      fixed (float* buffer1 = tempbuf[1]) 
      { 
       float* pbuffer0 = buffer0; 
       float* pbuffer1 = buffer1; 

       for (int i = 0; i < length; i++) 
       { 
        *pbuffer0++ = *pbuffer++; 
        *pbuffer1++ = *pbuffer++; 
       } 
      } 
     } 
    } 
} 

これはまだ少しより高いパフォーマンスを得る可能性があります。

+0

私はあなたのコードをテストし、それはヒットミスです。 1つは速く、次は遅く、1%だけ実行します。 私の最善の答えはテスト#4です。私は安全な解決策が必要です。安全と不安全の20%の違いは悪くありませんが、私はまだそのギャップを減らすことは可能だと思います。 問題は、入力バッファをチェックするためにCLSにインデックスを作成させないことです。別のチェック(&& offset MaXKilleR

+0

最高のパフォーマンスが本当に必要な場合は、あなたがこれまでに得たベストアンサーから作成したILをチェックし、トリミングできる余分なものがないかどうかを確認する必要があります。 – jerryjvl

+0

PS:IDEの外ですべての測定を行っていたと思いますよね? – jerryjvl

関連する問題