Forum>Hardware>M1 Integer vs. Floating Point Geschwindigkeit

M1 Integer vs. Floating Point Geschwindigkeit

andreas_g
andreas_g19.06.2109:42
Liebe Community,

kann jemand im Besitz eines M1-Mac den hier in der zweiten Antwort angeführten Code ausführen: ?

Auf meinem Intel-Mac von 2009 bekomme ich folgende Ergebnisse:
short add/sub: 2.687930 [0]
short mul/div: 5.887147 [0]
long add/sub: 2.273578 [0]
long mul/div: 10.153706 [0]
long long add/sub: 2.279631 [0]
long long mul/div: 10.153597 [0]
float add/sub: 3.410509 [0]
float mul/div: 4.161580 [0]
double add/sub: 3.412539 [0]
double mul/div: 4.353723 [0]

Den Code einfach in ein leeres Textdokument einfügen, als main.cpp speichern und dann im Terminal:
gcc main.cpp -o test
./test

Hintergrund der Aktion ist, dass ich gerade überlege, ob ich ein bestimmtes Problem mit 64-bit Integers oder Double Precision Floats lösen soll. Vorteil der Integer ist die zusätzliche Genauigkeit (64 bit vs. 53 bit). Ich stelle allerdings tatsächlich einen Performance-Verlust im Vergleich zur Float-Variante fest.
0

Kommentare

Urkman19.06.2110:17
Hier mal als Vergleich die Werte von meinen MacBook Pro 16" mit i9 von 2019:

short add/sub: 1.651706 [0]
short mul/div: 4.298906 [0]
long add/sub: 1.266210 [0]
long mul/div: 5.825064 [0]
long long add/sub: 1.251795 [0]
long long mul/div: 5.654382 [0]
float add/sub: 2.112368 [0]
float mul/div: 2.905793 [0]
double add/sub: 2.294119 [0]
double mul/div: 3.302503 [0]
0
Urkman19.06.2110:26
Und hier die Werte vom M1:

short add/sub: 2.061687 [0]
short mul/div: 3.817571 [0]
long add/sub: 2.033921 [0]
long mul/div: 3.332240 [0]
long long add/sub: 2.035983 [0]
long long mul/div: 3.333299 [0]
float add/sub: 3.443586 [0]
float mul/div: 3.911592 [0]
double add/sub: 3.443227 [0]
double mul/div: 4.230765 [0]
0
andreas_g
andreas_g19.06.2110:34
Vielen Dank! Die Ergebnisse sind sehr interessant. Auf dem M1 wären also Integers die bessere Wahl, auf dem Intel dagegen eher die Floats, insbesondere deshalb, weil mehr Multiplikationen und Divisionen als Additionen und Subtraktionen mit im Spiel sind.

Spannend finde ich auch, wie wenig sich die Geschwindigkeit solch grundlegender Berechnungen im Verlauf der Zeit geändert hat und dass der M1 hier doch noch unterlegen ist (in der Effizienz natürlich nicht).
+2
piik
piik19.06.2111:01
Ist hier weniger = mehr?
-1
andreas_g
andreas_g19.06.2111:04
Die Ergebnisse sind die verstrichene Zeit in Sekunden. Weniger bedeutet schneller.
0
piik
piik19.06.2113:01
andreas_g
Spannend finde ich auch, wie wenig sich die Geschwindigkeit solch grundlegender Berechnungen im Verlauf der Zeit geändert hat und dass der M1 hier doch noch unterlegen ist (in der Effizienz natürlich nicht).
Das finde ich allerdings auch. 1 Jahrzehnt und die Integers wurden schneller - die Fließkomma-Einheit im M1 hingegen bekleckert sich nicht gerade mit Ruhm. Ein Glück, dass letztere nicht viel tut bei der Fluptizität des OS-UI.
0
becreart
becreart19.06.2113:09
Hm, bin mal gespannt wie der Nachfolger des M1 für die grossen iMacs operiert. Für meinen aktuellen iMac habe ich diese Ergebnisse bekommen (2019, i9)

