Проверил несколько вариантов (Haswell, Win7x64), посчитал latency в тактах (и сравнил с IACA): с условными переходами, с условными пересылками
cmovcc, с суммированием условий
setcc, с AVX2 (в AVX512 код будет проще). Табличный вариант проверять поленился.
(Исходный код)
Код:
Cmp1: mov EAX,0 ;0156 1/0.25
cmp ECX,N0 ;0156 1/0.25
jb .exit ;06 1-0.5/
mov EAX,1 ;0156 1/0.25
cmp ECX,N1 ;0156 1/0.25
jb .exit ;06 1-0.5/
...
mov EAX,10 ;0156 1/0.25
cmp ECX,N10 ;0156 1/0.25
jb .exit ;06 1-0.5/
mov EAX,11 ;0156 1/0.25
.exit: ret
Cmp2: mov EAX,11 ;0156 1/0.25
mov EDX,10 ;0156 1/0.25
cmp ECX,N10 ;0156 1/0.25
cmovb EAX,EDX ;0156*2 2/0.5
mov EDX,9 ;0156 1/0.25
cmp ECX,N9 ;0156 1/0.25
cmovb EAX,EDX ;0156*2 2/0.5
...
mov EDX,0 ;0156 1/0.25
cmp ECX,N0 ;0156 1/0.25
cmovb EAX,EDX ;0156*2 2/0.5
ret
Cmp3: xor EDX,EDX ;0156 0/0.25
xor EAX,EAX ;0156 0/0.25
cmp ECX,N0 ;0156 1/0.25
setge AL ;06 1/0.5
cmp ECX,N1 ;0156 1/0.25
setge DL ;06 1/0.5
add AL,DL ;0156 1/0.25
...
cmp ECX,N10 ;0156 1/0.25
setge DL ;06 1/0.5
add AL,DL ;0156 1/0.25
ret
Cmp4: vmovdqu ymm1,[const] ;23 3/0.5
vmovdqu ymm2,[const+32] ;23 3/0.5
vmovd xmm0,ECX ;5 1/1
vpbroadcastd ymm0,xmm0 ;5 3/1
vpcmpgtd ymm1,ymm1,ymm0 ;15 1/0.5
vpcmpgtd ymm2,ymm1,ymm0 ;15 1/0.5
vmovmskps EAX,ymm1 ;0 2/1
vmovmskps EDX,ymm2 ;0 2/1
or EAX,0x0800 ;0156 1/0.25
shl EDX,8 ;06 1/0.5
or EAX,EDX ;0156 1/0.25
bsf EAX,EAX ;1 3/1
ret
const: dd N0,N1,N2,N3,N4,N5,N6,N7
dd N8,N9,N10,0,0,0,0,0
Самым быстрым оказался вариант с условными переходами, он выдаёт значение через 6 тактов (если ни один переход не выполнится, а выполниться может максимум один) т.к. он хорошо параллелится по всем 4-м портам. Остальные варианты занимают 13-15 тактов.
Реальное измерение в таком цикле
Код:
mov EBX,-1
.cycl: mov ECX,EBX
ror ECX,3 ;Для перемешивания, нивелирует предсказание переходов
call CmpX
mov [result],EAX ;Добавим зависимость от результата
sub EBX,1
jnz .cycl
показывает результаты:
13s, 17s, 15s, 8s. Тестировались 11 равномерно распределённых в 2^32 порогов N0-N11.
Как видно даже один выполняемый переход резко всё портит для первого варианта. Отказ от перемешивания командой
ror времена практически не меняет (что тоже непонятно для первого варианта, ведь тогда практически все переходы предсказываются верно).
На удивление прекрасно отрабатывает AVX2 вариант, причина мне не совсем понятна, возможно влияет разворот цикла в очереди микрокоманд и её длины хватает чтобы AVX инструкции успевали параллелиться - throughput для тел процедур составляет 5.7, 11.8, 9.3,
3.0 такта соответственно, что и может объяснять аномально высокую скорость AVX кода.
Но в итоге максимальное реальное ускорение по сравнению с кодом на условных переходах составило всего полтора раза (ну или два раза если констант будет 15-16 штук). Считаю оно того не стоит.