Встроенные функции C ++ SSE2 или AVX2 для преобразования оттенков серого в ARGB

Мне было интересно, есть ли целочисленная инструкция SSE2 / AVX2 или последовательность инструкций (или встроенных функций), которые нужно выполнить, чтобы достичь следующего результата:

Дана строка из 8-байтовых пикселей формы:

A = {a, b, c, d, e, f, g, h}

Есть ли способ загрузить эти пиксели в регистр YMM, который содержит 8 32-битных пикселей ARGB, так что начальное значение оттенков серого транслируется в другие 2 байта каждого соответствующего 32-битного пикселя? Результат должен быть примерно таким: (0 - значение альфа)

B = {0aaa, 0bbb, 0ccc, 0ddd, 0eee, 0fff, 0ggg, 0hhh}

Я полный новичок в векторных расширениях, поэтому я даже не уверен, как к этому подойти, и возможно ли это вообще.

Любая помощь будет оценена по достоинству. Спасибо!

ОБНОВЛЕНИЕ1

Спасибо за ответы. Но у меня все еще есть проблема:

Я собрал этот небольшой пример и скомпилировал его с VS2015 на x64.

int main()
{
    unsigned char* pixels = (unsigned char*)_aligned_malloc(64, 32);
    memset(pixels, 0, 64);

    for (unsigned char i = 0; i < 8; i++)
        pixels[i] = 0xaa + i;

    __m128i grayscalePix = _mm_load_si128((const __m128i*)pixels);
    __m256i rgba = _mm256_cvtepu8_epi32(grayscalePix);
    __m256i mulOperand = _mm256_set1_epi32(0x00010101);

    __m256i result = _mm256_mullo_epi32(rgba, mulOperand);

   _aligned_free(pixels);
    return 0;
}

Проблема в том, что после выполнения

__m256i rgba = mm256_cvtepu8_epi32(grayscalePix)

rgba содержит только первые четыре двойных слова. Последние четыре равны 0.

В руководстве для разработчиков Intel говорится:

VPMOVZXBD ymm1, xmm2 / m64
Ноль расширить 8 упакованных 8-битных целых чисел в младших 8 байтах xmm2 / m64 до 8 упакованных 32-битных целых чисел в ymm1.

Я не уверен, что это предполагаемое поведение, или я все еще что-то упускаю.

Спасибо.


person redeye    schedule 08.11.2016    source источник
comment
Ваш код выглядит правильно. Вы уверены, что не просто тестируете это неправильно? Или что компилятор не оптимизировал часть / все, потому что результаты не используются? В Godbolt мне пришлось использовать -O0, чтобы компилятор сохранял векторные операции. Даже -Og или -O1 оптимизировали все, кроме malloc / free. Попробуйте сохранить вектор в массиве uint32_t и распечатать его с помощью printf или чего-то подобного C ++.   -  person Peter Cordes    schedule 09.11.2016
comment
Оптимизатор не вызывает беспокойства, поскольку я тестировал его в режиме отладки, но вы все равно были правы :) Очевидно, отладчик VS не отображает правильно значения _m256i. Кажется, будто он обрезает их на _m128i границе. Кроме того, окно регистров тоже не сильно помогло. Все выглядит нормально после того, как я сохранил вектор в памяти и сделал printf, поэтому я думаю, спасибо, что в порядке :)   -  person redeye    schedule 10.11.2016
comment
Ого, когда нельзя доверять отладчику, дела обстоят ПЛОХО! Стало ли лучше с отладчиком, когда вы используете результат?   -  person Peter Cordes    schedule 10.11.2016
comment
Я больше не беспокоился о просмотре значений _m256i в отладчике. Когда мне нужно проверить свой код на правильность, я использую #ifdef _DEBUG код, где я просто копирую все в память и смотрю там.   -  person redeye    schedule 10.11.2016


Ответы (3)


Обновление: ответ @chtz - еще лучшая идея, используя дешевую широковещательную нагрузку 128-> 256 вместо vpmovzx для подачи vpshufb, экономя полосу пропускания порта в случайном порядке.


Начните с PMOVZX, как предлагает Марк.

Но после этого PSHUFB (_mm256_shuffle_epi8) будет намного быстрее, чем PMULLD, за исключением того, что он конкурирует за порт перемешивания с PMOVZX. (И он работает в полосе движения, так что вам все равно понадобится PMOVZX).

Так что если вас интересует только пропускная способность, а не время ожидания, тогда _mm256_mullo_epi32 хорошо. Но если задержка имеет значение или если ваша пропускная способность в любом случае ограничена чем-то другим, кроме двух инструкций перетасовки портов на вектор, то лучше всего подойдет PSHUFB для дублирования байтов в каждом пикселе.

На самом деле, даже для пропускной способности _mm256_mullo_epi32 плохо для HSW и BDW: это 2 мупа (задержка 10 с) для p0, так что это 2 мупа для одного порта.

В SKL это 2 мупа (задержка 10 с) для p01, поэтому он может поддерживать такую ​​же пропускную способность на такт, что и VPMOVZXBD. Но это лишний 1 муп, что увеличивает вероятность возникновения узкого места.

(VPSHUFB составляет 1 мкоп, задержка 1 с для порта 5 на всех процессорах Intel, поддерживающих AVX2.)

person Peter Cordes    schedule 09.11.2016

Вы можете загрузить упакованные байты в регистр call __m256i _mm256_cvtepu8_epi32 (__m128i a) для преобразования в 32-битные значения, затем умножьте на 0x00010101, чтобы воспроизвести шкалу серого в R, G и B.

person Mark Lakata    schedule 08.11.2016
comment
pshufb часто бывает лучше, чем умножение. Смотрите мой ответ. - person Peter Cordes; 09.11.2016

Вы можете преобразовать 16 пикселей с одним vbroadcasti128 и двумя vpshufb. Широковещательная передача не требует порта 5, если она загружается напрямую из памяти, поэтому случайное воспроизведение может полностью использовать этот порт (он все равно будет узким местом на этом порту или при сохранении обратно в память).

void gray2rgba(char const* input, char* output, size_t length)
{
    length &= size_t(-16); // lets just care about sizes multiples of 16 here ...

    __m256i shuflo = _mm256_setr_epi32(
        0x80000000, 0x80010101, 0x80020202, 0x80030303,
        0x80040404, 0x80050505, 0x80060606, 0x80070707
    );
    __m256i shufhi = _mm256_setr_epi32(
        0x80080808, 0x80090909, 0x800a0a0a, 0x800b0b0b,
        0x800c0c0c, 0x800d0d0d, 0x800e0e0e, 0x800f0f0f
    );

    for(size_t i=0; i<length; i+=16)
    {
        __m256i in = _mm256_broadcastsi128_si256(*reinterpret_cast<const __m128i*>(input+i));
        __m256i out0 = _mm256_shuffle_epi8(in, shuflo);
        __m256i out1 = _mm256_shuffle_epi8(in, shufhi);
        _mm256_storeu_si256(reinterpret_cast<__m256i*>(output+4*i),    out0);
        _mm256_storeu_si256(reinterpret_cast<__m256i*>(output+4*i+32), out1);
    }
}

Демо Godbolt: https://godbolt.org/z/dUx6GZ

person chtz    schedule 30.11.2019