short add/sub: 1.495988 [0]
short mul/div: 3.926936 [0]
long add/sub: 1.149302 [0]
long mul/div: 5.125717 [0]
long long add/sub: 1.145657 [0]
long long mul/div: 5.148897 [0]
float add/sub: 1.921806 [0]
float mul/div: 2.665998 [0]
double add/sub: 1.899183 [0]
double mul/div: 2.862115 [0]
0
marm19.06.2113:22
Mal interessehalber: Werden diese Programme mit höchster Priorität ausgeführt? Woher weiß der Rechner, ob er sich hierfür ins Zeug legen soll oder nur einen Energiespar-Core bemüht?
0
Dunkelbier19.06.2113:25
marm
Mal interessehalber: Werden diese Programme mit höchster Priorität ausgeführt? Woher weiß der Rechner, ob er sich hierfür ins Zeug legen soll oder nur einen Energiespar-Core bemüht?
Und wie gut wird der Code mit gcc auf den M1 optimiert? Verwendet Xcode Nichte gerade einen ganz anderen Compiler?
0
Perry Goldsmith
Perry Goldsmith19.06.2114:30
@Dunkelbier hat recht. Die Werte des GCC sind hier nicht auf die Performance von aktuellen (Swift-)Apps auf dem Mac übertragbar. Wenn aber @andreas_g genau diesen Compiler in genau dieser Optimierungsstufe für ein Programm benutzen will (weil er beispielsweise anderen Code hinzulinken muss, der sehr speziell ist), dann kann ihn natürlich auch die „schlechte“ Optimierung des GCC hier interessieren.
0
Wiesi
Wiesi19.06.2114:36
Dunkelbier
Und wie gut wird der Code mit gcc auf den M1 optimiert? Verwendet Xcode Nichte gerade einen ganz anderen Compiler?

So wie es aussieht, verbringt das Programm die meiste Zeit in einer ziemlich einfach gestrickten Schleife, die zudem mit viel Sachverstand so gestaltet wurde, daß der Compiler kaum Tricks anwenden kann. Die Ergebnisse sind daher wohl vergleichbar.

@andreas_g hat uns jedoch recht wenig über die Algorithmen in seinem Projekt verraten. Normalerweise kann man bei diesen die Gleitkomma-Arithmetik nicht so einfach mal durch Festkomma ersetzen. Oft müssen beim Festkomma Skalierungsfaktoren mitgeführt werden, damit der Zahlenbreich nicht überschritten wird. Beim Gleitkomma kann es wiederum zu Stellenauslöschungen kommen. Wenn diese nicht entdeckt werden, ist das Ergebnis ungenau bis unbrauchbar. Bei einem hinreichend komplexen Problem ist ein Vergleich der nackten Rechengeschwindigkeiten wenig aussagekräftig.
„Everything should be as simple as possible, but not simpler“
+2
Perry Goldsmith
Perry Goldsmith19.06.2114:50
Wenn es sich um ein rechenintensives Programm handelt, dann sollte man aber heutzutage auf dem Mac auf jeden Fall überlegen, ob man nicht Metal für die Berechnungen benutzen kann. (Metal löst auch CoreCL ab.)
Das Testprogramm läuft ja (falls sich bei der Default-Optimierung des gcc in den letzten Jahren nichts grundlegend geändert hat) nur auf einem einzigen Core und kann also die Vorteile der verbesserten Parallelverarbeitung gar nicht nutzen.
0
Dunkelbier19.06.2115:37
Wiesi
So wie es aussieht, verbringt das Programm die meiste Zeit in einer ziemlich einfach gestrickten Schleife, die zudem mit viel Sachverstand so gestaltet wurde, daß der Compiler kaum Tricks anwenden kann. Die Ergebnisse sind daher wohl vergleichbar.
Ja, aber es kommt ja auf letztlich auf das zu lösende Problem an. Und da hat die Compileroptimierung ein gehöriges Wort mitzunehmen. Ich finde diesen Test für deshalb eher vollkommen nutzlos.

Was nützt Dir denn reine vermutlich gute Fließkomma-Performance, wenn der endgültige Code wegen eines unpassenden Compilers einfach nicht gut übersetzt wird, bzw. werden kann?
0
Wiesi
Wiesi19.06.2117:22
Dunkelbier
Die Performanz eines Programms hängt natürlich nicht nur von der nackten Rechengeschwindigkeit ab. Aber in diesem Thread ging es es wohl um die Frage, ob man durch Umstieg auf die Festkomma-Rechnung einen zusätzlichen Gewinn an Geschwindigkeit erreichen könnte, ganz unabhängig von all dem Anderen. Und hier hat sich gezeigt, daß Intel in der Beschleunigung der Gleitkomma-Rechnung offensichtlich weiter ist, als der M1. Bei der FK-Rechnung ist es eher umgekehrt. Hierbei ist zu berücksichtigen, daß die Division im unterschied zu den übrigen drei Grundrechenarten nur durch Iteration zum Ergebnis kommt. Um diese zu beschleunigen hat Intel beim GK großen Aufwand betrieben, beim FK nicht.

