Загрузка 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:


С 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-битном режиме. Так что он не застрял подражая мозговым путям.

Другие вопросы по тегам