9

次のプログラムを検討してください。

for i=1 to 10000000 do
  z <- z*z + c

ここでz、 とcは複素数です。

x87 対 SSE および単精度対倍精度演算を使用した、このプログラムの効率的な x86 アセンブラー実装は何ですか?

EDITこれを別の言語で記述し、コンパイラが最適なマシンコードを生成することを信頼できることは知っていますが、最適なx86アセンブラを自分で記述する方法を学ぶためにこれを行っています。によって生成されたコードを既に見てきましたgcc -O2が、改善の余地はたくさんあると思いますが、最適な x86 アセンブラーを自分で書くほど熟達していないので、ここで助けを求めています。

4

3 に答える 3

7

アセンブラー自体でこれを行う必要はありません。特に単精度を使用できる場合は、効率的な実装のために組み込み関数を介して SSE を使用できます。

temp.re = z.re * z.re - z.im * z.im;
temp.im = 2.0 * z.re * z.im;
z.re = temp.re + c.re;
z.im = temp.im + c.im;

入力ベクトルを適切にシャッフルすると、1 つの命令 ( ) ですべての乗算を取得_mm_mul_psし、2 番目の命令 ( _mm_hadd_ps) で加算を取得できます。

倍精度が必要な場合は、同じ一般原則が適用されますが、2 つの乗算と 2 つの水平方向の加算が必要になります。

最近のほとんどの x86 CPU には 2 つのスカラー FPU があるため、SSE での倍精度の利点は価値がない可能性があることに注意してください。


これは、SSE を使用した初期の実用的な実装です。現在は多かれ少なかれデバッグされていると思いますが、パフォーマンスは gcc -O3 でコンパイルされたスカラー コードよりもはるかに優れているわけではありませんが、gcc はこのために SSE コードを生成するのにかなりうまく機能します。

static Complex loop_simd(const Complex z0, const Complex c, const int n)
{
    __m128 vz = _mm_set_ps(z0.im, z0.re, z0.im, z0.re);
    const __m128 vc = _mm_set_ps(0.0f, 0.0f, c.im, c.re);
    const __m128 vs = _mm_set_ps(0.0f, 0.0f, -0.0f, 0.0f);
    Complex z[2];
    int i;

    for (i = 0; i < n; ++i)
    {
        __m128 vtemp;

        vtemp = _mm_shuffle_ps(vz, vz, 0x16); // temp = { z.re, z.im, z.im, z.re }
        vtemp = _mm_xor_ps(vtemp, vs);        // temp = { z.re, -z.im, z.im, z.re }
        vtemp = _mm_mul_ps(vtemp, vz);        // temp = { z.re * z.re, - z.im * z.im, z.re * z.im, z.im * z.re }
        vtemp = _mm_hadd_ps(vtemp, vtemp);    // temp = { z.re * z.re - z.im * z.im, 2 * z.re * z.im, ... }
        vz = _mm_add_ps(vtemp, vc);           // temp = { z.re * z.re - z.im * z.im + c.re, 2 * z.re * z.im + c.im, ... }
    }
    _mm_storeu_ps(&z[0].re, vz);
    return z[0];
}

内側のループは 6 つの SSE 命令 (実際には 5 つあるはずです) + ループ自体のちょっとしたハウスキーピングであることに注意してください。

L4:
    movaps  %xmm0, %xmm1
    shufps  $22, %xmm0, %xmm1
    xorps   %xmm3, %xmm1
    mulps   %xmm1, %xmm0
    haddps  %xmm0, %xmm0
    addps   %xmm2, %xmm0
    incl    %eax
    cmpl    %edi, %eax
    jne L4
L2:
于 2012-04-26T08:43:08.707 に答える
6

お気に入りのコンパイラからの逆アセンブリを見てください。zandの複数の値に対してこの計算を実行する場合c(マンデルブロ画像の計算など)、一度に 4 つの値を処理し、これらを SSE レジスタに入れることをお勧めします。Paul R の回答のコードを見ると、一度に 4 つの値に対してこれらすべての計算を行うことができます。

__m128 z_im, z_re, c_im, c_re; //Four z and c values packed
__m128 re = _mm_sub_ps(_mm_mul_ps(z_re, z_re), _mm_mul_ps(z_im, z_im));
__m128 im = _mm_mul_ps(z_re, z_im);
im = _mm_add_ps(im, im); // Multiply by two
z_re = _mm_add_ps(re, c_re);
z_im = _mm_add_ps(im, c_im);
于 2012-04-26T08:43:23.613 に答える
3

Z = Z*Z + C

それがマンデルブロ フラクタル反復です。

このために高度に最適化されたコードがネット上で見つかるはずです。Xaos と Fractint のソースコードから始めます。

于 2012-04-26T08:52:54.653 に答える