Im großen und ganzen ist der Unterschied nicht so gewaltig, daß man allein daran die Entscheidung zwischen GK und FK festmachen kann. Da hast Du recht.
„Everything should be as simple as possible, but not simpler“
0
gfhfkgfhfk19.06.2117:40
Wiesi
So wie es aussieht, verbringt das Programm die meiste Zeit in einer ziemlich einfach gestrickten Schleife, die zudem mit viel Sachverstand so gestaltet wurde, daß der Compiler kaum Tricks anwenden kann. Die Ergebnisse sind daher wohl vergleichbar.
Der GCC 11.1.0 optimiert bei mir die Schleife mit -O3 für short, int und long long weg, weil er erkennt, dass eine Konstante Anzahl an Schleifendurchläufen erfolgt und er die Schleifen einfach zu
einer Multiplikation vereinfachen kann.
+1
andreas_g
andreas_g19.06.2120:20
Vielen Dank für die zahlreichen Rückmeldungen. Wie so oft ist das Problem wohl nicht so einfach, wie vermutet. Für alle die Interesse am Hintergrund haben, ist hier der betroffene Quelltext zu finden: .

Im Wesentlichen dreht sich alles um die Funktion host_time_us(), die wiederum die Funktion host_time_sec() aufruft. Diese wird rund 250.000 mal pro Sekunde aufgerufen, ist also wesentlich für die Performance des Programms. Es ist gut möglich, dass hier auch andere Faktoren eine Rolle spielen. Bei der Verwendung von Float können die häufig verwendeten Daten beispielsweise besser auf die vorhandenen Register verteilt werden, wodurch eventuell Zugriffe auf den Cache oder Hauptspeicher eingespart werden.
0
piik
piik19.06.2123:07
Hier die Ergebnisse eines i5-10600K mit Nominal-Takt:
(dabei wurde im Mittel etwa 1 Kern ausgelastet und die CPU benötigte etwa 10W)

short add/sub: 1.630882 [0]
short mul/div: 4.045346 [0]
long add/sub: 1.187958 [0]
long mul/div: 5.275700 [0]
long long add/sub: 1.192073 [0]
long long mul/div: 5.280803 [0]
float add/sub: 1.968692 [0]
float mul/div: 2.743673 [0]
double add/sub: 1.964599 [0]
double mul/div: 2.947810 [0]
0
Perry Goldsmith
Perry Goldsmith20.06.2107:15
andreas_g
Im Wesentlichen dreht sich alles um die Funktion host_time_us(), die wiederum die Funktion host_time_sec() aufruft. Diese wird rund 250.000 mal pro Sekunde aufgerufen[…]
Alle 4 Mikrosekunden einen doppelten Funktionsaufruf mit Stackframes und dem ganzen Brimborium? Kann man denn für diesen Zweck nicht ereignisorientiert (lazy) arbeiten und den Zähler eines Hardware-Timerbausteins benutzen?

Ich habe auf die Schnelle natürlich nicht den ganzen Code verstanden.
0
andreas_g
andreas_g20.06.2107:43
Perry Goldsmith
andreas_g
Im Wesentlichen dreht sich alles um die Funktion host_time_us(), die wiederum die Funktion host_time_sec() aufruft. Diese wird rund 250.000 mal pro Sekunde aufgerufen[…]
Alle 4 Mikrosekunden einen doppelten Funktionsaufruf mit Stackframes und dem ganzen Brimborium? Kann man denn für diesen Zweck nicht ereignisorientiert (lazy) arbeiten und den Zähler eines Hardware-Timerbausteins benutzen?

Ich habe auf die Schnelle natürlich nicht den ganzen Code verstanden.
Ich habe falsch gerechnet. Es wird tatsächlich alle 100 Mikrosekunden aufgerufen. Dann wird jeweils geprüft, ob ein Task im Scheduler fällig ist (m68000.h, M68000_AddCycles()). Eine höhere Auflösung wäre besser, aber es ist eben ein Kompromiss.
+1
Marcel Bresink20.06.2109:48
Dunkelbier
Und wie gut wird der Code mit gcc auf den M1 optimiert? Verwendet Xcode Nichte gerade einen ganz anderen Compiler?

Ja, aber hier wird ja auch gar nicht gcc verwendet. Wenn man den Befehl gcc auf einem normal (mit Xcode) konfigurierten Mac aufruft, wird in Wirklichkeit nicht gcc gestartet, sondern Apples clang-Compiler, der die ganze C-Sprachenfamilie dann von der Semantik her so auswertet, wie gcc es machen würde.

Wenn man die entsprechenden O-Optionen verwendet, wird also voll optimierter Code für den M1 erzeugt.
0
marcel15120.06.2109:59
Ich bin kein großer Programmierer, daher verzeiht eine vielleicht nicht so schlaue frage. Floats sind ja Gleitkommazahlen und Integers ganze Zahlen. Sollte man nicht diejenigen Zahlen nehmen mit denen man auch rechnen will oder auf welchem Holzweg bin ich hier?
0
andreas_g
andreas_g20.06.2110:47
marcel151
Ich bin kein großer Programmierer, daher verzeiht eine vielleicht nicht so schlaue frage. Floats sind ja Gleitkommazahlen und Integers ganze Zahlen. Sollte man nicht diejenigen Zahlen nehmen mit denen man auch rechnen will oder auf welchem Holzweg bin ich hier?

