7

バッファをデ/インターリーブする最速の方法を探しています。より具体的に言うと、オーディオ データを扱っているので、チャンネルと FFT バッファーの分割/結合に費やす時間を最適化しようとしています。

現在、配列ごとに 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;
}
4

5 に答える 5

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

私はすべてのテストで同様の結果を得ました。アンセーフ コードが最も高速であることは明らかですが、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 とのヒット/ミス比較でもあります。これまでのところ、テスト 4 が最善の方法です。

私がjerryjvlに言ったように、問題はCLSが入力バッファをインデックスチェックしないようにすることです.2番目のチェック(&&オフセット<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.001234 seconds +/- 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) が (offset < inout.Length) を意味することをコンパイラーに伝えることができれば、チェック (inout[offset]) がドロップされ、基本的に安全でないパフォーマンスが得られます。

于 2009-06-07T13:55:46.977 に答える
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つである場合は、速度が向上します。

于 2009-06-07T12:35:12.770 に答える
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++;
                }
            }
        }
    }
}

これにより、パフォーマンスがさらに向上する可能性があります。

于 2009-06-07T14:23:00.807 に答える
1

それを行うための組み込み関数がないため、配列インデックスを使用することが考えられる最速の操作です。そのようなインデクサーとソリューションは、メソッド呼び出しを導入し、JIT オプティマイザーがバインドされたチェックを最適化できないようにすることで、事態を悪化させるだけです。

とにかく、あなたの現在の方法は、unsafeあなたが使用できる最速の非ソリューションだと思います。パフォーマンスが本当に重要な場合 (通常、信号処理アプリケーションではそうです)、unsafeC# ですべてを実行し (これは十分に高速で、おそらく C に匹敵します)、安全なメソッドから呼び出すメソッドにラップすることができます。 .

于 2009-06-07T11:35:12.580 に答える
0

多くの読者は、オーディオ処理のような安全でないソリューションを必要としない理由を疑問に思うと思います。これは、熱狂的な最適化を求めるタイプのものであり、VM を介して強制されていることを知っていると、私は不満を抱くでしょう。

于 2009-06-07T11:55:52.337 に答える