Векторизация цикла 001

У меня есть проблема оптимизации векторизации.

У меня есть структура 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’ я получаю векотизацию.
Тем не менее, для типа «плавать» я не.
Вместо этого я получаю следующее сообщение:

цикл не был векторизован: векторизация возможна, но кажется неэффективной.

Как это может быть?

1

Решение

В следующем коде использование прагмы 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.

1

Другие решения

Что-то в этом роде должно работать (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.

1

По вопросам рекламы [email protected]