4

私は現在、複素数の配列(std :: complexと同じように整列されたメモリですが、現在は独自のADTを使用しています)を同じスカラー値の配列で最も効率的にインプレース乗算しようとしています。複素数配列としてのサイズ。

アルゴリズムはすでに並列化されています。つまり、呼び出し元のオブジェクトが作業をスレッドに分割します。この計算は数億のアレイで実行されるため、完了するまでに時間がかかる場合があります。CUDAは、この製品のソリューションではありません。私はブーストにアクセスできるので、BLAS/uBLASを使用する可能性があります。

SIMDの方がはるかに良い結果が得られるかもしれないと思っていますが、複素数でこれを行う方法については十分に理解していません。私が今持っているコードは次のとおりです(これはターゲットマシンのコアの数に対応するスレッドにチャンクアップされていることを思い出してください)。ターゲットマシンも不明です。したがって、一般的なアプローチがおそらく最善です。

void cmult_scalar_inplace(fcomplex *values, const int start, const int end, const float *scalar)
{
    for (register int idx = start; idx < end; ++idx)
    {
        values[idx].real *= scalar[idx];
        values[idx].imag *= scalar[idx];
    }
}

fcomplexは次のように定義されます。

struct fcomplex
{
    float real;
    float imag;
};

最終的にループカウントは常に2の累乗になるため、手動でループを展開しようとしましたが、コンパイラーはすでにそれを実行しています(32まで展開しました)。私はスカラーへのconstfloat参照を試しました-1つのアクセスを節約すると考えて-そしてそれはコンパイラーがすでに行っていたものと等しいことが証明されました。私はSTLと変換を試しましたが、どちらのゲームも結果は近いですが、それでもさらに悪い結果になります。また、std :: complexにキャストして、乗算のスカラー*複合体にオーバーロードされた演算子を使用できるようにしましたが、最終的には同じ結果が得られました。

それで、何かアイデアを持っている人はいますか?これを検討する上で、多くの感謝を捧げます!ターゲットプラットフォームはWindowsです。Visual Studio 2008を使用しています。製品にGPLコードを含めることはできません!本当にありがとう。

4

4 に答える 4

1

私が見ている問題の1つは、関数内で、スカラーポインターが実際に複素数配列の中央を指しているのではないことをコンパイラーが理解するのが難しいことです(scalar理論的には、複素数または複素数の実数部を指している可能性があります)。これは実際に評価の順序を強制します。

私が見るもう1つの問題は、ここでは計算が非常に単純であるため、他の要因が生の速度に影響を与えることです。したがって、パフォーマンスを本当に気にする場合、唯一の解決策は、いくつかのバリエーションを実装し、実行時にユーザーマシンでそれらをテストして発見することです。最速は何ですか。

私が考えているのは、さまざまな展開サイズを使用し、との配置を調整するscalarことですvalues(メモリアクセスパターンは、キャッシュ効果に大きな影響を与える可能性があります)。

不要なシリアル化の問題の場合、オプションは、次のようなものに対して生成されたコードを確認することです。

float r0 = values[i].real, i0 = values[i].imag, s0 = scalar[i];
float r1 = values[i+1].real, i1 = values[i+1].imag, s1 = scalar[i+1];
float r2 = values[i+2].real, i2 = values[i+2].imag, s2 = scalar[i+2];
values[i].real = r0*s0; values[i].imag = i0*s0;
values[i+1].real = r1*s1; values[i+1].imag = i1*s1;
values[i+2].real = r2*s2; values[i+2].imag = i2*s2;

ここでは、オプティマイザには理論的にもう少し自由度があるためです。

于 2011-07-28T19:57:34.927 に答える
1

最善の策は、ターゲットプラットフォームで利用可能なものをすべて利用する最適化されたBLASを使用することです。

于 2011-07-28T18:57:12.897 に答える
1

これはSSEでかなり簡単に行うことができます。

void cmult_scalar_inplace(fcomplex *values, const int start, const int end, const float *scalar)
{
    for (int idx = start; idx < end; idx += 2)
    {
        __m128 vc = _mm_load_ps((float *)&values[idx]);
        __m128 vk = _mm_set_ps(scalar[idx + 1], scalar[idx + 1], scalar[idx], scalar[idx]);
        vc = _mm_mul_ps(vc, vk);
        _mm_store_ps((float *)&values[idx], vc);
    }
}

とは16バイトに整列する必要があることにvalues注意してください。scalar

または、インテルICCコンパイラーを使用して、ハードワークを実行させることもできます。


アップデート

これは、ループを2倍に展開し、単一のロード命令を使用して4つのスカラー値を取得し、2つのベクトルにアンパックする改良版です。

void cmult_scalar_inplace(fcomplex *values, const int start, const int end, const float *scalar)
{
    for (int idx = start; idx < end; idx += 4)
    {
        __m128 vc0 = _mm_load_ps((float *)&values[idx]);
        __m128 vc1 = _mm_load_ps((float *)&values[idx + 2]);
        __m128 vk = _mm_load_ps(&scalar[idx]);
        __m128 vk0 = _mm_shuffle_ps(vk, vk, 0x50);
        __m128 vk1 = _mm_shuffle_ps(vk, vk, 0xfa);
        vc0 = _mm_mul_ps(vc0, vk0);
        vc1 = _mm_mul_ps(vc1, vk1);
        _mm_store_ps((float *)&values[idx], vc0);
        _mm_store_ps((float *)&values[idx + 2], vc1);
    }
}
于 2011-07-28T19:42:52.737 に答える
0

インテルのIntegratedPerformancePrimitivesにアクセスできますか? 統合パフォーマンスプリミティブ これらには、このようなケースをかなり適切なパフォーマンスで処理する多くの関数があります。あなたはあなたの特定の問題でいくらかの成功を収めるかもしれませんが、あなたのコンパイラがすでにコードを最適化するというまともな仕事をしていても私は驚かないでしょう。

于 2011-07-28T18:53:47.297 に答える