Как лучше эмулировать логическое значение _mm_slli_si128 (128-битный битовый сдвиг), а не _mm_bslli_si128

Просматривая руководство по внутренним компонентам, я увидел эту инструкцию. Глядя на шаблон именования, смысл должен быть ясен: Сдвиг 128-битного регистра влево на фиксированное число бит, но это не так. На самом деле он сдвигается на фиксированное число байтов, что делает его точно таким же, как _mm_bslli_si128.

  • Это упущение? Разве он не должен сдвигаться на биты вроде _mm_slli_epi32 или _mm_slli_epi64?
  • Если нет, в какой ситуации я должен использовать это вместо _mm_bslli_si128?
  • Есть ли инструкция по сборке, которая делает это правильно?
  • Каков наилучший способ эмулировать это с меньшими сдвигами?

person lennartVH01    schedule 07.02.2021    source источник
comment
Мое сравнение старой и новой документации предполагает, что инструкция (V)PSLLDQ, выполняющая побайтовый сдвиг, сначала была раскрыта через встроенную функцию с непоследовательным названием (с использованием sli, ошибочно предполагающую сдвиг битов), в то время как встроенная функция с постоянным именем ( использование bslli, правильно предполагающее смещение байтов) не было добавлено намного позже, и в этот момент было невозможно удалить старую встроенную функцию, не нарушая существующий код. Таким образом, для нового кода предпочтительнее использовать вариант bslli в качестве встроенных функций с более подходящим названием.   -  person njuffa    schedule 07.02.2021
comment
Я как бы подозревал, что это исторический артефакт, но ваш комментарий подтверждает, что   -  person lennartVH01    schedule 07.02.2021


Ответы (2)


1 это не оплошность. Эта инструкция действительно сдвигается на байты, т.е. кратные 8 битам.

2 не имеет значения, _mm_slli_si128 и _mm_bslli_si128 эквивалентны, обе компилируются в инструкцию pslldq SSE2.

Что касается эмуляции, я бы сделал так, если у вас C++/17. Если вы пишете C++/14, замените if constexpr обычным if, а также добавьте сообщение в файл static_assert.

