2015-02-23 1 views
3

В рамках алгоритма сжатия, я искал оптимальный путь для достижения следующего:Оптимальное uint8_t растровый в 8 х 32-битный SIMD «BOOL» вектор

У меня есть простой растровое изображение в uint8_t. Например 01010011

То, что я хочу, это __m256i в виде: (0, MaxInt, 0, MaxInt, 0, 0, MaxInt, MaxInt)

Одним из способов достижения этой цели является перетасовки вектор 8 x maxint в вектор нулей. Но для этого сначала требуется, чтобы я расширил свой uint8_t до нужного растрового изображения в случайном порядке.

Мне интересно, есть ли лучший способ?

+0

Не могу придумать приятное решение. Вы можете создать таблицу со всеми предварительно вычисленными _m256i, индексированными uint8_t. Поскольку инструкции по смешиванию требуют немедленного, у вас может быть таблица смесей. AVX512 поможет с этим, я думаю. –

+1

В качестве альтернативы вы можете попробовать транслировать байт в каждую полосу, маскируя один важный бит в каждом из них и, наконец, сравнивая создание маски. – doynax

+1

@MarcGlisse lol, мы все ждем AVX512. Это буквально две инструкции. 'kmov + vmovdqa32' – Mysticial

ответ

2

Вот решение (PaulR улучшило мое решение, см. Конец моего ответа или его ответ) на основе вариации этого вопроса fastest-way-to-broadcast-32-bits-in-32-bytes.

__m256i t1 = _mm256_set1_epi8(x); 
__m256i t2 = _mm256_and_si256(t1, mask); 
__m256i t4 = _mm256_cmpeq_epi32(t2, _mm256_setzero_si256()); 
t4 = _mm256_xor_si256(t4, _mm256_set1_epi32(-1)); 

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

#include <x86intrin.h> 
#include <stdint.h> 
#include <stdio.h> 

int main(void) { 
    char mask[32] = { 
     0x01, 0x00, 0x00, 0x00, 
     0x02, 0x00, 0x00, 0x00, 
     0x04, 0x00, 0x00, 0x00, 
     0x08, 0x00, 0x00, 0x00, 
     0x10, 0x00, 0x00, 0x00, 
     0x20, 0x00, 0x00, 0x00, 
     0x40, 0x00, 0x00, 0x00, 
     0x80, 0x00, 0x00, 0x00, 
    }; 
    __m128i mask1 = _mm_loadu_si128((__m128i*)&mask[ 0]); 
    __m128i mask2 = _mm_loadu_si128((__m128i*)&mask[16]); 

    uint8_t x = 0x53; //0101 0011 
    __m128i t1 = _mm_set1_epi8(x); 
    __m128i t2 = _mm_and_si128(t1, mask1); 
    __m128i t3 = _mm_and_si128(t1, mask2); 
    __m128i t4 = _mm_cmpeq_epi32(t2,_mm_setzero_si128()); 
    __m128i t5 = _mm_cmpeq_epi32(t3,_mm_setzero_si128()); 
    t4 = _mm_xor_si128(t4, _mm_set1_epi32(-1)); 
    t5 = _mm_xor_si128(t5, _mm_set1_epi32(-1)); 

    int o1[4], o2[4]; 
    _mm_store_si128((__m128i*)o1, t4); 
    _mm_store_si128((__m128i*)o2, t5); 
    for(int i=0; i<4; i++) printf("%d \n", o1[i]); 
    for(int i=0; i<4; i++) printf("%d \n", o2[i]); 

} 

Edit:

PaulR улучшил мое решение

__m256i v = _mm256_set1_epi8(u); 
v = _mm256_and_si256(v, mask); 
v = _mm256_xor_si256(v, mask); 
return _mm256_cmpeq_epi32(v, _mm256_setzero_si256()); 

с маской определяется как

int mask[8] = { 
    0x01010101, 0x02020202, 0x04040404, 0x08080808, 
    0x10101010, 0x20202020, 0x40404040, 0x80808080, 
}; 

См свой ответ с тестирования производительности для получения более подробной информации.

+1

Вот результат: '-1 -1 0 0 -1 0 -1 0', если использовать целые числа без знака и меняет его, я думаю, это ожидаемый результат. – luk32

+0

@ Zboson: Я собрал тестовый упряжь с вашим кодом и моим сейчас - см. Мой отредактированный ответ для данных синхронизации (TL; DR: вы выиграли!). –

+0

@ luk32, порядок верен. Если вы печатаете регистр __m256i (0, -1,0, -1,0,0, -1,01) от наименее значимого бита до самого значимого, вы получаете -1 -1 0 0 -1 0 -1 0. –

4

Я думаю, что я бы, вероятно, пойти на подходе "грубой силы и невежества" изначально, может быть что-то вроде этого:

uint8_t u = 0x53; // 01010011 

const union { 
    uint32_t a[4]; 
    __m128i v; 
} kLUT[16] = { { { 0, 0, 0, 0 } }, 
       { { -1, 0, 0, 0 } }, 
       { { 0, -1, 0, 0 } }, 
       { { -1, -1, 0, 0 } }, 
       { { 0, 0, -1, 0 } }, 
       { { -1, 0, -1, 0 } }, 
       { { 0, -1, -1, 0 } }, 
       { { -1, -1, -1, 0 } }, 
       { { 0, 0, 0, -1 } }, 
       { { -1, 0, 0, -1 } }, 
       { { 0, -1, 0, -1 } }, 
       { { -1, -1, 0, -1 } }, 
       { { 0, 0, -1, -1 } }, 
       { { -1, 0, -1, -1 } }, 
       { { 0, -1, -1, -1 } }, 
       { { -1, -1, -1, -1 } } }; 
