为啥我不能删除 _mm_empty()?

Posted

技术标签:

【中文标题】为啥我不能删除 _mm_empty()?【英文标题】:Why can't I remove _mm_empty()?为什么我不能删除 _mm_empty()? 【发布时间】:2015-12-16 09:25:31 【问题描述】:

我有一个带有一些 SSE2 指令的 c++ 函数。问题是我在使用 microsoft visual c++ 编译此代码时收到以下链接器错误:

函数“void * __cdecl”中引用的未解析外部符号 _m_empty 进程(void *)"

当我评论 _m_empty 时,我会得到一个运行时错误! 但它应该用于 MMX 指令,不是吗?

#include "mex.h"
#include <pthread.h>
#include <emmintrin.h>
#include <stdint.h>
#include <stdlib.h>

#define malloc_aligned(a,b) _aligned_malloc(a,b)
#define IS_ALIGNED(ptr) ((((uintptr_t)(ptr)) & 0xF) == 0)

#define NUM_FEATURES 32
#define __attribute__(A) /* do nothing */

/*
 * This code is used for computing filter responses.  It computes the
 * response of a set of filters with a feature map.  
 *
 * Multithreaded version.
 */

struct thread_data 
  float *A;
  float *B;
  double *C;
  mxArray *mxC;
  const mwSize *A_dims;
  const mwSize *B_dims;
  mwSize C_dims[2];
;

// convolve A and B
void *process(void *thread_arg) 
  thread_data *args = (thread_data *)thread_arg;
  float *A = args->A;
  float *B = args->B;
  double *C = args->C;
  const mwSize *A_dims = args->A_dims;
  const mwSize *B_dims = args->B_dims;
  const mwSize *C_dims = args->C_dims;

  __m128 a,b,c;
  double *dst = C;
  for (int x = 0; x < C_dims[1]; x++) 
    for (int y = 0; y < C_dims[0]; y++) 
      __m128 v = _mm_setzero_ps();
      const float *A_src = A + y*NUM_FEATURES + x*A_dims[0]*NUM_FEATURES;
      const float *B_src = B;
      for (int xp = 0; xp < B_dims[1]; xp++) 
        const float *A_off = A_src;
        const float *B_off = B_src;
        for (int yp = 0; yp < B_dims[0]; yp++) 
          a = _mm_load_ps(A_off+0);
          b = _mm_load_ps(B_off+0);
          c = _mm_mul_ps(a, b);
          v = _mm_add_ps(v, c);

          a = _mm_load_ps(A_off+4);
          b = _mm_load_ps(B_off+4);
          c = _mm_mul_ps(a, b);
          v = _mm_add_ps(v, c);

          a = _mm_load_ps(A_off+8);
          b = _mm_load_ps(B_off+8);
          c = _mm_mul_ps(a, b);
          v = _mm_add_ps(v, c);

          a = _mm_load_ps(A_off+12);
          b = _mm_load_ps(B_off+12);
          c = _mm_mul_ps(a, b);
          v = _mm_add_ps(v, c);

          a = _mm_load_ps(A_off+16);
          b = _mm_load_ps(B_off+16);
          c = _mm_mul_ps(a, b);
          v = _mm_add_ps(v, c);

          a = _mm_load_ps(A_off+20);
          b = _mm_load_ps(B_off+20);
          c = _mm_mul_ps(a, b);
          v = _mm_add_ps(v, c);

          a = _mm_load_ps(A_off+24);
          b = _mm_load_ps(B_off+24);
          c = _mm_mul_ps(a, b);
          v = _mm_add_ps(v, c);

          a = _mm_load_ps(A_off+28);
          b = _mm_load_ps(B_off+28);
          c = _mm_mul_ps(a, b);
          v = _mm_add_ps(v, c);

          // N.B. Unroll me more/less if you change NUM_FEATURES

          A_off += NUM_FEATURES;
          B_off += NUM_FEATURES;
        

        A_src += A_dims[0]*NUM_FEATURES;
        B_src += B_dims[0]*NUM_FEATURES;
      
      // buf[] must be 16-byte aligned
      float buf[4] __attribute__ ((aligned (16)));
      _mm_store_ps(buf, v);
      _mm_empty();
      *(dst++) = buf[0]+buf[1]+buf[2]+buf[3];
    
  
  pthread_exit(NULL);
  return 0;