template<int i>
inline __m128i shiftLeftBits( __m128i vec )
{
    static_assert( i >= 0 && i < 128 );
    // Handle couple trivial cases
    if constexpr( 0 == i )
        return vec;
    if constexpr( 0 == ( i % 8 ) )
        return _mm_slli_si128( vec, i / 8 );

    if constexpr( i > 64 )
    {
        // Shifting by more than 8 bytes, the lowest half will be all zeros
        vec = _mm_slli_si128( vec, 8 );
        return _mm_slli_epi64( vec, i - 64 );
    }
    else
    {
        // Shifting by less than 8 bytes.
        // Need to propagate a few bits across 64-bit lanes.
        __m128i low = _mm_slli_si128( vec, 8 );
        __m128i high = _mm_slli_epi64( vec, i );
        low = _mm_srli_epi64( low, 64 - i );
        return _mm_or_si128( low, high );
    }
}
person Soonts    schedule 07.02.2021
comment
Я бы порекомендовал _mm_bslli_si128 - новое имя более четко подразумевает, что это сдвиг байта. Обратите внимание, что вам действительно не нужно if constexpr. Возможно, это поможет вашему компилятору сделать более эффективный код в режиме отладки, если в этом случае ваш компилятор не удалит if(false) блоков (кашель MSVC), но это все. С i в качестве параметра шаблона это определенно константа времени компиляции, и даже MSVC удаляет мертвый код с включенной оптимизацией. - person Peter Cordes; 07.02.2021
comment
@PeterCordes Согласно github.com, _mm_slli_si128 примерно в 13 раз популярнее, 109 тыс. результатов против 8 тыс. Я писал SSE/AVX SIMD в течение многих лет, но до сегодняшнего дня я даже не подозревал, что _mm_bslli_si128 вообще существует. - person Soonts; 07.02.2021
comment
@PeterCordes Что касается C ++, шаблоны необходимы, IMO. В этом отношении исходные встроенные функции C не работают. Их прототип позволяет передавать значения, отличные от constexpr, такие как результат rand(), который затем не компилируется с загадочной ошибкой, такой как «ожидаемое константное выражение». Если вы пишете функцию, принимающую аргумент int, это может произойти или не произойти в зависимости от того, встроена функция или нет. Не лучший UX. Шаблоны C++ с целочисленными аргументами и if constexpr прекрасно справляются с этим, язык требует, чтобы вы использовали аргумент constexpr, и если вы это сделаете, код гарантированно скомпилируется. - person Soonts; 07.02.2021
comment
Да, конечно, нужны шаблоны, чтобы i можно было скомпилировать в немедленную. Именно поэтому if constexpr не требуется. i остается постоянным, потому что вы не изменяете его ни в одном из ветвей. - person Peter Cordes; 07.02.2021
comment
Кроме того, неудивительно, что в существующих кодовых базах гораздо чаще используется старое менее понятное имя _mm_slli_si128, чем более новое более понятное имя _mm_bslli_si128, которое поддерживается компиляторами только последние ~5 лет. (Думаю, добавлено одновременно с новыми встроенными функциями для AVX512, см. мой ответ, когда я его опубликую.) Intel представила новое имя, чтобы мы могли начать его использовать, потому что старое имя было отстойным (IMO). - person Peter Cordes; 07.02.2021
comment
@PeterCordes Обновлено, если OP использует C++/14. Обычно мне нравятся функции компилятора, которые помогают мне писать правильный код; if constexpr — один из них, IMO — передайте не-constexpr, и он не скомпилируется. - person Soonts; 07.02.2021
comment
i — это параметр шаблона. Вы уже не можете сделать shiftLeftBits<x> для непостоянного x. Кроме того, даже если вы поместите код в функцию с простым аргументом int вместо шаблона, _mm_slli_si128( vec, i / 8 ); надежно не скомпилируется, если распространение констант не сможет превратить его в константу времени компиляции. (И некоторые компиляторы не будут обрабатывать _mm_slli_epi64( vec, i ); путем компиляции в movd / psllq xmm, xmm, но другие, как ни странно, будут.) В любом случае, if constexpr не сильно ухудшает читабельность, если вы к этому привыкли, просто хотел отметить, что это не помогает все, кроме отладки MSVC - person Peter Cordes; 07.02.2021
comment
Опубликовал ответ, в том числе, какие версии компилятора поддерживают более новый встроенный bslli. - person Peter Cordes; 07.02.2021

TL:DR: Это синонимы; имя bslli более новое, введенное примерно в то же время, что и новые встроенные функции AVX-512 (где-то до 2015 года, долго после того, как SSE2 _mm_slli_si128 стал широко использоваться). Я нахожу это более ясным и рекомендую его для новой разработки.


SSE/AVX2/AVX-512 не имеют битовых сдвигов с размерами элементов более 64. (Или любая другая операция битовой детализации, такая как add, за исключением чисто вертикального побитового логического материала, который на самом деле 128 полностью отдельных операций, а не одна большая широкая , Или для целей маскировки AVX-512 и широковещательной загрузки, может быть в виде фрагментов dword или qword, таких как _mm512_xor_epi32 / vpxord )

Вы должны каким-то образом эмулировать это, что может быть довольно эффективным для подсчета констант времени компиляции, поэтому вы можете выбирать между стратегиями в соответствии с c >= 64, с особыми случаями для c%8, сводящимися к байтовому сдвигу. Существующие вопросы и ответы SO охватывают это или см. ответ @Soonts на этот вопрос.

Количество переменных времени выполнения было бы отстойным; вам придется разветвляться или делать оба пути и смешивать, в отличие от битовых сдвигов элементов, где _mm_sll_epi64(v, _mm_cvtsi32_si128(i)) может компилироваться в movd/psllq xmm, xmm. К сожалению, аппаратных версий инструкций байтового тасования/сдвига с переменным числом переменных не существует, только для версий с битовым сдвигом.


bslli / bsrli — это новые, более четкие внутренние имена для тех же инструкций asm.

В b имена поддерживаются в текущей версии всех 4 основных компиляторов для x86 (Godbolt ), и я бы порекомендовал их для новой разработки, если только вам не нужна обратная совместимость с корявыми старыми компиляторами или по какой-то причине вам нравится старое имя, которое не позволяет отличить его от других операций. (например, знакомство; если вы не хотите, чтобы людям приходилось искать это новомодное имя в руководстве.)

  • gcc с 4.8
  • лязг с 3.7
  • ICC, начиная с ICC13 или ранее, у Godbolt нет более старых
  • MSVC начиная с 19.14 или ранее, у Godbolt нет более старых

