(Примечание редактора: изначально этот вопрос был: Как можно получить доступ к члену m128i_i8 или членам в целом объекта __m128i? , пытаясь использовать специфичный для MSVC метод в определении GCC для {{X0 }}. Но это была проблема XY, и принятый ответ касается проблемы XY здесь. Другой ответ действительно отвечает на этот вопрос.)

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

Я продолжаю получать сообщение об ошибке «запрос члена 'm128i_i8' в '(мое имя переменной)', который не относится к типу класса 'wirelabel {aka __vector (2) long long int}'», которого я не понимаю, потому что я включил все правильные заголовки и распознает переменные __m128i.

Примечание 1: wirelabel - это typedef для __m128i, т.е. существует в заголовке

typedef __m128i wirelabel 

Note2: причина использования Note1 объясняется в следующем вопросе: tbb :: cache_aligned_allocator: получение "запроса для члена ... неклассового типа с __m128i. Ошибка пользователя или ошибка?

Примечание 3: я использую компилятор g ++

Примечание 4: этот следующий вопрос не отвечает на мой вопрос, но обсуждает связанную информацию Почему вам не следует напрямую обращаться к полям __m128i?

Я также знаю, что есть функция _mm_set_epi8, но она требует, чтобы вы установили сразу все 8-битные разделы, и в настоящее время это не вариант для меня.


На вопрос принятый ответ отвечает:

Изменить: меня попросили уточнить, почему я думаю, что мне нужно получить доступ к каждой из 16 8-битных частей объекта __m128i, и вот почему: у меня есть массив bool с размером 'n * 128' (n - size_t), и мне нужно сохранить их в массиве 'wirelabel' с размером 'n'.

Теперь, поскольку wirelabel - это просто псевдоним / typedef (поправьте меня, если есть разница) для __m128i, каждый из индексов 'n' из 128 bool может быть сохранен в массиве 'wirelabel'.

Однако, чтобы сделать это, я считаю, что необходимо преобразовать каждые 8 бит в его эквивалент со знаком и сохранить его в правильном 8-битном индексе в каждом указателе 'wirelabel' в массиве.

6
z.karl 13 Мар 2018 в 21:35

2 ответа

Лучший ответ

Значит, ваши исходные данные непрерывны? Вам следует использовать _mm_load_si128 вместо того, чтобы возиться со скалярными компонентами векторных типов.


Ваша настоящая проблема заключается в упаковке массива bool (1 байт на элемент в ABI, используемом g ++ на x86) в растровое изображение. Вы должны делать это с SIMD, а не со скалярным кодом, чтобы установить 1 бит или байт за раз.

pmovmskb (_mm_movemask_epi8) отлично подходит для извлечения одного бита на байт ввода. Вам просто нужно расположить так, чтобы нужный бит оказался в высоком.

Очевидным выбором был бы сдвиг, но инструкции векторного сдвига конкурируют за тот же порт выполнения, что и pmovmskb на Haswell (порт 0). (http://agner.org/optimize/). Вместо этого добавление 0x7F создаст 0x80 (набор старших битов) для ввода 1, но 0x7F (сброс старших битов) для ввода 0 . (И bool в x86-64 System V ABI должен храниться в памяти как целое число 0 или 1, а не просто 0 по сравнению с любым ненулевым значением).

Почему не pcmpeqb против _mm_set1_epi8(1)? Skylake работает pcmpeqb на портах 0/1, но paddb на всех трех векторных портах ALU (0/1/5). Однако очень часто используется pmovmskb в результате pcmpeqb/w/d/q.

#include <immintrin.h>
#include <stdint.h>

// n is the number of uint16_t dst elements
// We access n*16 bool elements from src.
void pack_bools(uint16_t *dst, const bool *src, size_t n)
{
     // you can later access dst with __m128i loads/stores

    __m128i carry_to_highbit = _mm_set1_epi8(0x7F);
    for (size_t i = 0 ; i < n ; i+=1) {
        __m128i boolvec = _mm_loadu_si128( (__m128i*)&src[i*16] );
        __m128i highbits = _mm_add_epi8(boolvec, carry_to_highbit);
        dst[i] = _mm_movemask_epi8(highbits);
    }
}

Поскольку при записи этого растрового изображения мы хотим использовать скалярные хранилища, мы хотим, чтобы dst находился в uint16_t по причинам строгого сглаживания. С AVX2 вам понадобится uint32_t. (Или если вы сделали combine = tmp1 << 16 | tmp для объединения двух результатов pmovmskb. Но, вероятно, не делайте этого.)

Это компилируется в такой цикл asm (с gcc7.3 -O3 в проводнике компилятора Godbolt)

.L3:
    movdqu  xmm0, XMMWORD PTR [rsi]
    add     rsi, 16
    add     rdi, 2
    paddb   xmm0, xmm1
    pmovmskb        eax, xmm0
    mov     WORD PTR [rdi-2], ax
    cmp     rdx, rsi
    jne     .L3

Так что это не замечательно (7 мопов fuse-domain -> узкое место переднего конца при 16 bool за ~ 1,75 такта). Clang разворачивается на 2 и должен обрабатывать 16 булов за 1,5 цикла.

Использование сдвига (pslld xmm0, 7) будет выполняться только на одной итерации за 2 цикла на Haswell, узким местом является порт 0.

4
Peter Cordes 14 Мар 2018 в 06:51

Создайте анонимное объединение, содержащее член _m128i и массив другого типа, члены которого вы хотите установить. Воспроизведение типов разрешено в C и поддерживается как расширение в g ++, clang ++ и MSVC. Если вы хотите установить отдельные биты, вы можете объявить другой член как struct битовых полей. Порядок битового поля определяется реализацией, но вы все равно используете встроенный Intel, поэтому он будет прямым порядком байтов.

0
Davislor 13 Мар 2018 в 23:21