float *prepare(float *in, const int *dims) 
  float *F = (float *)malloc_aligned(16, dims[0]*dims[1]*NUM_FEATURES*sizeof(float));
  // Sanity check that memory is aligned
  if (!IS_ALIGNED(F))
    mexErrMsgTxt("Memory not aligned");

  float *p = F;
  for (int x = 0; x < dims[1]; x++) 
    for (int y = 0; y < dims[0]; y++) 
      for (int f = 0; f < dims[2]; f++)
        *(p++) = in[y + f*dims[0]*dims[1] + x*dims[0]];
      for (int f = dims[2]; f < NUM_FEATURES; f++)
        *(p++) = 0;
    
  
  return F;


// matlab entry point
// C = fconv(A, cell of B, start, end);
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])  
  if (nrhs != 4)
    mexErrMsgTxt("Wrong number of inputs"); 
  if (nlhs != 1)
    mexErrMsgTxt("Wrong number of outputs");

  // get A
  const mxArray *mxA = prhs[0];
  if (mxGetNumberOfDimensions(mxA) != 3 || 
      mxGetClassID(mxA) != mxSINGLE_CLASS)
    mexErrMsgTxt("Invalid input: A");

  // get B and start/end
  const mxArray *cellB = prhs[1];
  mwSize num_bs = mxGetNumberOfElements(cellB);  
  int start = (int)mxGetScalar(prhs[2]) - 1;
  int end = (int)mxGetScalar(prhs[3]) - 1;
  if (start < 0 || end >= num_bs || start > end)
    mexErrMsgTxt("Invalid input: start/end");
  int len = end-start+1;

  // start threads
  thread_data *td = (thread_data *)mxCalloc(len, sizeof(thread_data));
  pthread_t *ts = (pthread_t *)mxCalloc(len, sizeof(pthread_t));
  const mwSize *A_dims = mxGetDimensions(mxA);
  float *A = prepare((float *)mxGetPr(mxA), A_dims);
  for (int i = 0; i < len; i++) 
    const mxArray *mxB = mxGetCell(cellB, i+start);
    td[i].A_dims = A_dims;
    td[i].A = A;
    td[i].B_dims = mxGetDimensions(mxB);
    td[i].B = prepare((float *)mxGetPr(mxB), td[i].B_dims);
    if (mxGetNumberOfDimensions(mxB) != 3 ||
        mxGetClassID(mxB) != mxSINGLE_CLASS ||
        td[i].A_dims[2] != td[i].B_dims[2])
      mexErrMsgTxt("Invalid input: B");

    // compute size of output
    int height = td[i].A_dims[0] - td[i].B_dims[0] + 1;
    int width = td[i].A_dims[1] - td[i].B_dims[1] + 1;
    if (height < 1 || width < 1)
      mexErrMsgTxt("Invalid input: B should be smaller than A");
    td[i].C_dims[0] = height;
    td[i].C_dims[1] = width;
    td[i].mxC = mxCreateNumericArray(2, td[i].C_dims, mxDOUBLE_CLASS, mxREAL);
    td[i].C = (double *)mxGetPr(td[i].mxC);

    if (pthread_create(&ts[i], NULL, process, (void *)&td[i]))
      mexErrMsgTxt("Error creating thread");  
  

  // wait for the treads to finish and set return values
  void *status;
  plhs[0] = mxCreateCellMatrix(1, len);
  for (int i = 0; i < len; i++) 
    pthread_join(ts[i], &status);
    mxSetCell(plhs[0], i, td[i].mxC);
    free(td[i].B);
  
  mxFree(td);
  mxFree(ts);
  free(A);

