Загрузка 8 символов из памяти в переменную __m256 как упакованные числа с плавающей запятой одинарной точности
Я оптимизирую алгоритм размытия по Гауссу на изображении и хочу заменить использование плавающего буфера [8] в приведенном ниже коде встроенной переменной __m256. Какая серия инструкций лучше всего подходит для этой задачи?
// unsigned char *new_image is loaded with data
...
float buffer[8];
buffer[x ] = new_image[x];
buffer[x + 1] = new_image[x + 1];
buffer[x + 2] = new_image[x + 2];
buffer[x + 3] = new_image[x + 3];
buffer[x + 4] = new_image[x + 4];
buffer[x + 5] = new_image[x + 5];
buffer[x + 6] = new_image[x + 6];
buffer[x + 7] = new_image[x + 7];
// buffer is then used for further operations
...
//What I want instead in pseudocode:
__m256 b = [float(new_image[x+7]), float(new_image[x+6]), ... , float(new_image[x])];
1 ответ
Если вы используете AVX2, вы можете использовать PMOVZX для расширения нуля ваших символов до 32-битных целых в 256-битном регистре. Оттуда, преобразование в плавание может произойти на месте.
; rsi = new_image
VPMOVZXBD ymm0, [rsi] ; or SX to sign-extend (Byte to DWord)
VCVTDQ2PS ymm0, ymm0 ; convert to packed foat
Мой ответ на масштабирование значений байтовых пикселей (y=ax+b) с SSE2 (как поплавки)? может быть актуальным. Последующая часть пакета "обратно в байты" является полу-хитрой, если делать это с AVX2 packssdw/packuswb
потому что они работают в переулке, в отличие от vpmovzx
,
Из-за этого ответа отправлено две ошибки gcc:
- Загрузка SSQ /AVX movq (_mm_cvtsi64_si128) не складывается в pmovzx
- Нет встроенного для x86
MOVQ m64, %xmm
в 32-битном режиме. (TODO: сообщите об этом также для clang/LLVM?)
С AVX1, а не AVX2, вы должны сделать:
VPMOVZXBD xmm0, [rsi]
VPMOVZXBD xmm1, [rsi+4]
VINSERTF128 ymm0, ymm0, xmm1, 1 ; put the 2nd load of data into the high128 of ymm0
VCVTDQ2PS ymm0, ymm0 ; convert to packed foat. Yes, works without AVX2
Вам, конечно, никогда не нужен массив с плавающей точкой, просто __m256
векторы.
На самом деле я не могу найти способ сделать это с присущей ей безопасностью (избегая загрузки за пределы желаемого 8B с помощью -O0
) и оптимально (делает хороший код с -O3
).
Нет необходимости использовать SSE4.1 pmovsx
/ pmovzx
в качестве нагрузки, только с __m128i
исходный операнд. Тем не менее, они только читают объем данных, которые они фактически используют. В отличие от punpck*
Вы можете использовать это на последних 8B страницы без ошибок. (И на невыровненных адресах даже с версией не-AVX).
Там свойственно для movq
, но GCC 5.3 не видит через него и по-прежнему складывать загрузку в операнд памяти для vpmovzx
, Таким образом, функция составлена из 3 инструкций. clang 3.6 сворачивает movq в операнд памяти для pmovzx, а clang 3.5.1 - нет. ICC13 также делает оптимальный код.
Итак, вот плохое решение, которое я придумала. Не используйте это, #ifdef __OPTIMIZE__
плохо.
#if !defined(__OPTIMIZE__)
// Making your code compile differently with/without optimization is a TERRIBLE idea
// great way to create Heisenbugs that disappear when you try to debug them.
// Even if you *plan* to always use -Og for debugging, instead of -O0, this is still evil
#define USE_MOVQ
#endif
__m256 load_bytes_to_m256(uint8_t *p)
{
#ifdef USE_MOVQ // compiles to an actual movq then movzx reg, reg with gcc -O3
__m128i small_load = _mm_cvtsi64_si128( *(uint64_t*)p );
#else // USE_LOADU // compiles to a 128b load with gcc -O0, potentially segfaulting
__m128i small_load = _mm_loadu_si128( (__m128i*)p );
#endif
__m256i intvec = _mm256_cvtepu8_epi32( small_load );
//__m256i intvec = _mm256_cvtepu8_epi32( *(__m128i*)p ); // compiles to an aligned load with -O0
return _mm256_cvtepi32_ps(intvec);
}
С включенным USE_MOVQ, gcc -O3
(v5.3.0) испускает
load_bytes_to_m256(unsigned char*):
vmovq xmm0, QWORD PTR [rdi]
vpmovzxbd ymm0, xmm0
vcvtdq2ps ymm0, ymm0
ret
Глупый vmovq
это то, чего мы хотим избежать Если вы позволите ему использовать loadu_si128
версия, это сделает хороший оптимизированный код.
Написание версии, предназначенной только для AVX1, со встроенными функциями остается для читателя невеселым занятием. Вы просили "инструкции", а не "внутренности", и это единственное место, где есть пробел в внутренностях. Необходимость использовать _mm_cvtsi64_si128
Во избежание возможной загрузки из-за границы адресов глупо, ИМО. Я хочу иметь возможность рассматривать встроенные функции в терминах инструкций, которые они отображают, а встроенные функции загрузки / хранения информируют компилятор о гарантиях выравнивания или их отсутствии. Необходимость использовать встроенную инструкцию, которую я не хочу, довольно глупа.
Также обратите внимание, что если вы ищете в руководстве Intel insn ref, есть две отдельные записи для movq:
MOVD / MOVQ, версия, которая может иметь целочисленный регистр в качестве операнда src/dest (
66 REX.W 0F 6E
(или жеVEX.128.66.0F.W1 6E
) для (V)MOVQ xmm, r/m64). Вот где вы найдете встроенную функцию, которая может принимать 64-битное целое число.movq: версия, которая может иметь два регистра xmm в качестве операндов. Это расширение инструкции MMXreg -> MMXreg, которая также может загружаться / храниться, как MOVDQU. Его код операции
F3 0F 7E
(VEX.128.F3.0F.WIG 7E
) заMOVQ xmm, xmm/m64)
, Единственное внутреннее перечисленное здесьm128i _mm_mov_epi64(__m128i a)
для обнуления старшего 64b вектора при копировании.
Это действительно глупо. GCC даже не определяет _mm_cvtsi64_si128
для 32-битных целей. vmovq xmm, r/m64
конечно, не кодируется в 32-битном режиме, так как он полагается на VEX.W (или префикс REX для кодирования не-AVX) и может кодировать 64-битный регистр в качестве источника вместо 64-битной ячейки памяти. Вы можете использовать встроенную функцию для загрузки в регистр MMX, затем mmx -> xmm, затем _mm_mov_epi64
, но это, вероятно, не оптимизирует отскок через регистр mmx.
ICC13 определяет _mm_cvtsi64_si128
для 32 бит, но с -O0
компилируется в 2х vmovd
+ vpunpckldq
, Это удается использовать vmovq
с -O3
Впрочем, ( для отдельной тестовой функции) даже в 32-битном режиме. Так что он не застрял подражая мозговым путям.