Вычислите абсолютную разницу между целыми числами без знака, используя SSE

Есть ли в C метод без ветвления для вычисления абсолютной разницы между двумя целыми числами без знака? Например, учитывая переменные a и b, мне нужно значение 2 для случаев, когда a=3, b=5 или b=3, a=5. В идеале я также хотел бы иметь возможность векторизовать вычисления с использованием регистров SSE.


person Jack Nock    schedule 01.08.2010    source источник


Ответы (8)


Есть несколько способов сделать это, я упомяну только один:

SSE4

  • Используйте PMINUD и PMAXUD, чтобы разделить большее значение в регистре №1 и меньшее значение в регистре №2.
  • Вычтите их.

MMX/SSE2

  • Переверните бит знака двух значений, потому что следующая инструкция принимает только сравнение целых чисел со знаком.
  • PCMPGTD. Используйте этот результат в качестве маски.
  • Вычислить результаты как (a-b), так и (b-a)
  • Используйте POR ( PAND ( mask, a-b ), PANDN ( mask, b-a ) ), чтобы выбрать правильное значение абсолютной разницы.
person rwong    schedule 01.08.2010

На сайте tommesani.com одним из решений этой проблемы является двукратное использование насыщающего беззнакового вычитания.

Поскольку вычитание насыщения никогда не опускается ниже 0, вы вычисляете: r1 = (a-b).saturating r2 = (b-a).saturating

Если a больше b, то r1 будет содержать ответ, а r2 будет равно 0, и наоборот, если b>a. Объединение двух частичных результатов по операции ИЛИ даст желаемый результат.

Согласно руководство пользователя VTUNE, PSUBUSB/PSUBUSW доступны для 128-битных регистров, поэтому таким образом вы сможете получить тонну распараллеливания.

person Justin W    schedule 21.07.2011
comment
sub с беззнаковой насыщенностью, по-видимому, доступен только для слов или байтов, но, к счастью, это то, что я искал. Этот ответ немного лучше, чем ответ с наибольшим количеством голосов, использующий SSE4.1 PMINU/PMAXU/PSUB, потому что POR может работать на большем количестве портов, чем PSUB на некоторых процессорах (например, Intel Haswell). - person Peter Cordes; 24.12.2015

max(i,j) - min(i,j)
(i>j)*(i-j) + (j>i)*(j-i)

вы, конечно, можете использовать регистры SSE, но компилятор все равно может сделать это за вас

person Anycorn    schedule 01.08.2010

SSE2:

Кажется, примерно такая же скорость, как и у второй функции Ферноста. Иногда GCC планирует выполнение полного цикла быстрее, иногда немного медленнее.

__m128i big = _mm_set_epi32( INT_MIN, INT_MIN, INT_MIN, INT_MIN );

a = _mm_add_epi32( a, big ); // re-center the variables: send 0 to INT_MIN,
b = _mm_add_epi32( b, big ); // INT_MAX to -1, etc.
__m128i diff = _mm_sub_epi32( a, b ); // get signed difference
__m128i mask = _mm_cmpgt_epi32( b, a ); // mask: need to negate difference?
mask = _mm_andnot_si128( big, mask ); // mask = 0x7ffff... if negating
diff = _mm_xor_si128( diff, mask ); // 1's complement except MSB
diff = _mm_sub_epi32( diff, mask ); // add 1 and restore MSB

SSSE3:

Всегда немного быстрее, чем предыдущий. Существует множество вариаций в зависимости от того, как объявляются вещи вне цикла. (Например, преобразование a и b в volatile ускорит работу! Похоже, что это случайный эффект при планировании.) Но это постоянно ускоряет цикл или около того.

__m128i big = _mm_set_epi32( INT_MIN, INT_MIN, INT_MIN, INT_MIN );

a = _mm_add_epi32( a, big ); // re-center the variables: send 0 to INT_MIN,
b = _mm_add_epi32( b, big ); // INT_MAX to -1, etc.
__m128i diff = _mm_sub_epi32( b, a ); // get reverse signed difference
__m128i mask = _mm_cmpgt_epi32( b, a ); // mask: need to negate difference?
mask = _mm_xor_si128( mask, big ); // mask cannot be 0 for PSIGND insn
diff = _mm_sign_epi32( diff, mask ); // negate diff if needed

SSE4 (спасибо, Рвонг):

Не могу проверить это.

__m128i diff = _mm_sub_epi32( _mm_max_epu32( a, b ), _mm_min_epu32( a, b ) );
person Potatoswatter    schedule 20.08.2010

вычислить разницу и вернуть абсолютное значение

__m128i diff = _mm_sub_epi32(a, b);  
__m128i mask = _mm_xor_si128(diff, a);
mask = _mm_xor_si128(mask, b);
mask = _mm_srai_epi32(mask, 31);
diff = _mm_xor_si128(diff, mask);  
mask = _mm_srli_epi32(mask, 31);  
diff = _mm_add_epi32(diff, mask);  