【问题讨论】:

那里的展开很糟糕,你将依赖链保留在v 所以它大致相当于不展开。 是的。只要不必将变量溢出到堆栈中,拥有变量实际上并不需要任何成本。关键是要在“未展开”循环的迭代之间获得更多重叠,它们现在重叠,因此每次迭代仍需要 3 个周期(与根本不展开时相同),因为循环依赖于 @987654323 @。使用至少 3 个不同的独立 v's 运行速度可以提高 3 倍 完全删除_mm_empty,不要用任何东西替换它。这是近 20 年前 MMX 时代遗留下来的遗留物。如果你忽略它时出现问题,它将与 _mm_empty 完全无关。这是一条红鲱鱼。例如,这些加载地址真的对齐了吗? 我认为@harold 搞定了——你可能有一个对齐问题,只有在你注释掉_mm_empty 时才会出现——去掉_mm_empty,修复所有对齐问题,你应该没问题. _m_empty 是您的问题中的错字吗?我认为问题在于您的代码中存在拼写错误,并且它被视为隐式声明的外部函数,从而在链接时产生错误。 如果没有 _mm_empty() 内在函数,您会遇到什么运行时错误?您的其他一些代码是否使用 MMX,而这只是对其进行清理?顺便说一句,在循环内使用EMMS 来允许一些标量数学对性能来说看起来很糟糕。在 Haswell 上,它是 31 微指令,每 13 个周期的吞吐量为 1。如果不需要,你不想运行它。 【参考方案1】:

根据this link,MMX 未针对 x64 实现。使用成熟的 SSE2 n x64。

【讨论】:

我认为这个函数使用的是 SSE2 而不是 MMX。我说的对吗? 但是 _mm_empty 是一个 MMX 函数。 我知道,但我说“当我评论 _m_empty 时,我会收到运行时错误!”。为什么? 因为您不能随意注释掉程序的行并期望它仍然可以工作? x86-64 ISA 仍然可以在 64 位模式下运行 MMX 指令。您是否声称 MS 的编译器无法识别 64 位目标的内在函数?或者 Windows 不会运行使用 MMX 寄存器的 64 位代码?链接失效了。【参考方案2】:

我认为您代码中的_mm_empty 变成了对_m_empty 的引用,因为they're synonyms,并且您的构建环境仍然有一些带有#define _mm_empty _m_empty 或其他内容的标头。

但奇怪的是,您的构建环境实际上并未提供内在函数的定义。是否有关于它被隐式声明的编译器警告?这很奇怪,因为我预计完全缺乏 MMX 支持将意味着 _mm_empty / _m_empty 等效项也不存在。


运行时错误可能与此无关。就像 Paul R 在评论中指出的那样,您假设如果您可以编译未经修改的源代码,它会起作用。情况可能并非如此,因为在评论过这个问题的 x86 asm 专家(包括我)看来,_mm_empty 是不必要的。

我认为 Paul R 猜测您可能在某个地方有一个未对齐的指针听起来很合理。这些数组的对齐是否依赖于sizeof 指针?如果struct thread_data 类似于:

struct thread_data 
    some_type *ptr1;
    some_type *ptr2;
    int a;
    int b;
    float A[1024];
    float B[1024];
    ...
;

然后 32 位构建将具有 16B 对齐的数组,但 64 位构建不会。

所以调试你的运行时错误,并找出它是什么。 如果您告诉我们的只是“运行时错误”,我们将无能为力。如果您一开始就告诉我们它是什么,我们可以以一种或另一种方式告诉您它是否可能与删除 _mm_empty 有关。

【讨论】:

【参考方案3】:

感谢 harold、Paul 和 Peter,我找到了问题所在!你是对的,运行时错误与 _mm_empty 无关!问题是 _aligned_malloc 输入参数的顺序。当我交换输入时,运行时错误消失了。

另一个错误是 free() 函数。 _aligned_free() 必须用于释放对齐的内存。

