Получил ответ с помощью микробенчмарка, используя такой код:
void timingWithIf( volatile __m128i * pA, volatile unsigned long * pShift, unsigned long n )
{
__m128i r = *pA ;
for ( unsigned long i = 0 ; i < n ; i++ )
{
r = _mm_slli_si128( r, 8 ) ; // a << 64
unsigned long shift = *pShift ;
// does it hurt more to do the check, or just do the operation?
if ( shift > 64 )
{
r = _mm_sll_epi64( r, _mm_set_epi32( 0, 0, 0, shift - 64 ) ) ;
}
}
*pA = r ;
}
Это сгенерировало следующий код:
xor %eax,%eax
movdqa (%rdi),%xmm0
test %rdx,%rdx
movdqa %xmm0,0xffffffffffffffe8(%rsp)
jbe F0
pxor %xmm0,%xmm0
B0: movdqa 0xffffffffffffffe8(%rsp),%xmm2
pslldq $0x8,%xmm2
movdqa %xmm2,0xffffffffffffffe8(%rsp)
mov (%rsi),%rcx
cmp $0x40,%rcx
jbe F1
add $0xffffffffffffffc0,%rcx
movd %ecx,%xmm1
punpckldq %xmm0,%xmm1
punpcklqdq %xmm0,%xmm1
psllq %xmm1,%xmm2
movdqa %xmm2,0xffffffffffffffe8(%rsp)
F1: inc %rax
cmp %rdx,%rax
jb B0
F0: movdqa 0xffffffffffffffe8(%rsp),%xmm0
movdqa %xmm0,(%rdi)
retq
nopl 0x0(%rax)
Обратите внимание, что сдвиг, которого избегает ветвь, на самом деле требует трех инструкций SSE (четыре, если вы могли бы перемещать ALU -> XMM reg), плюс одну операцию добавления ALU:
add $0xffffffffffffffc0,%rcx
movd %ecx,%xmm1
punpckldq %xmm0,%xmm1
punpcklqdq %xmm0,%xmm1
psllq %xmm1,%xmm2
С 1 миллиардом циклов я измеряю:
1) shift == 64:
~ 2,5 с с помощью if (без смещения no-op).
~ 2.8 с выполнением смещения no-op.
2) со смещением == 65:
~ 2,8 с или без if.
Синхронизация была сделана на "Intel (R) Xeon (R) CPU X5570 @ 2,93 ГГц" (/ proc / cpuinfo) и была относительно непротиворечивой.
Даже когда ветвь полностью избыточна (смещение == 65), я не вижу большой разницы во времени, необходимом для выполнения операции, но это определенно помогает избежать инструкций, которые выполняли бы SSE безСдвиг влево, когда (shift == 64).