Es gibt hier zwei mögliche Vorgehensweisen. Derzeit wird mit Sekunden gerechnet. Dafür werden Floats verwendet, um die nötige Präzision von Mikrosekunden zu behalten. Alternativ könnte auch mit Mikrosekunden gerechnet werden, die dann mit Integers darstellbar wären, weil eine höhere Genauigkeit nicht erforderlich ist.
Der Nachteil beim Rechnen mit Floats doppelter Präzision ist, dass nur 53 bit Genauigkeit vorhanden sind. Überschreitet der Zähler also den Wert von 1<<53 Mikrosekunden, wird beim Hochzählen das Intervall länger als eine Mikrosekunde. Wie lange es dauert, bis es zur Überschreitung kommt, ist plattformabhängig. Die internen Zähler haben je nach System andere Frequenzen. Bei meinem System hat der Zähler eine Frequenz von einem GHz. Wenn ich mich nicht verrechnet habe, würde auf meinem System also ein Verlust der Genauigkeit nach rund 104 Tagen eintreten:

(1<<53) / (1000000000 * 60 * 60 * 24) = 104,25

Bei Verwendung von Signed Integers mit 64 bit Präzision würde ein Overflow erst nach 106.752 Tagen oder 292 Jahren eintreten.
+2
Wiesi
Wiesi20.06.2111:30
Nachdem ich nun weiß, wo das Problem liegt, würde ich im Festkomma rechnen. Früher, als noch alles einfacher war, hätte ich in Assembler eine Interrupt-Routine geschrieben und deren Zyklen gezählt. (Habe ich tatsächlich mal gemacht. Wenn ich mich recht erinnere, waren es 70 Zyklen à 1,75 μs um eine Zeile von der Tastatur einzulesen.) Auf jeden Fall dürfte die Zeit für das nackte Hochzählen im Vergleich zum Wasserkopf der C-Routine vernachlässigbar sein.
„Everything should be as simple as possible, but not simpler“
+1
andreas_g
andreas_g20.06.2118:00
Danke für alle Tipps! Ich habe eine Variante mit Integers, die geschwindigkeitsmäßig auf meinem Intel-System annähernd mit der Float-Variante mithalten kann. Das dürfte nach den Erkenntnissen aus diesem Thread auf einem M1 eher besser als schlechter sein. Jetzt ist noch testen angesagt, um sicherzustellen, dass ich keine Bugs erzeugt habe.
0
gfhfkgfhfk24.06.2113:27
Ich habe etwas mit dem Code herum gespielt, und AVX/AVX2 und NEON Intrinsics im Code ergänzt. Damit das sich besser aufteilen lässt enthält die innere Schleife nun 16 Anweisungen und keine 10 mehr. Dafür wird auch der Test für int ausgeführt, anscheinend hat der ursprüngliche Autor das Programm auf Windows geschrieben, weil dort int und long die gleiche Größe haben. Auf einem UNIX sind long und long long identisch.

Das veränderte ursprüngliche Programm mit "g++ -std=c++17 orig.cc -o orig" bzw. sehr sinnvoll das ganze auch einmal mit "g++ -std=c++17 -O3 orig.cc -o orig-opt" zu übersetzen.
#include <stdio.h>
#ifdef _WIN32
#include <sys/timeb.h>
#else
#include <sys/time.h>
#endif
#include <time.h>
#include <cstdlib>
#include <iostream>
#include <iomanip>

using namespace std;

double
mygettime(void) {
# ifdef _WIN32
  struct _timeb tb;
  _ftime(&tb);
  return (double)tb.time + (0.001 * (double)tb.millitm);
# else
  struct timeval tv;
  if(gettimeofday(&tv, 0) < 0) {
    perror("oops");
  }
  return (double)tv.tv_sec + (0.000001 * (double)tv.tv_usec);
# endif
}

