Willkommen in der Welt der denormalisierten Gleitkommazahlen ! Sie können die Leistung zerstören !!!
Denormale (oder subnormale) Zahlen sind eine Art Hack, um einige zusätzliche Werte aus der Gleitkommadarstellung sehr nahe Null zu erhalten. Operationen mit denormalisiertem Gleitkomma können zehn- bis hundertmal langsamer sein als mit normalisiertem Gleitkomma. Dies liegt daran, dass viele Prozessoren sie nicht direkt verarbeiten können und sie mithilfe von Mikrocode abfangen und auflösen müssen.
Wenn Sie die Zahlen nach 10.000 Iterationen ausdrucken, werden Sie feststellen, dass sie je nach Verwendung 0
oder 0.1
Verwendung zu unterschiedlichen Werten konvergiert haben .
Hier ist der auf x64 kompilierte Testcode:
int main() {
double start = omp_get_wtime();
const float x[16]={1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0,2.1,2.2,2.3,2.4,2.5,2.6};
const float z[16]={1.123,1.234,1.345,156.467,1.578,1.689,1.790,1.812,1.923,2.034,2.145,2.256,2.367,2.478,2.589,2.690};
float y[16];
for(int i=0;i<16;i++)
{
y[i]=x[i];
}
for(int j=0;j<9000000;j++)
{
for(int i=0;i<16;i++)
{
y[i]*=x[i];
y[i]/=z[i];
#ifdef FLOATING
y[i]=y[i]+0.1f;
y[i]=y[i]-0.1f;
#else
y[i]=y[i]+0;
y[i]=y[i]-0;
#endif
if (j > 10000)
cout << y[i] << " ";
}
if (j > 10000)
cout << endl;
}
double end = omp_get_wtime();
cout << end - start << endl;
system("pause");
return 0;
}
Ausgabe:
#define FLOATING
1.78814e-007 1.3411e-007 1.04308e-007 0 7.45058e-008 6.70552e-008 6.70552e-008 5.58794e-007 3.05474e-007 2.16067e-007 1.71363e-007 1.49012e-007 1.2666e-007 1.11759e-007 1.04308e-007 1.04308e-007
1.78814e-007 1.3411e-007 1.04308e-007 0 7.45058e-008 6.70552e-008 6.70552e-008 5.58794e-007 3.05474e-007 2.16067e-007 1.71363e-007 1.49012e-007 1.2666e-007 1.11759e-007 1.04308e-007 1.04308e-007
//#define FLOATING
6.30584e-044 3.92364e-044 3.08286e-044 0 1.82169e-044 1.54143e-044 2.10195e-044 2.46842e-029 7.56701e-044 4.06377e-044 3.92364e-044 3.22299e-044 3.08286e-044 2.66247e-044 2.66247e-044 2.24208e-044
6.30584e-044 3.92364e-044 3.08286e-044 0 1.82169e-044 1.54143e-044 2.10195e-044 2.45208e-029 7.56701e-044 4.06377e-044 3.92364e-044 3.22299e-044 3.08286e-044 2.66247e-044 2.66247e-044 2.24208e-044
Beachten Sie, dass die Zahlen im zweiten Durchgang sehr nahe bei Null liegen.
Denormierte Zahlen sind im Allgemeinen selten und daher versuchen die meisten Prozessoren nicht, sie effizient zu handhaben.
Um zu demonstrieren, dass dies alles mit denormalisierten Zahlen zu tun hat, wenn wir Denormals auf Null setzen, indem wir dies am Anfang des Codes hinzufügen:
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
Dann ist die Version mit 0
nicht mehr 10x langsamer und wird tatsächlich schneller. (Dies erfordert, dass der Code mit aktiviertem SSE kompiliert wird.)
Dies bedeutet, dass wir anstatt dieser seltsamen Werte mit niedrigerer Genauigkeit von nahezu Null zu verwenden, stattdessen nur auf Null runden.
Timings: Core i7 920 bei 3,5 GHz:
// Don't flush denormals to zero.
0.1f: 0.564067
0 : 26.7669
// Flush denormals to zero.
0.1f: 0.587117
0 : 0.341406
Am Ende hat dies wirklich nichts damit zu tun, ob es sich um eine Ganzzahl oder einen Gleitkomma handelt. Das 0
oder 0.1f
wird in ein Register außerhalb beider Schleifen konvertiert / gespeichert. Das hat also keinen Einfluss auf die Leistung.