質問

MMX命令を備えたINT32データアレイの要素を累積的に追加するインラインアセンブラーループがあります。特に、MMXレジスタが16のINT32に対応して16の異なる累積合計を並行して計算できるという事実を使用します。

このコードをMMX Intrinsicsに変換したいと思いますが、コンパイラを明示的に整理して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の独立した合計の要素がインターリーブされるように配置されています。
役に立ちましたか?

解決

VS2010は、Intrinsicsを使用して同等のコードで適切な最適化ジョブを実行します。ほとんどの場合、それは本質的なものをコンパイルします:

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);
}

これは、Sumループの内側部分の生成されたアセンブリコードです(そのプロログとエピログなし)。 MM1-MM6でほとんどの合計が効率的に保持される方法に注意してください。それとは対照的に、それを各合計に追加するために数をもたらすために使用され、最後の2つの合計を提供するMM7と対照的です。このプログラムの7サムバージョンには、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サムおよび8サムの内因性ベースのプログラムをプロファイルします。より速く実行するものを選択します。

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

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

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top