__m256i v = _mm256_set_m128i(kLUT[u >> 4].v, kLUT[u & 15].v); 

Использование clang -O3 это компилируется:

movl %ebx, %eax    ;; eax = ebx = u 
andl $15, %eax     ;; get low offset = (u & 15) * 16 
shlq $4, %rax 
leaq _main.kLUT(%rip), %rcx ;; rcx = kLUT 
vmovaps (%rax,%rcx), %xmm0  ;; load low half of ymm0 from kLUT 
andl $240, %ebx    ;; get high offset = (u >> 4) * 16 
vinsertf128 $1, (%rbx,%rcx), %ymm0, %ymm0 
            ;; load high half of ymm0 from kLUT 

FWIW я бросил вместе простой тест проводов для трех реализаций: (я) простой скаляр код эталонной реализации, (б) выше код, (III) реализация на основе @ ответ Zboson, в (я v) несколько улучшенная версия (iii) и (v) дальнейшее улучшение на (iv) с использованием предложения от @MarcGlisse. Я получил следующие результаты с 2.6GHz Haswell CPU (скомпилирован с clang -O3):

scalar code:         7.55336 ns/vector 
Paul R:          1.36016 ns/vector 
Z boson:          1.24863 ns/vector 
Z boson (improved):       1.07590 ns/vector 
Z boson (improved + @MarcGlisse suggestion): 1.08195 ns/vector 

Так @ решения Zboson (ов) выигрыш, примерно 10% - 20%, по-видимому, потому что они нужны только 1 нагрузку, по сравнению 2 для моего.

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


Немного улучшенная версия @ реализации Zboson в:

__m256i v = _mm256_set1_epi8(u); 
v = _mm256_and_si256(v, mask); 
v = _mm256_xor_si256(v, mask); 
return _mm256_cmpeq_epi32(v, _mm256_setzero_si256()); 


Далее улучшенный вариант @ реализации, включающего предложения Zboson в от @MarcGlisse:

__m256i v = _mm256_set1_epi8(u); 
v = _mm256_and_si256(v, mask); 
return _mm256_cmpeq_epi32(v, mask); 

(Обратите внимание, что mask должен содержать реплицируются 8 бит значения в каждом 32-битном элементе, то есть 0x01010101, 0x02020202, ..., 0x80808080)


+0

Да , вы, вероятно, правы - штраф за неуравновешенные нагрузки на Хасуэлл/Бродвелл довольно мал, но по-прежнему лучше поддерживать выравнивание по возможности. Я просто выбрал приведенный выше пример как отправную точку, а не фактическое решение, но я буду работать над его улучшением. –

+0

Я думаю, что вы правы в -1 - я мысленно перевел 'maxint' на' INT_MAX', но я вижу, что OP также упоминает логику SIMD. Я исправлю это. Предел выравнивания loadu равен 0 для выровненных данных, как вы говорите, и довольно мал для смещенных данных, для некоторого значения «довольно мало». –

+1

Я просто проверил, и кажется, что для инициализации моего массива лучшим вариантом будет 'const __m128i tab [] = {_ mm_set_epi32 (0,0,0,0), ...}' и надеюсь, что _mm_set_epi32 оценивается по время компиляции массиву не должно быть динамически инициализировано. Поэтому использование скалярного массива вместо того, что вы делаете, имеет смысл. –

1

Основываясь на всех ответах, я взломал решение, используя превосходную библиотеку Agner Fog (которая обрабатывает как решения AVX2, AVX, так и SSE с общей абстракцией). Я хотел бы рассказать об этом в качестве альтернативного ответа:

// Used to generate 32 bit vector bitmasks from 8 bit ints 
static const Vec8ui VecBitMask8(
     0x01010101 
    , 0x02020202 
    , 0x04040404 
    , 0x08080808 
    , 0x10101010 
    , 0x20202020 
    , 0x40404040 
    , 0x80808080); 

// As above, but for 64 bit vectors and 4 bit ints 
static const Vec4uq VecBitMask4(
     0x0101010101010101 
    , 0x0202020202020202 
    , 0x0404040404040404 
    , 0x0808080808080808); 

template <typename V> 
inline static Vec32c getBitmapMask(); 

template <> inline Vec32c getBitmapMask<Vec8ui>() {return VecBitMask8;}; 
template <> inline Vec32c getBitmapMask<Vec8i>() {return VecBitMask8;}; 
template <> inline Vec32c getBitmapMask<Vec4uq>() {return VecBitMask4;}; 
template <> inline Vec32c getBitmapMask<Vec4q>() {return VecBitMask4;}; 

// Returns a bool vector representing the bitmask passed. 
template <typename V> 
static inline V getBitmap(const uint8_t bitMask) { 
    Vec32c mask = getBitmapMask<V>(); 
    Vec32c v1(bitMask); 
    v1 = v1 & mask; 
    return ((V)v1 == (V)mask); 
} 
+0

Прохладный - я попытался включить это в тестовый жгут, но он бросает много ошибок компиляции с clang ++ - do Мне нужно сделать что-нибудь, кроме '#include ', чтобы сделать эту работу? –

+0

vectorclass.h должен это сделать. Однако вам нужно скомпилировать C++ 11. –

+0

Хмм - все еще получается много ошибок даже с '-std = C++ 11' - первый из них:' vectorf128.h: 215: 22: ошибка: двусмысленное преобразование для функционального стиля, отличное от 'const Vec4fb' до «Vec4ib» - я попробую другой компилятор, когда у меня появится шанс (возможно, завтра). –

Смежные вопросы