Warum ist SSE scalar sqrt (x) langsamer als rsqrt (x) * x?

Ich habe einige unserer Grundrechenarten auf einem Intel Core Duo analysiert und bei der Betrachtung verschiedener Ansätze zur Quadratwurzel etwas Merkwürdiges festgestellt: Bei Verwendung der SSE-Skalaroperationen ist es schneller, eine reziproke Quadratwurzel zu nehmen und diese zu multiplizieren um den sqrt zu bekommen, muss der native sqrt opcode verwendet werden!

Ich teste es mit einer Schleife wie:

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() );
}

Ich habe dies mit ein paar verschiedenen Körpern für die TestSqrtFunction versucht, und ich habe einige Timings, die meinen Kopf wirklich kratzen. Das Schlimmste war bei weitem, die native Funktion sqrt () zu verwenden und den "intelligenten" Compiler "optimieren" zu lassen. Bei 24ns / float mit der x87-FPU war dies erbärmlich schlecht:

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

Das nächste, was ich versuchte, war die Verwendung eines Intrinsic, um den Compiler zu zwingen, den skalaren sqrt-Opcode von SSE zu verwenden:

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
}

Dies war mit 11,9 ns / float besser. Ich habe es auch versuchtCarmacks verrückte Newton-Raphson-Approximationstechnik, der mit 4.3ns / float sogar besser lief als die Hardware, allerdings mit einem Fehler von 1 zu 210 (Das ist zu viel für meine Zwecke).

Das doozy war, als ich die SSE-Operation für versuchtewechselseitig Quadratwurzel, und verwenden Sie dann eine Multiplikation, um die Quadratwurzel zu erhalten (x * 1 / √x = √x). Obwohl dies zwei abhängige Operationen erfordert, war es mit 1,24 ns / float und einer Genauigkeit von 2 die mit Abstand schnellste Lösung-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
}

Meine Frage ist grundsätzlichwas gibt? Warum ist SSEs eingebauter Hardware-Quadratwurzel-Opcode?Langsamer als es aus zwei anderen mathematischen Operationen zu synthetisieren?

Ich bin mir sicher, dass dies wirklich die Kosten für die Operation selbst sind, da ich Folgendes überprüft habe:

Alle Daten passen in den Cache und die Zugriffe erfolgen sequentiellDie Funktionen sind inlineDas Abrollen der Schlaufe macht keinen UnterschiedCompiler-Flags sind auf volle Optimierung gesetzt (und die Assembly ist gut, habe ich überprüft)

(bearbeiten: stephentyrone weist korrekterweise darauf hin, dass Operationen mit langen Zahlenfolgen die vektorisierenden SIMD-gepackten Operationen verwenden sollten, wie zrsqrtps - aber die Array-Datenstruktur dient hier nur zu Testzwecken: Was ich wirklich zu messen versuche, istSkalar Leistung für die Verwendung in Code, der nicht vektorisiert werden kann.)

Antworten auf die Frage(5)

Ihre Antwort auf die Frage