Это требует на одну операцию меньше, чем при использовании операции сравнения со знаком, и создает меньшее давление на регистр.

То же количество регистров, что и раньше, еще 2 операции, улучшенное ветвление и слияние цепочек зависимостей, объединение инструкций для декодирования мопов и использование отдельных единиц. Хотя для этого требуется загрузка, которая может быть вне кеша. У меня нет идей после этого.

__m128i mask, diff;
diff = _mm_set1_epi32(-1<<31); // dependency branch after
a = _mm_add_epi32(a, diff); // arithmetic sign flip
b = _mm_xor_si128(b, diff); // bitwise sign flip parallel with 'add' unit
diff = _mm_xor_si128(a, b); // reduce uops, instruction already decoded
mask = _mm_cmpgt_epi32(b, a); // parallel with xor
mask = _mm_and_si128(mask, diff); // dependency merge, branch after
a = _mm_xor_si128(a, mask); // if 2 'bit' units in CPU, parallel with next
b = _mm_xor_si128(b, mask); // reduce uops, instruction already decoded
diff = _mm_sub_epi32(a, b); // result

После хронометража каждой версии с 2 миллионами итераций на Core2Duo различия неизмеримы. Так что выбирайте то, что легче понять.

person Phernost    schedule 19.08.2010
comment
sum должно быть diff? Ба, теперь, когда я внимательно прочитал ваше, оно очень похоже на мое. Но более умно, приятно использовать разницу со знаком в качестве сравнения со знаком. Однако сравнение с нулем, как правило, легче, чем сдвиг вправо. - person Potatoswatter; 20.08.2010
comment
На самом деле мы оба ошиблись: в первой функции нужна функция консенсуса с тремя входами, а не трехстороннее XOR. - person Potatoswatter; 22.08.2010

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

Я не просмотрел все ответы sse, но, возможно, некоторые из приведенных ниже представлены в векторном коде; конечно, все нижеперечисленное можно векторизовать (если у вас есть беззнаковое сравнение для начала или подделка его, сначала переключив msb). Я подумал, что было бы полезно получить несколько практических скалярных ответов на вопрос.

unsigned udiff( unsigned a, unsigned b )
{
      unsigned result = a-b;   // ok if a<b;
      if(a <b ) result = -result; 
      return result;
}
unsigned udiff( unsigned a, unsigned b )
{
      unsigned n =(a<b)? (unsigned)-1 : 0u;
      unsigned result = a-b;
      return (result^n)-n; // 'result' if n = 0; '-result' if n = 0xFFFFFFFF
}


unsigned udiff( unsigned a, unsigned b )
{
      unsigned axb = a^b;
      if( a < b )  axb = 0;
      return (axb^b) - (axb^a);  // a-b, or b-a
}

Это будет работать на x86_64 (или где-либо, где 64-битные временные файлы в основном бесплатны)

unsigned udiff( unsigned a, unsigned b )
{
      unsigned n= (unsigned)( 
         (long long)((unsigned long long)a - (unsigned long long)b)>>32 
                      ); // same n as 2nd example
      unsigned result = a-b;
      return (result^n)-n; // 'result' if n = 0; '-result' if n = 0xFFFFFFFF
}
person greggo    schedule 21.03.2014

Попробуйте это (предполагается 2-е дополнение, что нормально, судя по тому факту, что вы запрашиваете SSE):

int d = a-b;
int ad = ((d >> 30) | 1) * d;

Объяснение: знаковый бит (бит 31) распространяется вниз до 1-го бита. | 1 часть гарантирует, что множитель равен 1 или -1. Умножения выполняются быстро на современных процессорах.

person zvrba    schedule 01.08.2010
comment
Но бит знака a-b не указывает на то, что a ‹ b. рассмотрим a=0xF0000000 и b = 1. Если бы это было так, вы могли бы использовать abs(a-b). - person greggo; 21.03.2014

Эм... это довольно легко...

int diff = abs( a - b );

Легко векторизуемый (с использованием SSE3 как):

__m128i sseDiff = _mm_abs_epi32( _mm_sub_epi32( a, b ) );

a и b — целые числа без знака. Рассмотрим a=0 и b=0xffffffff. Правильная абсолютная разница — 0xffffffff, но ваше решение даст 1.

person Goz    schedule 11.09.2010
comment
Как объяснило странное редактирование, это неправильно. Другой пример для 8-битных целых чисел без знака: для 4 - 255 правильная абсолютная разность равна 251. Но трактуя это как знаковое -5 и взяв абсолютное значение, вы получите 5, что является тем же ответом, что и для 255 - 250. - person Peter Cordes; 27.10.2017