template< typename Type >
void my_test(const char* name) {
    Type v  = 0;
    Type va[16];
    // Do not use constants or repeating values
    //  to avoid loop unroll optimizations.
    // All values >0 to avoid division by 0
    // Perform ten ops/iteration to reduce
    //  impact of ++i below on measurements
    va[ 0] = (Type)(rand() % 256)/16 + 1;
    va[ 1] = (Type)(rand() % 256)/16 + 1;
    va[ 2] = (Type)(rand() % 256)/16 + 1;
    va[ 3] = (Type)(rand() % 256)/16 + 1;
    va[ 4] = (Type)(rand() % 256)/16 + 1;
    va[ 5] = (Type)(rand() % 256)/16 + 1;
    va[ 6] = (Type)(rand() % 256)/16 + 1;
    va[ 7] = (Type)(rand() % 256)/16 + 1;
    va[ 8] = (Type)(rand() % 256)/16 + 1;
    va[ 9] = (Type)(rand() % 256)/16 + 1;
    va[10] = (Type)(rand() % 256)/16 + 1;
    va[11] = (Type)(rand() % 256)/16 + 1;
    va[12] = (Type)(rand() % 256)/16 + 1;
    va[13] = (Type)(rand() % 256)/16 + 1;
    va[14] = (Type)(rand() % 256)/16 + 1;
    va[15] = (Type)(rand() % 256)/16 + 1;

#ifdef DEBUG
    cout << "va {";
    for (size_t i = 0; i != 16; ++i) {
        cout << va[i] << ", ";
    }
    cout << "}\n";
#endif

    double t1 = mygettime(), t2, t3, t4;
    for (size_t i = 0; i < 100000000; ++i) {
        v += va[ 0];
        v -= va[ 1];
        v += va[ 2];
        v -= va[ 3];
        v += va[ 4];
        v -= va[ 5];
        v += va[ 6];
        v -= va[ 7];
        v += va[ 8];
        v -= va[ 9];
        v += va[10];
        v -= va[11];
        v += va[12];
        v -= va[13];
        v += va[14];
        v -= va[15];
    }

    t2 = mygettime();
    // Pretend we make use of v so compiler doesn't optimize out
    //  the loop completely
    //printf("%s add/sub: %f [%d]\n", name, mygettime() - t1, (int)v&1);
    cout << setw (9) << name << " add/sub: " << fixed << setw(7) << setprecision(4) << t2 - t1 << " [" << v << "]\n";
    t3 = mygettime();

    v = 1;

    for (size_t i = 0; i < 100000000; ++i) {
        v *= va[ 0];
        v /= va[ 1];
        v *= va[ 2];
            v /= va[ 3];
            v *= va[ 4];
            v /= va[ 5];
        v *= va[ 6];
        v /= va[ 7];
        v *= va[ 8];
        v /= va[ 9];
        v *= va[10];
        v /= va[11];
        v *= va[12];
        v /= va[13];
        v *= va[14];
        v /= va[15];
    }

    t4 = mygettime();
  // Pretend we make use of v so compiler doesn't optimize out
  //  the loop completely
  //printf("%s mul/div: %f [%d]\n", name, mygettime() - t1, (int)v&1);
  cout << setw (9) << name << " mul/div: " << fixed << setw(7) << setprecision(4) << t4 - t3 << " [" << v << "]\n";
}

int main() {
  my_test< short >("short");
  my_test< int >("int");
  my_test< long >("long");
  my_test< long long >("long long");
  my_test< float >("float");
  my_test< double >("double");

  return 0;
}
Die Version mit AVX/AVX bzw. NEON Intrinsics. Zu übersetzen mit "g++ -std=c++17 -DAVX -DAVX2 -O3 -mavx2 main.cc -o main".
#include <cstdlib>
#include <iostream>
#include <iomanip>

#ifdef _WIN32
#include <sys/timeb.h>
#else
#include <sys/time.h>
#endif

#include <time.h>

// ARM NEON
#ifdef NEON
#include <arm_neon.h>
#endif

// Intel AVX
#ifdef AVX
#include <immintrin.h>
#endif

using namespace std;

double
mygettime(void) {
# ifdef _WIN32
  struct _timeb tb;
  _ftime(&tb);
  return (double)tb.time + (0.001 * (double)tb.millitm);
# else
  struct timeval tv;
  if(gettimeofday(&tv, 0) < 0) {
    perror("oops");
  }
  return (double)tv.tv_sec + (0.000001 * (double)tv.tv_usec);
# endif
}

template <typename T, size_t VALUES, size_t LOOPS>
class add_sub_loop {
public:
    static T doit (const T* va) {
        T r = 0;

        for (size_t i = 0; i != LOOPS; ++i) {
            for (size_t j = 0; j != VALUES; ++j) {
                r += va[j];
            }
        }

        return r;
    }
};

#ifdef NEON
template<size_t VALUES, size_t LOOPS>
class add_sub_loop<short,VALUES,LOOPS> {
public:
    static short doit (const short* va) {
        short r = 0;
        int16x8_t    v = vdupq_n_s16 (0);
        int16x8_t   x = vdupq_n_s16 (0);

        for (size_t j = 0; j != LOOPS; ++j) {
            for (size_t i = 0; i != VALUES/8; ++i) {
                v = vld1q_s16 (va+i*8);
                x = vaddq_s16 (v, x);
            }
        }
        
        r += vgetq_lane_s16 (x, 0);
        r += vgetq_lane_s16 (x, 1);
        r += vgetq_lane_s16 (x, 2);
        r += vgetq_lane_s16 (x, 3);
        r += vgetq_lane_s16 (x, 4);
        r += vgetq_lane_s16 (x, 5);
        r += vgetq_lane_s16 (x, 6);
        r += vgetq_lane_s16 (x, 7);

        return r;
    }
};

