1

SSE2を使用していくつかの値を一緒に追加するこの関数があります。これは、lhsとrhsを一緒に追加し、結果をlhsに格納することになっています。

template<typename T>
void simdAdd(T *lhs,T *rhs)
{
    asm volatile("movups %0,%%xmm0"::"m"(lhs));
    asm volatile("movups %0,%%xmm1"::"m"(rhs));

    switch(sizeof(T))
    {
        case sizeof(uint8_t):
        asm volatile("paddb %%xmm0,%%xmm1":);
        break;

        case sizeof(uint16_t):
        asm volatile("paddw %%xmm0,%%xmm1":);
        break;

        case sizeof(float):
        asm volatile("addps %%xmm0,%%xmm1":);
        break;

        case sizeof(double):
        asm volatile("addpd %%xmm0,%%xmm1":);
        break;

        default:
        std::cout<<"error"<<std::endl;
        break;
    }

    asm volatile("movups %%xmm0,%0":"=m"(lhs));
}

私のコードは次のような関数を使用しています:

float *values=new float[4];
float *values2=new float[4];

values[0]=1.0f;
values[1]=2.0f;
values[2]=3.0f;
values[3]=4.0f;

values2[0]=1.0f;
values2[1]=2.0f;
values2[2]=3.0f;
values2[3]=4.0f;

simdAdd(values,values2);
for(uint32_t count=0;count<4;count++) std::cout<<values[count]<<std::endl;

ただし、コードを実行すると、2,4,6,8ではなく1,2,3,4が出力されるため、これは機能しません。

4

2 に答える 2

5

最近のほとんどのコンパイラでは、インライン アセンブリのサポートが信頼できないことがわかりました (つまり、実装には単なるバグがあります)。一般に、C 関数のように見えるが実際には特定のオペコードにコンパイルされる宣言であるコンパイラ組み込み関数を使用する方が適切です。

組み込み関数を使用すると、オペコードの正確なシーケンスを指定できますが、レジスタの色付けはコンパイラに任せます。C 変数と asm レジスタの間でデータを移動しようとするよりもはるかに信頼性が高くなります。これは、インライン アセンブラが常に失敗する場所です。また、コンパイラは命令をスケジュールできるため、パイプラインの危険を回避することでパフォーマンスを向上させることができます。つまり、この場合、次のことができます

void simdAdd(float *lhs,float *rhs)
{
   _mm_storeu_ps( lhs, _mm_add_ps(_mm_loadu_ps( lhs ), _mm_loadu_ps( rhs )) );
}

とにかく、あなたの場合、2つの問題があります。

  1. ポインターと値の違いを大きく混乱させるひどい GCC インライン アセンブリ構文。lhs と rhs の代わりに*lhsandを使用します。*rhsどうやら「=m」構文は、「物そのものではなく、あなたに渡す物へのポインタを暗黙的に使用する」ことを意味します。
  2. GCC には source,destination 構文がありxmm1ますxmm0

私はコードパッドに修正された例を載せました(この答えが混乱するのを避け、それが機能することを実証するため)。

于 2010-11-16T02:13:30.130 に答える
0

ここで間違っていることがいくつかあります。まず、XMM レジスタをロードして値を変数に格納するステートメントが間違っています。

asm volatile("movups %0,%%xmm0"::"m"(lhs));
asm volatile("movups %0,%%xmm1"::"m"(rhs));
...
asm volatile("movups %%xmm0,%0":"=m"(lhs));

読むべき

asm volatile("movups %0,%%xmm0"::"m"(*lhs));
asm volatile("movups %0,%%xmm1"::"m"(*rhs));
...
asm volatile("movups %%xmm0,%0":"=m"(*lhs));

* に注意してください。ポインター値をロードして追加し、ポインター引数を渡すために使用された一時的にそれらを格納していました (その結果、関数呼び出しが返されたときにメモリに書き込まれずに忘れられます)。

これらの修正を行っても、一般に、これは適切な手法ではありません。asm ステートメントを使用して独自の例を作成しましたが、渡されるパラメーターのアライメントされていない性質を説明するのを忘れたため、欠陥がありました。asm ステートメントを使用すると非常に面倒になり、組み込み関数を使用するとはるかに簡単で読みやすくなります。正しいデータ型を使用するように注意してください。

template<typename T>
void simdAdd(T *lhs,T *rhs)
{
    switch(sizeof(T))
    {
        case sizeof(uint8_t):
        {
          __m128i lh128;
          lh128 = _mm_add_epi8( _mm_loadu_si128( (__m128i *)lhs ),
                                _mm_loadu_si128( (__m128i *)rhs ) );
          _mm_storeu_si128( (__m128i *)lhs, lh128 );
        }
        break;

        case sizeof(uint16_t):
        {
          __m128i lh128;
          lh128 = _mm_add_epi16( _mm_loadu_si128( (__m128i *)lhs ),
                                 _mm_loadu_si128( (__m128i *)rhs ) );
          _mm_storeu_si128( (__m128i *)lhs, lh128 );
        }
        break;

        case sizeof(float):
        {
          __m128 lh128;
          lh128 = _mm_add_ps( _mm_loadu_ps( (float *)lhs ),
                              _mm_loadu_ps( (float *)rhs ) );
          _mm_storeu_ps( (float *)lhs, lh128 );
        }
        break;

        case sizeof(double):
        {
          __m128d lh128;
          lh128 = _mm_add_pd( _mm_loadu_pd( (double *)lhs ),
                              _mm_loadu_pd( (double *)rhs ) );
          _mm_storeu_pd( (double *)lhs, lh128 );
        }
        break;

        default:
        std::cout<<"error"<<std::endl;
        break;
    }
}

注意すべきことは、データ型のサイズだけでは、渡されたデータ型を知るのに十分ではないということです。テンプレート型がチェックしている基本型と同じサイズを共有しているからといって、それが同じ型であるとは限りません。したがって、私の例では、キャストでこのケースをカバーするように強制しています。この関数が指定した型でのみ使用されることが確実でない限り、これは一般的に安全でない方法です。たとえば、float サイズの整数を使用すると、予想外に間違った結果が返され、コンパイラはそれについて警告することができません。

于 2014-11-19T22:02:38.837 に答える