Warum ist mein Programm so langsam?

8

Jemand entschied sich dafür, einen schnellen Test zu machen, um zu sehen, wie der native Client in Bezug auf Geschwindigkeit mit Javascript verglichen wird. Sie haben das getan, indem sie 10 000 000-Quadrat-Quadrat-Berechnungen durchgeführt und die benötigte Zeit gemessen haben. Das Ergebnis mit Javascript: 0,096 Sekunden und mit NaCl: 4,241 Sekunden ... Wie kann das sein? Ist Geschwindigkeit nicht einer der Gründe, NaCl überhaupt zu verwenden? Oder fehlen mir einige Compiler-Flags oder etwas?

Hier ist der Code, der ausgeführt wurde:

%Vor%

PS: Diese Frage ist eine bearbeitete Version von etwas, das im erschienen ist native Client-Mailingliste

    
gman 22.04.2013, 20:28
quelle

1 Antwort

19

HINWEIS: Diese Antwort ist eine bearbeitete Version von etwas, das im erschienen ist native Client-Mailingliste

Microbenchmarks sind schwierig: Wenn Sie nicht verstehen, was Sie gerade tun, ist es sehr einfach, Äpfel-zu-Orangen-Vergleiche zu erstellen, die für das Verhalten, das Sie beobachten / messen möchten, nicht relevant sind.

Ich werde ein wenig mit Ihrem eigenen Beispiel arbeiten (ich schließe NaCl aus und halte mich an die bestehenden, "erprobten und wahren" Technologien).

Hier ist Ihr Test als natives C-Programm:

%Vor%

Ok. Wir können Milliarden Zyklen in 25,43 Sekunden machen. Aber lass uns sehen, was Zeit braucht: wir ersetzen "result + = sqrt (i);" mit "result + = i;"

%Vor%

Wow! 95% der Zeit wurde tatsächlich in der von der CPU bereitgestellten sqrt-Funktion verbracht, alles andere benötigte weniger als 5%. Aber was ist, wenn wir den Code nur ein bisschen ändern: Ersetze "printf ("% g% g \ n ", result, tt);" mit "printf ("% g \ n ", tt);" ?

%Vor%

Hmm ... Sieht so aus, als wäre "sqrt" fast so schnell wie "+". Wie kann das sein? Wie kann printf den vorherigen Zyklus AT ALL beeinflussen?

Mal sehen:

%Vor%

Die erste Version ruft tatsächlich sqrt Milliarden mal auf, aber die zweite Version tut das überhaupt nicht! Stattdessen prüft es, ob die Nummer negativ ist und ruft in diesem Fall nur sqrt auf! Warum? Was versuchen die Compiler (oder vielmehr Compilerautoren) hier?

Nun, es ist einfach: Da wir in dieser speziellen Version nicht "result" verwendet haben, kann "sqrt" call sicher weggelassen werden ... wenn der Wert nicht negativ ist, das ist! Wenn es dann negativ ist (abhängig von FPU-Flags) kann sqrt verschiedene Dinge tun (unsinniges Ergebnis zurückgeben, Programm abstürzen usw.). Deshalb ist diese Version dutzendfach schneller - berechnet aber keine Quadratwurzeln!

Hier ist das letzte Beispiel, das zeigt, wie falsch microbenchmarks gehen können:

%Vor%

Ausführungszeit ist ... NULL? Wie kann es sein? Milliarden Berechnungen in weniger als einem Wimpernschlag? Mal sehen:

%Vor%

Oh, oh, Zyklus ist komplett eliminiert! Alle Berechnungen erfolgten zur Kompilierzeit und um die Verletzung zu beleidigen, wurden beide "Uhr" -Aufrufe vor dem Start des Zyklus ausgeführt!

Was ist, wenn wir es in eine separate Funktion einfügen?

%Vor%

Immer noch das gleiche ??? Wie kann das sein?

%Vor%

Uh-oh: Compiler ist schlau genug, um den Zyklus durch eine Multiplikation zu ersetzen!

Wenn Sie jetzt NaCl auf der einen Seite und JavaScript auf der anderen Seite hinzufügen, erhalten Sie ein so komplexes System, dass die Ergebnisse buchstäblich unvorhersehbar sind.

Das Problem hier ist, dass Sie für Microbenchmark versuchen, Stück Code zu isolieren und dann seine Eigenschaften zu bewerten, aber dann wird Compiler (egal JIT oder AOT) versuchen, Ihre Bemühungen zu vereiteln, weil es versucht, alle nutzlosen Berechnungen zu entfernen dein Programm!

Microbenchmarks nützlich, sicher, aber sie sind FORENSIC ANALYSIS-Tool, nicht etwas, das Sie verwenden möchten, um die Geschwindigkeit von zwei verschiedenen Systemen zu vergleichen! Dafür braucht man etwas "Reales" (in gewissem Sinne der Welt: etwas, das nicht durch übereifrigen Compiler in Stücke zerlegt werden kann). Arbeitsaufwand: Besonders beliebt sind Sortieralgorithmen.

Benchmarks, die sqrt verwenden, sind besonders unangenehm, da sie, wie wir gesehen haben, normalerweise mehr als 90% der Zeit einen einzigen CPU-Befehl ausführen: sqrtsd (fsqrt, wenn es eine 32-Bit-Version ist), was natürlich identisch ist JavaScript und NaCl. Diese Benchmarks können (wenn sie richtig implementiert werden) als Lackmustest dienen (wenn die Geschwindigkeit einiger Implementierungen zu sehr von dem abweicht, was die einfache native Version zeigt, dann tun Sie etwas falsch), aber sie sind nutzlos, da die Geschwindigkeiten von NaCl, JavaScript, C # verglichen werden. oder Visual Basic.

    
gman 23.04.2013 07:24
quelle