Если вы посмотрите руководство по внутренним компонентам, _mm_slli_si128 указан как встроенный параметр для PSLLDQ, который представляет собой сдвиг байта. Это не ошибка, а просто шутка Intel или какой-то другой процесс, который они использовали для выбора имен для встроенных функций еще во времена SSE2. (В информатике есть только две сложные проблемы: инвалидация кеша и присвоение имен).

В мнемонике ассемблера также используется тот же шаблон, при котором перетасовка байтов не выглядит иначе, чем сдвиг битов. psllw xmm, 1 / pslld / psllq / pslldq. Опять же, вам просто нужно знать, что 128-битный размер особенный и должен быть перетасовкой байтов, а не битовым сдвигом, потому что в x86 такого нет. (Или вы должны проверить руководство.)

Запись руководства asm для pslldq, в свою очередь, перечисляет встроенные функции для его форм, что интересно, только с использованием b имя для версии __m512i AVX-512BW. Когда SSE2 и AVX2 были новыми, я думаю, что _mm_slli_si128 и _mm256_slli_si256 были единственными доступными именами. Конечно, он более поздний, чем встроенные функции SSE2.

(Обратите внимание, что версии si256 и si512 — это всего лишь 2 или 4 копии 16-байтовой операции, не сдвигающие байты по 128-битным дорожкам; то, о чем просили несколько других вопросов и ответов. Это часто делает AVX2 подобные варианты перетасовок и palignr намного менее полезны, чем они могли бы быть в противном случае: либо вообще не стоит использовать, либо нужны дополнительные перетасовки вдобавок к этому.)

Я думаю, что это новое имя bslli было введено, когда AVX-512 был новым. Примерно в то же время Intel изобрела несколько новых имен для других встроенных функций, а встроенные функции загрузки/сохранения AVX-512 принимают void* вместо __m512i*, что является значительным улучшением количества шума в коде, особенно для C, где разрешено неявное преобразование в void*. (Создание смещенного __m512i* на самом деле не является проблемой. в терминах C, но вы не могли нормально разопределить его, поэтому это выглядит странно.) Так что тогда велась работа по очистке внутреннего именования, и я думаю, что это было частью этого.

(AVX-512 также дал Intel возможность ввести некоторые довольно плохие имена, такие как _mm_loadu_epi32(const void*) — вы могли бы догадаться, что это строго безопасный способ выполнения 32-битной загрузки movd, верно? Нет, к сожалению, это встроенный для vmovdqu32 xmm, [mem] без маскировки. Это просто _mm_loadu_si128 с другим типом C для указателя arg. Это сделано для согласованности с шаблоном именования для _mm_maskz_loadu_epi32. Было бы неплохо иметь встроенные функции void* загрузки/сохранения для __m128i и __m256i, но если они вводят в заблуждение такие имена (особенно когда вы не используете версии mask/maskz в соседнем коде), я просто буду придерживаться этих громоздких приведений _mm256_loadu_si256( (const __m256i*)(arr + i) ) для старой встроенной функции, потому что я люблю набирать 256 три раза. ›.‹

Я бы хотел, чтобы asm был более удобным в сопровождении (или чтобы встроенные функции просто использовали мнемонику asm), потому что он намного более лаконичен; Intel, как правило, хорошо называет свои мнемоники.


Это несколько, но не полностью помогает отметить разницу между epi16/32/64 и si128: EPI = расширенное (SSE вместо MMX) упакованное целое число. (Упакованный подразумевает несколько элементов SIMD). si128 означает целый 128-битный целочисленный вектор.

Из имени невозможно сделать вывод, что вы не делаете то же самое, просто делая одно 128-битное целое число вместо упакованных элементов. Вам просто нужно знать, что нет вещей с битовой гранулярностью, которые когда-либо пересекают 64-битные границы, только перетасовки SIMD (которые работают с байтами). Это позволяет избежать комбинаторного взрыва, связанного с созданием действительно широкого бочкообразного переключателя, или распространения переноса на такое большое расстояние для 128-битного сложения или чего-либо еще.

person Peter Cordes    schedule 07.02.2021