template<size_t VALUES, size_t LOOPS>
class add_sub_loop<int,VALUES,LOOPS> {
public:
    static int doit (const int* va) {
        int r = 0;
        int32x4_t    v = vdupq_n_s32 (0);
        int32x4_t   x = vdupq_n_s32 (0);

        for (size_t j = 0; j != LOOPS; ++j) {
            for (size_t i = 0; i != VALUES/4; ++i) {
                v = vld1q_s32 (va+i*4);
                x = vaddq_s32 (v, x);
            }
        }

        r += vgetq_lane_s32 (x, 0);
        r += vgetq_lane_s32 (x, 1);
        r += vgetq_lane_s32 (x, 2);
        r += vgetq_lane_s32 (x, 3);

        return r;
    }
};

template<size_t VALUES, size_t LOOPS>
class add_sub_loop<long, VALUES, LOOPS> {
public:
    static long doit (const long* va) {
        long long r = 0;
        int64x2_t    v = vdupq_n_s64 (0);
        int64x2_t    x = vdupq_n_s64 (0);

        for (size_t j = 0; j != LOOPS; ++j) {
            for (size_t i = 0; i != VALUES/2; ++i) {
                v = vld1q_s64 (va+i*2);
                x = vaddq_s64 (v, x);
            }
        }

        r += vgetq_lane_s64 (x, 0);
        r += vgetq_lane_s64 (x, 1);

        return r;
    }
};

template<size_t VALUES, size_t LOOPS>
class add_sub_loop<float,VALUES,LOOPS> {
public:
    static float doit (const float* va) {
        float r = 0.0f;
        float32x4_t    v = vdupq_n_f32 (0.0f);
        float32x4_t x = vdupq_n_f32 (0.0f);
        
        for (size_t j = 0; j != LOOPS; ++j) {
            float32x4_t x = vdupq_n_f32 (0.0f);
            for (size_t i = 0; i != VALUES/4; ++i) {
                v = vld1q_f32 (va+i*4);
                x = vaddq_f32 (v, x);
            }
        }

        r += vgetq_lane_f32(x, 0);
        r += vgetq_lane_f32(x, 1);
        r += vgetq_lane_f32(x, 2);
        r += vgetq_lane_f32(x, 3);

        return r;
    }
};
#endif

#ifdef AVX2
template<size_t VALUES, size_t LOOPS>
class add_sub_loop<short,VALUES,LOOPS> {
public:
    static short doit (const short* va) {
        __m256i v = _mm256_set1_epi16 (0);
        __m256i x = _mm256_set1_epi16 (0);
        short* r = reinterpret_cast<short*>(&x);

        for (size_t j = 0; j != LOOPS; ++j) {
            for (size_t i = 0; i != VALUES/16; ++i) {
                v = _mm256_lddqu_si256 (reinterpret_cast<const __m256i*>(va+16*i));
                x = _mm256_add_epi16 (v, x);
            }
        }

        x = _mm256_hadd_epi16(x,x);

        return r[0] + r[1] + r[2] + r[3] + r[8] + r[9] + r[10] + r[11];
    }
};

template<size_t VALUES, size_t LOOPS>
class add_sub_loop<int,VALUES,LOOPS> {
public:
    static int doit (const int* va) {
        __m256i v = _mm256_set1_epi32 (0);
        __m256i x = _mm256_set1_epi32 (0);
        int* r = reinterpret_cast<int*>(&x);

        for (size_t j = 0; j != LOOPS; ++j) {
            for (size_t i = 0; i != VALUES/8; ++i) {
                v = _mm256_lddqu_si256 (reinterpret_cast<const __m256i*>(va+8*i));
                x = _mm256_add_epi32 (v, x);
            }
        }

        x = _mm256_hadd_epi32 (x, x);

        return r[0] + r[1] + r[4] + r[5];
    }
};
#endif

