стоимость проверки в сравнении с операцией SSE? - PullRequest
3 голосов
/ 02 апреля 2012

Вот два разных способа, которыми я мог бы сделать сдвиг влево на> = 64 бита с помощью встроенных функций SSE.Второй вариант рассматривает случай (shift == 64) специально, избегая одной инструкции SSE, но добавляя стоимость проверки if:

inline __m128i shiftLeftGte64ByBits( const __m128i & a, const unsigned shift )
{
   __m128i r ;

   r = _mm_slli_si128( a, 8 ) ; // a << 64

   r = _mm_sll_epi64( r, _mm_set_epi32( 0, 0, 0, shift - 64 ) ) ;

   return r ;
}

inline __m128i shiftLeftGte64ByBits( const __m128i & a, const unsigned shift )
{
   __m128i r ;

   r = _mm_slli_si128( a, 8 ) ; // a << 64

   if ( shift > 64 )
   {
      r = _mm_sll_epi64( r, _mm_set_epi32( 0, 0, 0, shift - 64 ) ) ;
   }

   return r ;
}

Мне было интересно (примерно), какова стоимость этого if() проверка сравнивается со стоимостью самой инструкции сдвига (возможно, относительно времени или количества циклов, необходимых для обычной инструкции сдвига АЛУ влево).

1 Ответ

1 голос
/ 03 апреля 2012

Получил ответ с помощью микробенчмарка, используя такой код:

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).

...