В различных контекстах, таких как биоинформатика, вычислений по целым числам байтов достаточно. Для лучшей производительности многие архитектуры процессоров предлагают наборы инструкций SIMD (например, MMX, SSE, AVX), которые разделяют регистры на байтовые, полуслововые и текстовые компоненты, а затем выполняют арифметические, логические и сдвиговые операции по отдельным компонентам.Эффективное параллельное байтовое вычисление предиката «меньше или равно» для целых чисел со знаком
Однако некоторые архитектуры не предлагают таких SIMD-инструкций, требующих их эмулирования, что часто требует значительного количества бит-скручивания. На данный момент я просматриваю сравнения SIMD и, в частности, параллельное сравнение подписанных байтовых размеров. У меня есть решение, которое, по моему мнению, достаточно эффективно с использованием портативного кода C (см. Функцию vsetles4
ниже). Он основан на наблюдении, сделанном в 2000 году Питером Монтгомери в newsgroup posting, что (A+B)/2 = (A AND B) + (A XOR B)/2
без переполнения промежуточных вычислений.
Можно ли ускорить этот конкретный код эмуляции (функция vsetles4
)? Для первого заказа будет разрешено любое решение с меньшим количеством основных операций. Я ищу решения в портативном ISO-C99 без использования специфических для машины характеристик. Большинство архитектур поддерживают ANDN
(a & ~ b), поэтому это можно считать доступным как единую операцию с точки зрения эффективности.
#include <stdint.h>
/*
vsetles4 treats its inputs as arrays of bytes each of which comprises
a signed integers in [-128,127]. Compute in byte-wise fashion, between
corresponding bytes of 'a' and 'b', the boolean predicate "less than
or equal" as a value in [0,1] into the corresponding byte of the result.
*/
/* reference implementation */
uint32_t vsetles4_ref (uint32_t a, uint32_t b)
{
uint8_t a0 = (uint8_t)((a >> 0) & 0xff);
uint8_t a1 = (uint8_t)((a >> 8) & 0xff);
uint8_t a2 = (uint8_t)((a >> 16) & 0xff);
uint8_t a3 = (uint8_t)((a >> 24) & 0xff);
uint8_t b0 = (uint8_t)((b >> 0) & 0xff);
uint8_t b1 = (uint8_t)((b >> 8) & 0xff);
uint8_t b2 = (uint8_t)((b >> 16) & 0xff);
uint8_t b3 = (uint8_t)((b >> 24) & 0xff);
int p0 = (int32_t)(int8_t)a0 <= (int32_t)(int8_t)b0;
int p1 = (int32_t)(int8_t)a1 <= (int32_t)(int8_t)b1;
int p2 = (int32_t)(int8_t)a2 <= (int32_t)(int8_t)b2;
int p3 = (int32_t)(int8_t)a3 <= (int32_t)(int8_t)b3;
return (((uint32_t)p3 << 24) | ((uint32_t)p2 << 16) |
((uint32_t)p1 << 8) | ((uint32_t)p0 << 0));
}
/* Optimized implementation:
a <= b; a - b <= 0; a + ~b + 1 <= 0; a + ~b < 0; (a + ~b)/2 < 0.
Compute avg(a,~b) without overflow, rounding towards -INF; then
lteq(a,b) = sign bit of result. In other words: compute 'lteq' as
(a & ~b) + arithmetic_right_shift (a^~b, 1) giving the desired
predicate in the MSB of each byte.
*/
uint32_t vsetles4 (uint32_t a, uint32_t b)
{
uint32_t m, s, t, nb;
nb = ~b; // ~b
s = a & nb; // a & ~b
t = a^nb; // a^~b
m = t & 0xfefefefe; // don't cross byte boundaries during shift
m = m >> 1; // logical portion of arithmetic right shift
s = s + m; // start (a & ~b) + arithmetic_right_shift (a^~b, 1)
s = s^t; // complete arithmetic right shift and addition
s = s & 0x80808080; // MSB of each byte now contains predicate
t = s >> 7; // result is byte-wise predicate in [0,1]
return t;
}
И ваш вопрос? – rici
@rici Существуют ли более быстрые решения? Я отредактирую, поэтому на самом деле вопрос содержит знак вопроса. – njuffa
Действительно ли вы рассчитали время на это быстрее, чем на массив 'int8_t' и применяете' <= '? (Не время по отношению к 'vsetles4_ref' - по времени относительно того, что вы не пытаетесь упаковать эти вещи в' uint32_t's вообще.) – user2357112