Почему скаляр SSE sqrt (x) медленнее, чем rsqrt (x) * x?

Я описал некоторые наши основные математические навыки на Intel Core Duo и рассматривал различные подходы к квадратному корню Iмы заметили нечто странное: используя скалярные операции SSE, быстрее получить взаимный квадратный корень и умножить его, чтобы получить sqrt, чем использовать собственный код операции sqrt! I '

Я проверяю это с помощью цикла что-то вроде:

inline float TestSqrtFunction( float in );

void TestFunc()
{
  #define ARRAYSIZE 4096
  #define NUMITERS 16386
  float flIn[ ARRAYSIZE ]; // filled with random numbers ( 0 .. 2^22 )
  float flOut [ ARRAYSIZE ]; // filled with 0 to force fetch into L1 cache

  cyclecounter.Start();
  for ( int i = 0 ; i < NUMITERS ; ++i )
    for ( int j = 0 ; j < ARRAYSIZE ; ++j )
    {
       flOut[j] = TestSqrtFunction( flIn[j] );
       // unrolling this loop makes no difference -- I tested it.
    }
  cyclecounter.Stop();
  printf( "%d loops over %d floats took %.3f milliseconds",
          NUMITERS, ARRAYSIZE, cyclecounter.Milliseconds() );
}

я пробовал это с несколькими различными телами для TestSqrtFunction, и яу меня есть время, которое действительно царапает мою голову. Хуже всего было использовать встроенную функцию sqrt () и позволитьумный" компилятор "оптимизировать», На 24ns / float, используя x87 FPU, это было патетически плохо:

inline float TestSqrtFunction( float in )
{  return sqrt(in); }

Следующее, что я попробовал, было использование встроенного, чтобы заставить компилятор использовать SSE 's скалярный sqrt код операции:

inline void SSESqrt( float * restrict pOut, float * restrict pIn )
{
   _mm_store_ss( pOut, _mm_sqrt_ss( _mm_load_ss( pIn ) ) );
   // compiles to movss, sqrtss, movss
}

Это было лучше, на 11,9 нс / плавать. Я тоже пробовалКармак»дурацкая техника приближения Ньютона-Рафсона, который работал даже лучше, чем аппаратные, на 4,3 нс / с плавающей точкой, хотя с ошибкой 1 в 210 (что слишком много для моих целей).

Странно было, когда я попробовал SSE оп дляобоюдный квадратный корень, а затем использовать умножение, чтобы получить квадратный корень (х * 1 / √х = √Икс ). Несмотря на то, что для этого требуются две зависимые операции, это было самое быстрое решение на сегодняшний день, с 1,24 нс / с плавающей запятой и точностью до 2-14:

inline void SSESqrt_Recip_Times_X( float * restrict pOut, float * restrict pIn )
{
   __m128 in = _mm_load_ss( pIn );
   _mm_store_ss( pOut, _mm_mul_ss( in, _mm_rsqrt_ss( in ) ) );
   // compiles to movss, movaps, rsqrtss, mulss, movss
}

Мой вопрос в основномчто дает?Почему SSE 's встроенный в аппаратный квадратный корень код операциипомедленнее чем синтезировать его из двух других математических операций?

Я уверен, что это действительно стоимость самой операции, потому что ямы проверили:

Все данные помещаются в кэш, и доступ осуществляется последовательнофункции встроеныРазвертывание петли не имеет значенияфлаги компилятора установлены на полную оптимизацию (и сборка хорошая, я проверял) (

редактировать: stephentyrone правильно указывает, что операции с длинными строками чисел должны использовать векторизацию SIMD-упакованных операций, напримерrsqrtps - но структура данных массива здесь только для целей тестирования: то, что я действительно пытаюсь измерить,скаляр производительность для использования в коде, который можетт векторизация.)

Ответы на вопрос(5)

Ваш ответ на вопрос