9

C で SSE 組み込み関数のロープを学習しようとしています。 double データの 2 成分ベクトルをロードし、それに何かを追加してからメモリに保存しようとするコードがあります。すべてが機能します。データを SEE レジスタにロードし、それらの SSE レジスタでデータを操作できますが、その処理されたデータを元の配列に書き戻そうとするとすぐに元の配列 (最初にデータを読み取った場所) に書き戻します。 place!) セグメンテーション違反が発生します。

誰でもこの問題について私にアドバイスしてもらえますか - これは私を狂わせています。

double res[2] __attribute__((aligned(16)));

for(int k=0; k<n; k++){
int i=0;
for(; i+1<n; i+=2)
  {
    __m128d cik = _mm_load_pd(&C[i+k*n]);
    int j = 0;
    for(; j+1<n; j+=2)
      {
        __m128d aTij = _mm_load_pd(&A_T[j+i*n]);
        __m128d bjk = _mm_load_pd(&B[j+k*n]);
        __m128d dotpr = _mm_dp_pd(aTij, bjk,2);
        cik = _mm_add_pd(cik, dotpr);
      }
    _mm_store_pd(res, cik);
    //C[i+k*n] = res[0];
  }
}

上で述べたように、最初にデータを読み取った 1 次元配列 "C" に結果を格納する場所を除いて、このコードではすべてが機能します。つまり、前のコメント記号を削除すると、

//C[i+k*n] = res[0];

セグメンテーション違反が発生します。

アライメントされたメモリ バージョンの _mm_load_pd (したがって、C はメモリ内でアライメントされている必要があります) を使用して C から読み取ることができますが、書き戻すことはできません。「C」は整列する必要があり、ご覧のとおり「res」も整列する必要があります。

免責事項:私の元のコードを読んだ

_mm_store_pd(&C[i+k*n], cik);

これもセグメンテーション違反を引き起こし、問題を解決するために明示的な配置で「res」を導入し始めました。

補遺

A、B、C は次のように宣言されます。

buf = (double*) malloc (3 * nmax * nmax * sizeof(double));
double* A = buf + 0;
double* B = A + nmax*nmax;
double* C = B + nmax*nmax;

posix_memalign で試みられた解決策

元の 1 次元配列に書き込むときのセグメンテーション違反の問題を解決するために、対応する行列にバッファーを使用するようになりました。ただし、C_buff に書き戻そうとすると、これでも segfaul が発生します。

double res[2] __attribute__((aligned(16)));

double * A_T;
posix_memalign((void**)&A_T, 16, n*n*sizeof(double));

double * B_buff;
posix_memalign((void**)&B_buff, 16, n*n*sizeof(double));

double * C_buff;
posix_memalign((void**)&C_buff, 16, n*n*sizeof(double));

for(int y=0; y<n; y++)
  for(int x=0; x<n; x++)
    A_T[x+y*n] = A[y+x*n];

for(int x=0; x<n; x++)
  for(int y=0; y<n; y++)
    B_buff[y+x*n] = B[y+x*n];

for(int x=0; x<n; x++)
  for(int y=0; y<n; y++)
    C_buff[y+x*n] = C[y+x*n];

for(int k=0; k<n; k++){
  int i=0;
  for(; i+1<n; i+=2)
    {
      __m128d cik = _mm_load_pd(&C_buff[i+k*n]);
      int j = 0;
      for(; j+1<n; j+=2)
        {
          __m128d aTij = _mm_load_pd(&A_T[j+i*n]);
          __m128d bjk = _mm_load_pd(&B_buff[j+k*n]);
          __m128d dotpr = _mm_dp_pd(aTij, bjk,2);
          cik = _mm_add_pd(cik, dotpr);
        }
      _mm_store_pd(&C_buff[i+k*n], cik);

  //_mm_store_pd(res, cik);
      //C_buff[i+k*n] = res[0];
  //C_buff[i+1+k*n] = res[1];
    }
}
4

3 に答える 3

1

を削除すると_mm_store_pd(&C_buff[i+k*n], cik);、ループ全体が最適化されて削除されます。コンパイラは、for ループ全体が意味のある作業につながらないと推測し、それを削除します。そのため、セグメンテーション違反が発生しなくなりました。
セグメンテーション違反は、配列のサイズが原因であると確信しています。あなたの例に基づいて、この簡単なプログラムを検討してください。

#include <stdio.h>
#include "emmintrin.h"

int main(){
int n = 15;
int y,x,k,i,j;

double * A;
posix_memalign((void**)&A, 16, n*n*sizeof(double));

double * B;
posix_memalign((void**)&B, 16, n*n*sizeof(double));

double * C;
posix_memalign((void**)&C, 16, n*n*sizeof(double));

for(y=0; y<n; y++)
  for(x=0; x<n; x++)
    A[x+y*n] = 0.1;

for(x=0; x<n; x++)
  for(y=0; y<n; y++)
    B[y+x*n] = 0.1;

for(x=0; x<n; x++)
  for( y=0; y<n; y++)
    C[y+x*n] = 0.1;

for( k=0; k<n; k++){
   i=0;
  for(; i+1<n; i+=2)
    {
      __m128d cik = _mm_load_pd(&C[i+k*n]);
       j = 0;
      for(; j+1<n; j+=2)
        {
          __m128d aTij = _mm_load_pd(&A[j+i*n]);
          __m128d bjk = _mm_load_pd(&B[j+k*n]);
          __m128d dotpr = _mm_add_pd(aTij, bjk);
          cik = _mm_add_pd(cik, dotpr);
        }
      _mm_store_pd(&C[i+k*n], cik);
    }
}
printf("C[15]: %f\n", C[15]);
printf("C[14]: %f\n", C[14]);

n が奇数であるため、これによりセグメンテーション違反が発生します。n=15 を n=16 に変更すると、すべてが期待どおりに実行されます。したがって、配列を偶数 (またはさらに良いことに、キャッシュ ラインのサイズ -> 64 バイト == 8 DP 要素または 16 SP 要素) にパディングすると、このような問題が回避され、パフォーマンスが向上します。

于 2015-03-16T15:49:21.543 に答える
0

A simple trick would be to perform an ASSERT and see if it triggers:

ASSERT( ((size_t)(&C_buff[i+k*n]) & 0xF) == 0);

The ASSERT will fire when the address isn't SSE aligned. 64 bit builds should provide 16B alignment by default. If you plan to have 32bit code, use one of the above align_malloc functions. You'll need to use the relevant align_free or you'll crash.

于 2013-11-21T14:22:56.800 に答える
0

を使用しても__attribute__((aligned(32)))、同じエラーが発生していました (ミスアラインメントの可能性が 50% ありました)。次に、次の関数を使用して、100% のアラインメントの可能性を取得しました (a は 2 のべき乗である必要があります)。

void * malloc_float_align(size_t n, unsigned int a/*alignment*/, float *& output)
{
    void * adres=NULL;
    void * adres2=NULL;
    adres=malloc(n*sizeof(float)+a);
    size_t adr=(size_t)adres;
    size_t adr2=adr+a-(adr&(a-1u)); // a valid address for a alignment
    adres2=(void * ) adr2;
    output=(float *)adres2;
    return adres;                //pointer to be used in free()
}

次に、メインでの使用:

int main()
{


  float * res=NULL;
  void * origin=malloc_float_align(1024,32u,res);
  //use res for sse/avx
  free(origin); // actual allocation is more than 1024 elements
  return 0;
}

もちろん、これは c++ であるため、一部の関数パラメーター スタイルを変更するだけで動作させることができます。

于 2013-07-15T10:25:00.590 に答える