Мне нужен способ сравнить значения типа __m128i
в C++ для общего порядка между любыми значениями типа __m128i
. Тип порядка не имеет значения, если он устанавливает общий порядок между всеми значениями типа __m128i
. Следовательно, сравнение может быть меньше, чем между 128-битными целыми числами или чем-то еще, если оно обеспечивает общий порядок.
Я попытался использовать оператор <
, но он не вернул bool
, а вместо этого, кажется, сравнивает векторные компоненты __m128i
(т.е. SIMD):
#include <emmintrin.h>
inline bool isLessThan(__m128i a, __m128i b) noexcept {
// error: cannot convert '__vector(2) long int' to 'bool' in return
return a < b;
}
Другая возможность — использовать memcmp
/strcmp
или что-то подобное, но это, скорее всего, не будет оптимальным. Ориентируясь на современные процессоры Intel x86-64 по крайней мере с SSE4.2 и AVX2, есть ли какие-либо встроенные функции/инструкции, которые я мог бы использовать для таких сравнений? Как это сделать?
PS: Подобные вопросы были заданы для проверки равенства, но не для заказа:
Ну вот.
inline bool isLessThan( __m128i a, __m128i b )
{
/* Compare 8-bit lanes for ( a < b ), store the bits in the low 16 bits of the
scalar value: */
const int less = _mm_movemask_epi8( _mm_cmplt_epi8( a, b ) );
/* Compare 8-bit lanes for ( a > b ), store the bits in the low 16 bits of the
scalar value: */
const int greater = _mm_movemask_epi8( _mm_cmpgt_epi8( a, b ) );
/* It's counter-intuitive, but this scalar comparison does the right thing.
Essentially, integer comparison searches for the most significant bit that
differs... */
return less > greater;
}
Порядок не идеален, потому что pcmpgtb
обрабатывает эти байты как целые числа со знаком, но вы сказали, что это не важно для вашего варианта использования.
Обновлять: вот немного более медленная версия с порядком сортировки uint128_t
.
// True if a < b, for unsigned 128 bit integers
inline bool cmplt_u128( __m128i a, __m128i b )
{
// Flip the sign bits in both arguments.
// Transforms 0 into -128 = minimum for signed bytes,
// 0xFF into +127 = maximum for signed bytes
const __m128i signBits = _mm_set1_epi8( (char)0x80 );
a = _mm_xor_si128( a, signBits );
b = _mm_xor_si128( b, signBits );
// Now the signed byte comparisons will give the correct order
const int less = _mm_movemask_epi8( _mm_cmplt_epi8( a, b ) );
const int greater = _mm_movemask_epi8( _mm_cmpgt_epi8( a, b ) );
return less > greater;
}
Мы строим беззнаковые сравнения из знаковых, сдвигая диапазон беззнаковых входных данных на знаковые (переворачивая старший бит = вычитание 128).
Я пробовал это, но он генерирует ужасную сборку, не так ли? Или этого следует ожидать?
Вы можете использовать -Gv
с MSVC, поэтому он использует __vectorcall
, поэтому ваш исходный код по-прежнему компилируется с помощью GCC. (И не будет переопределять соглашение о вызовах clang x86-64 System V.)
Можем ли мы сделать что-нибудь полезное с помощью pcmpgtq
, а затем перетасовать + замаскировать младший элемент старшим элементом? Это, вероятно, дороже; pmovmskb
или movmskpd
дешевые, в худшем случае 2 мкп на бульдозерном семействе. (Высокая задержка в семействе Bulldozer для xmm->int одинаково вредна в любом случае, и оба могут происходить параллельно.)
Простым решением (с помощью gcc/clang) было бы преобразовать в
__int128
и сравнить с<
: godbolt.org/z/Kh0Yd5. Не обязательно самый эффективный (но это также зависит от того, обычно ли ваши входные данные поступают из регистров или памяти)