按照哈罗德的建议,我将主循环更改为使用三个独立的 v。如果我做错了,请纠正我。现在程序(不是函数!)运行速度快了 300 毫秒(2.4s -> 2.1s)。

void *process(void *thread_arg) 
  thread_data *args = (thread_data *)thread_arg;
  float *A = args->A;
  float *B = args->B;
  double *C = args->C;
  const mwSize *A_dims = args->A_dims;
  const mwSize *B_dims = args->B_dims;
  const mwSize *C_dims = args->C_dims;

  __m128 a,b,c;
  double *dst = C;
  for (int x = 0; x < C_dims[1]; x++) 
    for (int y = 0; y < C_dims[0]; y++) 
      __m128 v = _mm_setzero_ps(), v1 = _mm_setzero_ps(), v2 = _mm_setzero_ps(), v3 = _mm_setzero_ps();     
      const float *A_src = A + y*NUM_FEATURES + x*A_dims[0]*NUM_FEATURES;
      const float *B_src = B;

      for (int xp = 0; xp < B_dims[1]; xp++) 
        const float *A_off = A_src;
        const float *B_off = B_src;
        for (int yp = 0; yp < B_dims[0]; yp++) 
          a = _mm_load_ps(A_off+0);
          b = _mm_load_ps(B_off+0);
          c = _mm_mul_ps(a, b);
          v1 = _mm_add_ps(v1, c);

          a = _mm_load_ps(A_off+4);
          b = _mm_load_ps(B_off+4);
          c = _mm_mul_ps(a, b);
          v2 = _mm_add_ps(v2, c);

          a = _mm_load_ps(A_off+8);
          b = _mm_load_ps(B_off+8);
          c = _mm_mul_ps(a, b);
          v3 = _mm_add_ps(v3, c);

          a = _mm_load_ps(A_off+12);
          b = _mm_load_ps(B_off+12);
          c = _mm_mul_ps(a, b);
          v1 = _mm_add_ps(v1, c);

          a = _mm_load_ps(A_off+16);
          b = _mm_load_ps(B_off+16);
          c = _mm_mul_ps(a, b);
          v2 = _mm_add_ps(v2, c);

          a = _mm_load_ps(A_off+20);
          b = _mm_load_ps(B_off+20);
          c = _mm_mul_ps(a, b);
          v3 = _mm_add_ps(v3, c);

          a = _mm_load_ps(A_off+24);
          b = _mm_load_ps(B_off+24);
          c = _mm_mul_ps(a, b);
          v1 = _mm_add_ps(v1, c);

          a = _mm_load_ps(A_off+28);
          b = _mm_load_ps(B_off+28);
          c = _mm_mul_ps(a, b);
          v2 = _mm_add_ps(v2, c);

          // N.B. Unroll me more/less if you change NUM_FEATURES

          A_off += NUM_FEATURES;
          B_off += NUM_FEATURES;
        
        A_src += A_dims[0]*NUM_FEATURES;
        B_src += B_dims[0]*NUM_FEATURES;
      
        v = _mm_add_ps(v, v1);
        v = _mm_add_ps(v, v2);
        v = _mm_add_ps(v, v3);

      // buf[] must be 16-byte aligned
      __declspec(align(16)) float buf[4];
      _mm_store_ps(buf, v);

      *(dst++) = buf[0]+buf[1]+buf[2]+buf[3];
    
  
  pthread_exit(NULL);
  return 0;

【讨论】:

@harold 你对我所做的展开有什么看法? 这并没有让我知道,因为我不在这个答案的 cmets 中,但是是的,看起来更好 - 也许也可以尝试 4 v。

以上是关于为啥我不能删除 _mm_empty()?的主要内容,如果未能解决你的问题,请参考以下文章

如果有 string.Empty 为啥没有 char.Empty? [复制]

unitywebplayer为啥安装了不能用

为啥我不能删除一个项目?

为啥我不能删除这个表格?

为啥我不能删除此 SQL Server 表 [重复]

为啥我不能删除代码中的中间变量?