SSE _mm_load_pd在_mm_store_pd段错误时起作用

时间:2013-03-18 11:42:02

标签: c sse simd intrinsics

我正在尝试用C学习SSE内在函数的绳索。我有一段代码,我在其中加载双数据的双组分向量,向其中添加内容然后尝试将其存储回内存。 一切正常:我可以将数据加载到SEE寄存器中,我可以对那些SSE寄存器中的数据进行操作,但是当我尝试将处理后的数据写回原始数组时(我在第一次读取数据时)我得到了一个分段错误。

任何人都可以就此问题向我提出建议 - 这让我感到疯狂。

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

正如我上面所说,除了我将结果存储到一维数组“C”之外,一切都在这个代码中工作,我从那里开始读取我的数据。 也就是说,当我删除

前面的评论标志时
//C[i+k*n] = res[0];

我遇到了分段错误。

我怎样才能从C读取_mm_load_pd的对齐内存版本(所以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解决方案

为了在写入原始一维数组时解决分段错误问题,我现在使用缓冲区来处理相应的矩阵。但是,当尝试写回C_buff时,这仍然是segfauls!

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

3 个答案:

答案 0 :(得分: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元素)将防止此类问题,并将导致更好的性能。

答案 1 :(得分: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()
}

然后在main中使用:

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 ++编写的,所以你只需改变一些函数参数样式就可以了。

答案 2 :(得分:0)

一个简单的技巧是执行ASSERT并查看它是否触发:

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

当地址未与SSE对齐时,ASSERT将触发。 默认情况下,64位构建应提供16B对齐。 如果您计划使用32位代码,请使用上述align_malloc函数之一。 您需要使用相关的align_free,否则您将崩溃。