#ifdef AVX
template<size_t VALUES, size_t LOOPS>
class add_sub_loop<float,VALUES,LOOPS> {
public:
    static float doit (const float* va) {
        __m256 v = _mm256_set_ps (0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
        __m256 x = _mm256_set_ps (0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);

        float* r = reinterpret_cast<float*>(&x);

        for (size_t j = 0; j != LOOPS; ++j) {
            for (size_t i = 0; i != VALUES/8; ++i) {
                v = _mm256_load_ps (va+8*i);
                x = _mm256_add_ps (v, x);

            }
        }

        x = _mm256_hadd_ps (x, x);

        return r[0] + r[1] + r[4] + r[5];
    }
};

template<size_t VALUES, size_t LOOPS>
class add_sub_loop<double,VALUES,LOOPS> {
public:
    static double doit (const double* va) {
        __m256d v = _mm256_set_pd (0.0, 0.0, 0.0, 0.0);
        __m256d x = _mm256_set_pd (0.0, 0.0, 0.0, 0.0);

        double* r = reinterpret_cast<double*>(&x);

        for (size_t j = 0; j != LOOPS; ++j) {
            for (size_t i = 0; i != VALUES/4; ++i) {
                v = _mm256_load_pd (va+4*i);
                x = _mm256_add_pd (v, x);
            }
        }

        x = _mm256_hadd_pd (x, x);

        return r[0] + r[2];
    }
};
#endif

template <typename T, size_t VALUES, size_t LOOPS>
class mul_div_loop {
public:
    static T doit (const T* va, const T* vc [[maybe_unused]]) {
        T r = 1;

        for (size_t i = 0; i != LOOPS; ++i) {
            for (size_t j = 0; j != VALUES; j+=2) {
                r *= va[j];
                r /= va[j+1];
            }
        }

        return r;
    }
};

#ifdef NEON
template <size_t VALUES, size_t LOOPS>
class mul_div_loop<short,VALUES,LOOPS> {
public:
    static short doit (const short* va, const short* vc [[maybe_unused]]) {
        short r = 1;
        int16x8_t v = vdupq_n_s16 (0);
        int16x8_t x = vdupq_n_s16 (1);

        for (size_t j = 0; j != LOOPS; ++j) {
            for (size_t i = 0; i != VALUES/8; ++i) {
                v = vld1q_s16 (va+i*8);
                x = vmulq_s16 (v, x);
            }
        }

        r *= vgetq_lane_s16 (x, 0);
        r *= vgetq_lane_s16 (x, 1);
        r *= vgetq_lane_s16 (x, 2);
        r *= vgetq_lane_s16 (x, 3);
        r *= vgetq_lane_s16 (x, 4);
        r *= vgetq_lane_s16 (x, 5);
        r *= vgetq_lane_s16 (x, 6);
        r *= vgetq_lane_s16 (x, 7);

        return r;
    }
};

template <size_t VALUES, size_t LOOPS>
class mul_div_loop<int,VALUES,LOOPS> {
public:
    static int doit (const int* va, const int* vc [[maybe_unused]]) {
        int r = 1;
        int32x4_t v = vdupq_n_s32 (0);
        int32x4_t x = vdupq_n_s32 (1);

        for (size_t j = 0; j != LOOPS; ++j) {
            for (size_t i = 0; i != VALUES/4; ++i) {
                v = vld1q_s32 (va+i*2);
                x = vmulq_s32 (v, x);
            }
        }

        r *= vgetq_lane_s32 (x, 0);
        r *= vgetq_lane_s32 (x, 1);
        r *= vgetq_lane_s32 (x, 2);
        r *= vgetq_lane_s32 (x, 3);

        return r;
    }
};

template <size_t VALUES, size_t LOOPS>
class mul_div_loop<float,VALUES,LOOPS> {
public:
    static float doit (const float* va [[maybe_unused]], const float* vc) {
        float r = 1.0f;
        float32x4_t    v = vdupq_n_f32 (0.0f);
        float32x4_t x = vdupq_n_f32 (1.0f);

        for (size_t j = 0; j != LOOPS; ++j) {
            for (size_t i = 0; i != VALUES/4; ++i) {
                v = vld1q_f32 (vc+i*4);
                x = vmulq_f32 (v, x);
            }
        }

        r *= vgetq_lane_f32 (x, 0);
        r *= vgetq_lane_f32 (x, 1);
        r *= vgetq_lane_f32 (x, 2);
        r *= vgetq_lane_f32 (x, 3);

        return r;
    }
};
#endif

#ifdef AVX2
template<size_t VALUES, size_t LOOPS>
class mul_div_loop<short,VALUES,LOOPS> {
public:
    static short doit (const short* va, const short* vb [[maybe_unused]]) {
        __m256i v = _mm256_set1_epi16 (0);
        __m256i x = _mm256_set1_epi16 (1);

        short* r = reinterpret_cast<short*>(&x);

        for (size_t j = 0; j != LOOPS; ++j) {
            for (size_t i = 0; i != VALUES/16; ++i) {
                v = _mm256_lddqu_si256 (reinterpret_cast<const __m256i*>(va+16*i));
                x = _mm256_mullo_epi16(x, v);
            }
        }

        return r[0] * r[1] * r[2] * r[3] * r[4] * r[5] * r[6] * r[7] * r[8] * r[9] * r[10] * r[11] * r[12] * r[13] * r[14] * r[15];
    }
};

template<size_t VALUES, size_t LOOPS>
class mul_div_loop<int,VALUES,LOOPS> {
public:
    static int doit (const int* va, const int* vb [[maybe_unused]]) {
        __m256i v = _mm256_set1_epi32 (0);
        __m256i x = _mm256_set1_epi32 (1);

        int* r = reinterpret_cast<int*>(&x);

        for (size_t j = 0; j != LOOPS; ++j) {
            for (size_t i = 0; i != VALUES/8; ++i) {
                v = _mm256_lddqu_si256 (reinterpret_cast<const __m256i*>(va+8*i));
                x = _mm256_mullo_epi32(x, v);
            }
        }

        return r[0] * r[1] * r[2] * r[3] * r[4] * r[5] * r[6] * r[7];
    }
};
#endif

#ifdef AVX
template<size_t VALUES, size_t LOOPS>
class mul_div_loop<float,VALUES,LOOPS> {
public:
    static float doit (const float* va [[maybe_unused]], const float* vc) {
        __m256 v = _mm256_set_ps (1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f);
        __m256 x = _mm256_set_ps (1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f);

        float* r = reinterpret_cast<float*>(&x);

        for (size_t j = 0; j != LOOPS; ++j) {
            for (size_t i = 0; i != VALUES/8; ++i) {
                v = _mm256_load_ps (vc+8*i);
                x = _mm256_mul_ps (x, v);
            }
        }

        return r[0] * r[1] * r[2] * r[3] * r[4] * r[5] * r[6] * r[7];
    }
};

template<size_t VALUES, size_t LOOPS>
class mul_div_loop<double,VALUES,LOOPS> {
public:
    static double doit (const double* va [[maybe_unused]], const double* vc) {
        __m256d v = _mm256_set_pd (1.0, 1.0, 1.0, 1.0);
        __m256d x = _mm256_set_pd (1.0, 1.0, 1.0, 1.0);

        double* r = reinterpret_cast<double*>(&x);

        for (size_t j = 0; j != LOOPS; ++j) {
            for (size_t i = 0; i != VALUES/4; ++i) {
                v = _mm256_load_pd (vc+4*i);
                x = _mm256_mul_pd (x, v);
            }
        }

        return r[0] * r[1] * r[2] * r[3];
    }
};
#endif

template <typename T>
void my_test (const char*const name) {
    constexpr size_t NUM_LOOPS = 100000000;
    constexpr size_t NUM_VALUE = 16;
    T v  = 0;
    // Do not use constants or repeating values
    //  to avoid loop unroll optimizations.
    // All values >0 to avoid division by 0
    // Perform ten ops/iteration to reduce
    //  impact of ++i below on measurements
    T va[NUM_VALUE], vb[NUM_VALUE], vc[NUM_VALUE];

    for (size_t i = 0; i != NUM_VALUE; ++i) {
        va[i] = (T)(rand() % 256) / 16 + 1;
        if (0 == (i % 2)) {
            vb[i] =  va[i];
            vc[i] =  va[i];
        } else {
            vb[i] = -va[i];
            vc[i] = 1.0/va[i];
        }
    }

    double t1 = mygettime(), t2, t3, t4;

    v = add_sub_loop<T, NUM_VALUE, NUM_LOOPS>::doit(vb);

    // Pretend we make use of v so compiler doesn't optimize out
    //  the loop completely
    t2 = mygettime();

    cout << setw (9) << name << " add/sub: " << fixed << setw(7) << setprecision(4) << t2 - t1 << " [" << v << "]\n";

    t3 = mygettime();

    v = mul_div_loop<T, NUM_VALUE, NUM_LOOPS>::doit(va, vc);

    // Pretend we make use of v so compiler doesn't optimize out
    //  the loop completely
    t4 = mygettime();

    cout << setw (9) << name << " mul/div: " << fixed << setw(7) << setprecision(4) << t4 - t3 << " [" << v << "]\n";
}



int main() {
    my_test<short>("short");
    my_test<int>("int");
    my_test<long>("long");
    my_test<long long>("long long");
    my_test<float>("float");
    my_test<double>("double");

    return EXIT_SUCCESS;
}
0
omoopo24.06.2116:00
Hallo, wollte auch mal meine Sicht dazu geben
Gewechselt von MP 6.1 3,5 64/2TB ssd auf M1 Mini 16/1TB und muss sagen nach meinem Gefühl ist der M1 Mini gefühlt in allem schneller egal ob PS oder FCP und da ist es mir relativ ob Floats oder … aber schon alleine vom Preis her sind Welten Unterschied.
Ich möchte keinem auf die Füße treten da ist nur meine Meinung.
Grüße
0

Kommentieren

Sie müssen sich einloggen, um sich an einer Diskussion beteiligen zu können.