Компиляция простой С++-программы, использующей встроенные средства SSE
Я новичок в инструкциях SSE, и я пытался изучить их с этого сайта:
http://www.codeproject.com/Articles/4522/Introduction-to-SSE-Programming
Я использую компилятор GCC на Ubuntu 10.10 с процессором Intel Core i7 960
Вот код, основанный на статье, которую я попытался:
Для двух массивов длины ARRAY_SIZE он вычисляет
fResult[i] = sqrt( fSource1[i]*fSource1[i] + fSource2[i]*fSource2[i] ) + 0.5
Вот код
#include <iostream>
#include <iomanip>
#include <ctime>
#include <stdlib.h>
#include <xmmintrin.h> // Contain the SSE compiler intrinsics
#include <malloc.h>
void myssefunction(
float* pArray1, // [in] first source array
float* pArray2, // [in] second source array
float* pResult, // [out] result array
int nSize) // [in] size of all arrays
{
int nLoop = nSize/ 4;
__m128 m1, m2, m3, m4;
__m128* pSrc1 = (__m128*) pArray1;
__m128* pSrc2 = (__m128*) pArray2;
__m128* pDest = (__m128*) pResult;
__m128 m0_5 = _mm_set_ps1(0.5f); // m0_5[0, 1, 2, 3] = 0.5
for ( int i = 0; i < nLoop; i++ )
{
m1 = _mm_mul_ps(*pSrc1, *pSrc1); // m1 = *pSrc1 * *pSrc1
m2 = _mm_mul_ps(*pSrc2, *pSrc2); // m2 = *pSrc2 * *pSrc2
m3 = _mm_add_ps(m1, m2); // m3 = m1 + m2
m4 = _mm_sqrt_ps(m3); // m4 = sqrt(m3)
*pDest = _mm_add_ps(m4, m0_5); // *pDest = m4 + 0.5
pSrc1++;
pSrc2++;
pDest++;
}
}
int main(int argc, char *argv[])
{
int ARRAY_SIZE = atoi(argv[1]);
float* m_fArray1 = (float*) _aligned_malloc(ARRAY_SIZE * sizeof(float), 16);
float* m_fArray2 = (float*) _aligned_malloc(ARRAY_SIZE * sizeof(float), 16);
float* m_fArray3 = (float*) _aligned_malloc(ARRAY_SIZE * sizeof(float), 16);
for (int i = 0; i < ARRAY_SIZE; ++i)
{
m_fArray1[i] = ((float)rand())/RAND_MAX;
m_fArray2[i] = ((float)rand())/RAND_MAX;
}
myssefunction(m_fArray1 , m_fArray2 , m_fArray3, ARRAY_SIZE);
_aligned_free(m_fArray1);
_aligned_free(m_fArray2);
_aligned_free(m_fArray3);
return 0;
}
Я получаю следующую ошибку компиляции
[Programming/SSE]$ g++ -g -Wall -msse sseintro.cpp
sseintro.cpp: In function ‘int main(int, char**)’:
sseintro.cpp:41: error: ‘_aligned_malloc’ was not declared in this scope
sseintro.cpp:53: error: ‘_aligned_free’ was not declared in this scope
[Programming/SSE]$
Где я перепутаю? Я пропускаю некоторые файлы заголовков? Кажется, я включил все соответствующие.
Ответы
Ответ 1
_ aligned_malloc и _ aligned_free являются Microsoft-измами, Используйте posix_memalign или memalign в Linux и другие. Для Mac OS X вы можете просто использовать malloc, так как он всегда равен 16 байт. Для портативного SSE-кода вы обычно хотите реализовать функции-обертки для выровненных распределений памяти, например.
void * malloc_simd(const size_t size)
{
#if defined WIN32 // WIN32
return _aligned_malloc(size, 16);
#elif defined __linux__ // Linux
return memalign(16, size);
#elif defined __MACH__ // Mac OS X
return malloc(size);
#else // other (use valloc for page-aligned memory)
return valloc(size);
#endif
}
Реализация free_simd
оставлена как упражнение для читателя.
Ответ 2
Короткий ответ: используйте _mm_malloc
и _mm_free
из xmmintrin.h
вместо _aligned_malloc
и _aligned_free
.
Обсуждение
Вы должны не использовать _aligned_malloc
, _aligned_free
, posix_memalign
, memalign
или что-то еще, когда вы пишете код SSE/AVX. Это все функции, связанные с компилятором и платформой (MSVC или GCC или POSIX).
Intel представила функции _mm_malloc
и _mm_free
в компиляторе Intel специально для вычислений SIMD (см. this). Другие компиляторы с целевой архитектурой x86 тоже добавили их (так же, как они регулярно добавляют внутреннюю среду Intel). В этом смысле они являются единственным кросс-платформенным решением: они должны быть доступны в каждом компиляторе, поддерживающем SSE.
Эти функции объявляются в заголовке xmmintrin.h
. Любой заголовок для более поздней версии SSE/AVX автоматически включает предыдущие, поэтому было бы достаточно включить только smmintrin.h
или emmintrin.h
например.
Ответ 3
Это напрямую не отвечает на ваш вопрос, но я хочу указать, что ваш код SSE неправильно написан, я был бы удивлен, если он сработает. Вам нужно использовать операции загрузки/хранения для не-sse-типов, которые включают выровненные не-sse-типы, такие как ваш выровненный массив float (вам нужно сделать это, даже если у вас есть динамический массив типа SSE). Вы должны помнить, что, когда вы работаете с SSE, типы данных SSE предполагают представлять данные в регистрах SSE, а все остальное обычно находится в системной памяти или не SSE-регистрах, и поэтому вам нужно загрузить/сохранить из/для регистрации и памяти. Как ваша функция должна выглядеть так:
void myssefunction
(
float* pArray1, // [in] first source array
float* pArray2, // [in] second source array
float* pResult, // [out] result array
int nSize // [in] size of all arrays
)
{
const __m128 m0_5 = _mm_set_ps1(0.5f); // m0_5[0, 1, 2, 3] = 0.5
for (size_t index = 0; index < nSize; index += 4)
{
__m128 pSrc1 = _mm_load_ps(pArray1 + index); // load 4 elements from memory into SSE register
__m128 pSrc2 = _mm_load_ps(pArray2 + index); // load 4 elements from memory into SSE register
__m128 m1 = _mm_mul_ps(pSrc1, pSrc1); // m1 = *pSrc1 * *pSrc1
__m128 m2 = _mm_mul_ps(pSrc2, pSrc2); // m2 = *pSrc2 * *pSrc2
__m128 m3 = _mm_add_ps(m1, m2); // m3 = m1 + m2
__m128 m4 = _mm_sqrt_ps(m3); // m4 = sqrt(m3)
__m128 pDest = _mm_add_ps(m4, m0_5); // pDest = m4 + 0.5
_mm_store_ps(pResult + index, pDest); // store 4 elements from SSE register to memory.
}
}
Также стоит отметить, что у вас есть предел количества регистров, которые можно использовать в данный момент времени (что-то вроде 16 для SSE2). Вы можете написать код, который пытается использовать больше, чем предел, но это приведет к разливу реестров.