У меня есть функция C ++ с некоторыми инструкциями SSE2. Проблема в том, что я получаю следующую ошибку компоновщика при компиляции этого кода с помощью Microsoft Visual C ++:
неразрешенный внешний символ _m_empty, указанный в функции «void * __cdecl»
процесс (недействительный *)
И когда я комментирую _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);
}
В соответствии с эта ссылка, MMX не реализован для x64. Используйте полноценный SSE2 n x64.
я думаю _mm_empty
в вашем коде превращается в ссылку на _m_empty
, так как они синонимы, и ваша среда сборки по-прежнему имеет заголовок с #define _mm_empty _m_empty
или что-то.
Но, как ни странно, ваша среда сборки на самом деле не дает определения для внутренней. Есть ли предупреждение компилятора о том, что оно неявно объявлено? Это странно, потому что я ожидаю, что полное отсутствие поддержки MMX будет означать _mm_empty
/ _m_empty
эквивалентности тоже не было бы.
Ошибка времени выполнения, вероятно, не связана с этим. Как отметил Пол Р. в комментарии, вы предполагаете, что если бы вы могли получить неизмененный исходный код для компиляции, он бы работал. Это, вероятно, не так, потому что это _mm_empty
выглядит ненужным для экспертов по x86 asm (включая меня), которые прокомментировали этот вопрос.
Я думаю, что предположение Пола Р., что у вас может быть указатель без выравнивания, звучит разумно. Выравнивание какого-либо из этих массивов зависит от 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
,
Благодаря Гарольду, Полу и Питеру, я нашел проблему! Вы были правы, ошибка времени выполнения не связана с _mm_empty! Проблема была в порядке ввода входных параметров _aligned_malloc. Ошибка выполнения исчезла, когда я поменял местами входы.
Еще одной ошибкой была функция free (). _aligned_free () необходимо использовать для освобождения выровненной памяти.
Как предложил Гарольд, я изменил основной цикл, чтобы использовать три независимых v. Если я ошибся, поправьте меня, пожалуйста. Теперь программа (не функция!) Работает на 300 мс быстрее (2,4 с -> 2,1 с).
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;
}