1

MMX 命令を使用して int32 データ配列から要素を累積的に追加するインライン アセンブラー ループがあります。特に、MMX レジスタが 16 個の int32 に対応できるという事実を利用して、16 個の異なる累積和を並列に計算します。

このコードを MMX 組み込み関数に変換したいと考えていますが、8 つの MMX レジスタを使用して 16 の独立した和を計算するようコンパイラに明示的に指示することはできないため、パフォーマンスが低下するのではないかと心配しています。

誰かがこれについてコメントして、以下のコードを変換して組み込み関数を使用する方法についての解決策を提案できますか?

== インラインアセンブラ (ループ内のみ) ==

paddd   mm0, [esi+edx+8*0]  ; add first & second pair of int32 elements
paddd   mm1, [esi+edx+8*1]  ; add third & fourth pair of int32 elements ...
paddd   mm2, [esi+edx+8*2]
paddd   mm3, [esi+edx+8*3]
paddd   mm4, [esi+edx+8*4]
paddd   mm5, [esi+edx+8*5]
paddd   mm6, [esi+edx+8*6]
paddd   mm7, [esi+edx+8*7]  ; add 15th & 16th pair of int32 elements
  • esi はデータ配列の先頭を指します
  • edx は、現在のループ反復のデータ配列にオフセットを提供します
  • データ配列は、16 個の独立した合計の要素がインターリーブされるように配置されます。
4

1 に答える 1

2

VS2010 は、組み込み関数を使用して同等のコードに対して適切な最適化作業を行います。ほとんどの場合、組み込みをコンパイルします。

sum = _mm_add_pi32(sum, *(__m64 *) &intArray[i + offset]);

次のようなものに:

movq  mm0, mmword ptr [eax+8*offset]
paddd mm1, mm0

これは ほど簡潔ではありませんpadd mm1, [esi+edx+8*offset]が、間違いなくかなり近いものです。実行時間は、メモリ フェッチによって支配される可能性があります。

問題は、VS が MMX レジスタを他の MMX レジスタにのみ追加することを好むように見えることです。上記のスキームは、最初の 7 つの合計に対してのみ機能します。8 番目の合計では、一部のレジスタを一時的にメモリに保存する必要があります。

完全なプログラムとそれに対応するコンパイル済みアセンブリ (リリース ビルド) を次に示します。

#include <stdio.h>
#include <stdlib.h>
#include <xmmintrin.h>

void addWithInterleavedIntrinsics(int *interleaved, int count)
{
    // sum up the numbers
    __m64 sum0 = _mm_setzero_si64(), sum1 = _mm_setzero_si64(),
          sum2 = _mm_setzero_si64(), sum3 = _mm_setzero_si64(),
          sum4 = _mm_setzero_si64(), sum5 = _mm_setzero_si64(),
          sum6 = _mm_setzero_si64(), sum7 = _mm_setzero_si64();

    for (int i = 0; i < 16 * count; i += 16) {
        sum0 = _mm_add_pi32(sum0, *(__m64 *) &interleaved[i]);
        sum1 = _mm_add_pi32(sum1, *(__m64 *) &interleaved[i + 2]);
        sum2 = _mm_add_pi32(sum2, *(__m64 *) &interleaved[i + 4]);
        sum3 = _mm_add_pi32(sum3, *(__m64 *) &interleaved[i + 6]);
        sum4 = _mm_add_pi32(sum4, *(__m64 *) &interleaved[i + 8]);
        sum5 = _mm_add_pi32(sum5, *(__m64 *) &interleaved[i + 10]);
        sum6 = _mm_add_pi32(sum6, *(__m64 *) &interleaved[i + 12]);
        sum7 = _mm_add_pi32(sum7, *(__m64 *) &interleaved[i + 14]);
    }

    // reset the MMX/floating-point state
    _mm_empty();

    // write out the sums; we have to do something with the sums so that
    // the optimizer doesn't just decide to avoid computing them.
    printf("%.8x %.8x\n", ((int *) &sum0)[0], ((int *) &sum0)[1]);
    printf("%.8x %.8x\n", ((int *) &sum1)[0], ((int *) &sum1)[1]);
    printf("%.8x %.8x\n", ((int *) &sum2)[0], ((int *) &sum2)[1]);
    printf("%.8x %.8x\n", ((int *) &sum3)[0], ((int *) &sum3)[1]);
    printf("%.8x %.8x\n", ((int *) &sum4)[0], ((int *) &sum4)[1]);
    printf("%.8x %.8x\n", ((int *) &sum5)[0], ((int *) &sum5)[1]);
    printf("%.8x %.8x\n", ((int *) &sum6)[0], ((int *) &sum6)[1]);
    printf("%.8x %.8x\n", ((int *) &sum7)[0], ((int *) &sum7)[1]);
}

