9

我正在尝试学习 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];

我得到一个分段错误。

我怎么可能用 _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 尝试的解决方案

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

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

即使使用__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++ 中,所以你可以让它只改变一些函数参数样式。

于 2013-07-15T10:25:00.590 回答
0

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

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

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

于 2013-11-21T14:22:56.800 回答