Ответ 1
Расширяя метод сравнения SSE с помощью dawg, вы можете комбинировать результаты сравнений с помощью вектора OR и перемещать маску результатов сравнения обратно в целое число для проверки на 0/ненулевое значение.
Кроме того, вы можете получить данные в векторах более эффективно (хотя он все еще довольно неуклюжий, чтобы получить много отдельных целых чисел в векторы, когда они живут в регистрах, а не сидеть в памяти).
Вам следует избегать хранилищ для переадресации магазинов, которые возникают в результате создания трех небольших магазинов и одной большой нагрузки.
///// UNTESTED ////////
#include <immintrin.h>
int eq3(int a, int b, int c, int d, int e, int f){
// Use _mm_set to let the compiler worry about getting integers into vectors
// Use -mtune=intel or gcc will make bad code, though :(
__m128i abcc = _mm_set_epi32(0,c,b,a); // args go from high to low position in the vector
// masking off the high bits of the result-mask to avoid false positives
// is cheaper than repeating c (to do the same compare twice)
__m128i dddd = _mm_set1_epi32(d);
__m128i eeee = _mm_set1_epi32(e);
dddd = _mm_cmpeq_epi32(dddd, abcc);
eeee = _mm_cmpeq_epi32(eeee, abcc); // per element: 0(unequal) or -1(equal)
__m128i combined = _mm_or_si128(dddd, eeee);
__m128i ffff = _mm_set1_epi32(f);
ffff = _mm_cmpeq_epi32(ffff, abcc);
combined = _mm_or_si128(combined, ffff);
// results of all the compares are ORed together. All zero only if there were no hits
unsigned equal_mask = _mm_movemask_epi8(combined);
equal_mask &= 0x0fff; // the high 32b element could have false positives
return equal_mask;
// return !!equal_mask if you want to force it to 0 or 1
// the mask tells you whether it was a, b, or c that had a hit
// movmskps would return a mask of just 4 bits, one for each 32b element, but might have a bypass delay on Nehalem.
// actually, pmovmskb apparently runs in the float domain on Nehalem anyway, according to Agner Fog table >.<
}
Это компилируется для довольно приятного asm, довольно похожего между clang и gcc, но clang -fverbose-asm
помещает приятные комментарии в тасовку. Только 19 инструкций, включая ret
, с приличным количеством parallelism от отдельных цепочек зависимостей. С помощью -msse4.1
или -mavx
он сохраняет еще пару инструкций. (Но, вероятно, не работает быстрее)
С clang, версия dawg примерно в два раза больше. С gcc происходит что-то плохое, и это ужасно (более 80 инструкций. Похож на gcc-оптимизацию, так как выглядит хуже, чем просто прямой перевод источника). Даже версия clang тратит так много времени на получение данных в/из векторных regs, что, возможно, было бы быстрее просто делать сравнения без ветвей и OR значения истины вместе.
Это компилируется для достойного кода:
// 8bit variable doesn't help gcc avoid partial-register stalls even with -mtune=core2 :/
int eq3_scalar(int a, int b, int c, int d, int e, int f){
char retval = (a == d) | (a == e) | (a == f)
| (b == d) | (b == e) | (b == f)
| (c == d) | (c == e) | (c == f);
return retval;
}
Играйте с тем, как получить данные от вызывающего в векторные рег.
Если группы из трех поступают из памяти, тогда проблема. прохождение указателей, так что векторная нагрузка может получить их из их исходного местоположения. Переход через целые регистры на пути к векторам отстой (более высокая латентность, больше uops), но если ваши данные уже живут в regs, это потеря, чтобы делать целые магазины, а затем векторные нагрузки. gcc невнимателен и следует рекомендациям руководства по оптимизации AMD, чтобы отскочить через память, хотя Agner Fog говорит, что он обнаружил, что это не стоит даже на процессорах AMD. Это определенно хуже для Intel, и, по-видимому, мышь или, возможно, еще хуже на AMD, поэтому это определенно неправильный выбор для -mtune=generic
. В любом случае...
Также можно сделать 8 наших 9 сравнений только с двумя сравнениями упакованных векторов.
9-й может быть выполнен с использованием целочисленного сравнения и имеет значение истинности ORed с векторным результатом. На некоторых процессорах (особенно AMD и, возможно, Intel Haswell и более поздних версиях), не перенося ни одно из 6 целых чисел в векторные regs, все могут быть победой. Смешивание трех целых нестационарных сравнений с векторными перетасовками/сравнениями будет чередовать их красиво.
Эти векторные сравнения могут быть установлены с помощью shufps
для целочисленных данных (поскольку он может объединять данные из двух исходных регистров). Это хорошо для большинства процессоров, но требует много раздражающего кастинга при использовании встроенных средств вместо фактического asm. Даже если есть байпасная задержка, это не плохой компромисс против чего-то вроде punpckldq, а затем pshufd.
aabb ccab
==== ====
dede deff
c==f
с asm что-то вроде:
#### untested
# pretend a is in eax, and so on
movd xmm0, eax
movd xmm1, ebx
movd xmm2, ecx
shl rdx, 32
#mov edi, edi # zero the upper 32 of rdi if needed, or use shld instead of OR if you don't care about AMD CPUs
or rdx, rdi # de in an integer register.
movq xmm3, rdx # de, aka (d<<32)|e
# in 32bit code, use a vector shuffle of some sort to do this in a vector reg, or:
#pinsrd xmm3, edi, 1 # SSE4.1, and 2 uops (same as movd+shuffle)
#movd xmm4, edi # e
movd xmm5, esi # f
shufps xmm0, xmm1, 0 # xmm0=aabb (low dword = a; my notation is backwards from left/right vector-shift perspective)
shufps xmm5, xmm3, 0b01000000 # xmm5 = ffde
punpcklqdq xmm3, xmm3 # broadcast: xmm3=dede
pcmpeqd xmm3, xmm0 # xmm3: aabb == dede
# spread these instructions out between vector instructions, if you aren't branching
xor edx,edx
cmp esi, ecx # c == f
#je .found_match # if there one of the 9 that true more often, make it this one. Branch mispredicts suck, though
sete dl
shufps xmm0, xmm2, 0b00001000 # xmm0 = abcc
pcmpeqd xmm0, xmm5 # abcc == ffde
por xmm0, xmm3
pmovmskb eax, xmm0 # will have bits set if cmpeq found any equal elements
or eax, edx # combine vector and scalar compares
jnz .found_match
# or record the result instead of branching on it
setnz dl
Это также 19 инструкций (не считая окончательного jcc/setcc), но один из них - идиома xor-zeroing, а также другие простые целые инструкции. (Более короткое кодирование, некоторые могут работать на port6 на Haswell +, которые не могут обрабатывать векторные инструкции). Возможно, существует более длинная цепочка dep из-за цепи тасований, которая строит abcc.