void main()
{
    int count        = 10000;
    int *interleaved = new int[16 * count];

    // create some random numbers to add up
    // (note that on VS2010, RAND_MAX is just 32767)
    for (int i = 0; i < 16 * count; ++i) {
        interleaved[i] = rand();
    }

    addWithInterleavedIntrinsics(interleaved, count);
}

以下は、合計ループの内側部分の生成されたアセンブリ コードです (プロローグとエピローグを除く)。ほとんどの合計が mm1-mm6 で効率的に保持されていることに注意してください。これを、各合計に加算する数値をもたらすために使用される mm0 と、最後の 2 つの合計を提供する mm7 と比較してください。このプログラムの 7-sum バージョンには、mm7 の問題はないようです。

012D1070  movq        mm7,mmword ptr [esp+18h]  
012D1075  movq        mm0,mmword ptr [eax-10h]  
012D1079  paddd       mm1,mm0  
012D107C  movq        mm0,mmword ptr [eax-8]  
012D1080  paddd       mm2,mm0  
012D1083  movq        mm0,mmword ptr [eax]  
012D1086  paddd       mm3,mm0  
012D1089  movq        mm0,mmword ptr [eax+8]  
012D108D  paddd       mm4,mm0  
012D1090  movq        mm0,mmword ptr [eax+10h]  
012D1094  paddd       mm5,mm0  
012D1097  movq        mm0,mmword ptr [eax+18h]  
012D109B  paddd       mm6,mm0  
012D109E  movq        mm0,mmword ptr [eax+20h]  
012D10A2  paddd       mm7,mm0  
012D10A5  movq        mmword ptr [esp+18h],mm7  
012D10AA  movq        mm0,mmword ptr [esp+10h]  
012D10AF  movq        mm7,mmword ptr [eax+28h]  
012D10B3  add         eax,40h  
012D10B6  dec         ecx  
012D10B7  paddd       mm0,mm7  
012D10BA  movq        mmword ptr [esp+10h],mm0  
012D10BF  jne         main+70h (12D1070h)  

それで、あなたは何ができますか?

  1. 7-sum および 8-sum 組み込みベースのプログラムをプロファイリングします。より速く実行されるものを選択してください。

  2. 一度に 1 つの MMX レジスタを追加するバージョンをプロファイリングします。最新のプロセッサが一度に 64 ~ 128 バイトをキャッシュにフェッチするという事実を利用できるはずです。8-sum バージョンが 1-sum バージョンよりも高速であることは明らかではありません。1-sum バージョンは、まったく同じ量のメモリをフェッチし、まったく同じ数の MMX 追加を行います。ただし、それに応じて入力をインターリーブする必要があります。

  3. ターゲット ハードウェアで許可されている場合は、SSE 命令の使用を検討してください。一度に 4 つの 32 ビット値を追加できます。SSE は、1999 年の Pentium III 以降の Intel CPU で利用可能です。

于 2010-06-07T03:44:44.040 に答える