У меня есть проблема оптимизации векторизации.
У меня есть структура pDst, которая имеет 3 поля с именами: «красный», «зеленый» и «синий».
Тип может быть ‘Char’, ‘Short’ или ‘Float’. Это дано и не может быть изменено.
Существует еще один массив pSrc, который представляет изображение [RGB] — а именно массив из 3 указателей, каждый из которых указывает на слой изображения.
Каждый слой строится с использованием ориентированного изображения плоскости IPP (а именно, каждая плоскость формируется независимо — ‘ippiMalloc_32f_C1’):
http://software.intel.com/sites/products/documentation/hpc/ipp/ippi/ippi_ch3/functn_Malloc.html.
Мы хотели бы скопировать его, как описано в следующем коде:
for(int y = 0; y < imageHeight; ++y)
{
for(int x = 0; x < imageWidth; ++x)
{
pDst[x + y * pDstRowStep].red = pSrc[0][x + y * pSrcRowStep];
pDst[x + y * pDstRowStep].green = pSrc[1][x + y * pSrcRowStep];
pDst[x + y * pDstRowStep].blue = pSrc[2][x + y * pSrcRowStep];
}
}
Тем не менее, в этой форме компилятор не может векторизовать код.
Сначала говорится:
msgstr «цикл не был векторизован: существование векторной зависимости.»
Когда я использую #pragma ivdep, чтобы помочь компилятору (так как нет никакой зависимости), я получаю следующую ошибку:
msgstr «цикл не был векторизован: разыменование слишком сложное.»
У кого-нибудь есть идеи, как разрешить векторизацию?
Я использую Intel Compiler 13.0.
Благодарю.
Если я отредактирую код следующим образом:
Ipp32f *redChannel = pSrc[0];
Ipp32f *greenChannel = pSrc[1];
Ipp32f *blueChannel = pSrc[2];
for(int y = 0; y < imageHeight; ++y)
{
#pragma ivdep
for(int x = 0; x < imageWidth; ++x)
{
pDst[x + y * pDstRowStep].red = redChannel[x + y * pSrcRowStep];
pDst[x + y * pDstRowStep].green = greenChannel[x + y * pSrcRowStep];
pDst[x + y * pDstRowStep].blue = blueChannel[x + y * pSrcRowStep];
}
}
Для выходных типов ‘char’ и ‘short’ я получаю векотизацию.
Тем не менее, для типа «плавать» я не.
Вместо этого я получаю следующее сообщение:
цикл не был векторизован: векторизация возможна, но кажется неэффективной.
Как это может быть?
В следующем коде использование прагмы ivdep, безусловно, игнорирует векторную зависимость, но эвристика / анализ затрат компилятора пришли к выводу, что векторизация цикла неэффективна:
Ipp32f *redChannel = pSrc[0];
Ipp32f *greenChannel = pSrc[1];
Ipp32f *blueChannel = pSrc[2];
for(int y = 0; y < imageHeight; ++y)
{
#pragma ivdep
for(int x = 0; x < imageWidth; ++x)
{
pDst[x + y * pDstRowStep].red = redChannel[x + y * pSrcRowStep];
pDst[x + y * pDstRowStep].green = greenChannel[x + y * pSrcRowStep];
pDst[x + y * pDstRowStep].blue = blueChannel[x + y * pSrcRowStep];
}
}
Векторизация будет неэффективной, поскольку операция включает копирование непрерывного блока памяти из источника в несмежные области памяти в месте назначения. Так что здесь происходит разброс. Если вы все еще хотите применить векторизацию и посмотреть, есть ли какие-либо улучшения производительности по сравнению с не векторизованной версией, используйте прагму simd вместо прагмы ivdep, как показано ниже:
#include<ipp.h>
struct Dest{
float red;
float green;
float blue;
};
void foo(Dest *pDst, Ipp32f **pSrc, int imageHeight, int imageWidth, int pSrcRowStep, int pDstRowStep){
Ipp32f *redChannel = pSrc[0];
Ipp32f *greenChannel = pSrc[1];
Ipp32f *blueChannel = pSrc[2];
for(int y = 0; y < imageHeight; ++y)
{
#pragma simd
for(int x = 0; x < imageWidth; ++x)
{
pDst[x + y * pDstRowStep].red = redChannel[x + y * pSrcRowStep];
pDst[x + y * pDstRowStep].green = greenChannel[x + y * pSrcRowStep];
pDst[x + y * pDstRowStep].blue = blueChannel[x + y * pSrcRowStep];
}
}
return;
}
Соответствующий отчет о векторизации:
$ icpc -c test.cc -vec-report2
test.cc(14): (col. 9) remark: SIMD LOOP WAS VECTORIZED
test.cc(11): (col. 5) remark: loop was not vectorized: not inner loop
Дополнительную документацию по прагме simd можно найти на https://software.intel.com/en-us/node/514582.
Что-то в этом роде должно работать (char
версия, не проверенная, также имейте в виду, что указатели __m128i должны быть правильно выровнены!)
void interleave_16px_to_rgb0(__m128i *red, __m128i *green, __m128i *blue, __m128i *dest) {
__m128i zero = _mm_setzero_si128();
__m128i rg_0 = _mm_unpackhi_epi8(*red, *green);
__m128i rg_1 = _mm_unpacklo_epi8(*red, *green);
__m128i bz_0 = _mm_unpackhi_epi8(*blue, zero);
__m128i bz_1 = _mm_unpacklo_epi8(*blue, zero);
dest[0] = _mm_unpackhi_epi16(rg_0, bz_0);
dest[1] = _mm_unpacklo_epi16(rg_0, bz_0);
dest[2] = _mm_unpackhi_epi16(rg_1, bz_1);
dest[3] = _mm_unpacklo_epi16(rg_1, bz_1);
}
Это займет 16 байтов от каждой плоскости:
r0 r1 r2 ... r16
g0 g1 g2 ... g16
b0 b1 b2 ... b16
и чередовать их вот так, записывая 16×4 байта, начиная с *dest
:
r0 g0 b0 0 r1 g1 b1 0 r2 g2 b2 0 ... r16 g16 b16 0
Само собой разумеется, что вы можете использовать то же семейство функций для чередования и других типов данных.
Обновить: еще лучше, поскольку у вас уже есть IPP, вы должны попытаться использовать то, что предоставляется, вместо того, чтобы изобретать велосипед. Из быстрой проверки видно, что вы ищете ippiCopy_8u_P3C3R или ippiCopy_8u_P4C4R.