Frage

ich an einem Punkt bin, wo ich wirklich C ++ Code, um zu optimieren. Ich bin eine Bibliothek für molekulare Simulationen zu schreiben, und ich brauche eine neue Funktion hinzuzufügen. Ich habe bereits versucht, diese Funktion in der Vergangenheit an, aber ich habe dann virtuell in verschachtelten Schleifen aufgerufenen Funktionen. Ich hatte schlechte Gefühle darüber, und die erste Implementierung bewiesen, dass dies eine schlechte Idee war. Allerdings war das OK für das Konzept zu testen.

Jetzt muß ich diese Funktion so schnell wie möglich sein (auch ohne Assembler-Code oder GPU-Berechnung, dies noch C ++ und besser lesbar als weniger sein). Jetzt weiß ich, ein wenig mehr über Vorlagen und Klassenpolitik (von Alexandrescu ausgezeichnetem Buch) und ich denke, dass eine Kompilierung-Code-Generierung die Lösung sein kann.

Allerdings muß ich den Entwurf testen, bevor die große Arbeit der Umsetzung in die Bibliothek zu tun. Die Frage ist, über den besten Weg, um die Effizienz dieser neuen Funktion zu testen.

Natürlich muss ich Optimierungen einschalten, weil ohne diese g ++ (und wahrscheinlich auch andere Compiler als auch) würde einige unnötige Operationen in dem Objektcode halten. Ich brauche auch eine starke Nutzung der neuen Funktion in der Benchmark zu machen, weil ein Delta von 1e-3 Sekunden kann den Unterschied zwischen einem guten und einem schlechten Design machen (diese Funktion wird millionenfach in dem realen Programm aufgerufen werden).

Das Problem ist, dass g ++ ist manchmal „zu klug“, während die Optimierung und kann eine ganze Schleife entfernen, wenn sie die Ansicht, dass das Ergebnis einer Berechnung wird nie verwendet. Ich habe schon die einmal gesehen, wenn am Ausgang Assembler-Code suchen.

Wenn ich etwas Druck in den stdout, wird der Compiler dann gezwungen werden, um die Berechnung in der Schleife zu tun, aber ich werde wahrscheinlich meist Benchmark die Iostream Umsetzung.

Wie kann ich ein richtig Benchmark einer kleinen Funktion aus einer Bibliothek extrahierte? Verwandte Frage: Gibt es einen richtigen Ansatz, um diese Art von in vitro Tests an einer kleinen Einheit zu tun oder muß ich den ganzen Kontext

?

Vielen Dank für die Hinweise!


Es scheint mehr Strategien zu sein, von der Compiler-spezifischen Optionen ermöglichen die Feinabstimmung auf allgemeinere Lösungen, die mit jedem Compiler wie volatile oder extern funktionieren sollen.

Ich glaube, ich werde alle diese versuchen. Vielen Dank für Ihre Antworten!

War es hilfreich?

Lösung

Wenn Sie erzwingen möchten jeder Compiler verwerfen kein Ergebnis haben sie das Ergebnis in ein flüchtiges Objekt schreiben. Dieser Vorgang kann nicht optimiert werden, per Definition.

template<typename T> void sink(T const& t) {
   volatile T sinkhole = t;
}

No Iostream Overhead, nur eine Kopie, die in dem generierten Code zu bleiben hat. Nun, wenn Sie ergibt sich aus einer Vielzahl von Operationen zu sammeln, ist es am besten, sie nicht eins nach dem anderen zu verwerfen. Diese Kopien können noch etwas Aufwand hinzufügen. Stattdessen sammelt irgendwie alle Ergebnisse in einem einzigen nicht-flüchtigen Objekt (so dass alle einzelnen Ergebnisse sind erforderlich) und dann dieses Ergebnis Objekt zu einem flüchtigen zuweisen. Z.B. wenn Ihre individuellen Operationen alle Strings erzeugen, können Sie Auswertung erzwingen, indem alle char Werte zusammen 1 Modulo << 32. Dies fügt kaum Overhead; die Saiten werden wahrscheinlich im Cache sein. Das Ergebnis der Addition wird anschließend zu flüchtigen zugeordnet werden, so dass jedes Zeichen in jedem Stachel muss in der Tat berechnet werden, keine Abkürzungen erlaubt.

Andere Tipps

Es sei denn, Sie haben eine wirklich aggressive Compiler (kann passieren), würde ich eine Prüfsumme empfehlen Berechnung (einfach alle Ergebnisse zusammen addieren) und Ausgabe der Prüfsumme.

Other than that, Sie könnten an der erzeugten Assembler-Code aussehen soll, bevor Sie irgendwelche Benchmarks so können Sie visuell überprüfen, dass alle Schleifen werden Lauf tatsächlich.

Compiler ist nur zu beseitigen Code-Zweig erlaubt, die nicht passieren kann. Solange es nicht, dass ein Zweig ausgeführt werden sollte ausschließen kann, wird es nicht beseitigen. Solange es irgendwo einige Datenabhängigkeit ist, wird der Code dort sein und ausgeführt werden. Compiler sind nicht zu klug, um Abschätzungs, welche Aspekte eines Programms werden nicht ausgeführt werden, und versuchen Sie nicht zu, denn das ist ein NP-Problem ist und kaum berechenbar. Sie haben einige einfache Kontrollen wie für if (0), aber das ist es.

Meine bescheidene Meinung ist, dass Sie möglicherweise von einem anderen Problem früher getroffen wurden, wie die Art und Weise C / C ++ boolean Ausdrücke auswertet.

Aber egal, da dies zu einem Test der Geschwindigkeit ist, können Sie überprüfen, dass die Dinge für dich selbst aufgerufen - führen Sie sie einmal ohne, dann ein weiteres Mal mit einem Test des Rückgabewertes. Oder eine statische Variable inkrementiert wird. Am Ende des Tests, drucken Sie die Nummer erzeugt. Die Ergebnisse werden gleich sein.

Um Ihre Frage zu In-vitro-Tests zu beantworten: Ja, das zu tun. Wenn Ihre App so zeitkritisch sind, tun. Auf der anderen Seite, Ihre Beschreibung Hinweise auf einem anderes Problem: Wenn Ihr Delta in einem Zeitraum von 1e-3 Sekunden, dann klingt das wie ein Problem des Rechenaufwand, da das Verfahren in Frage sehr aufgerufen werden muss, sehr oft (für wenige Läufe, 1e-3 Sekunden vernachlässigbaren).

Das Problem Domain, die Sie Modellierung sind klingt sehr komplex und die Datensätze sind wahrscheinlich sehr groß. Solche Dinge sind immer eine interessante Anstrengung. Stellen Sie sicher, dass Sie unbedingt die richtigen Datenstrukturen und Algorithmen zuerst, aber, und Mikro-Optimierung alles, was Sie danach wollen. . Also, ich würde sagen, Blick auf dem ganzen Kontext ersten ; -)

Aus Neugier, was ist das Problem, das Sie Berechnung?

Sie haben ein hohes Maß an Kontrolle über die Optimierungen für Ihre Zusammenstellung. -O1, -O2, und so weiter sind Aliase nur für eine Reihe von Schaltern.

Von den man-Seiten

       -O2 turns on all optimization flags specified by -O.  It also turns
       on the following optimization flags: -fthread-jumps -falign-func‐
       tions  -falign-jumps -falign-loops  -falign-labels -fcaller-saves
       -fcrossjumping -fcse-follow-jumps  -fcse-skip-blocks
       -fdelete-null-pointer-checks -fexpensive-optimizations -fgcse
       -fgcse-lm -foptimize-sibling-calls -fpeephole2 -fregmove -fre‐
       order-blocks  -freorder-functions -frerun-cse-after-loop
       -fsched-interblock  -fsched-spec -fschedule-insns  -fsched‐
       ule-insns2 -fstrict-aliasing -fstrict-overflow -ftree-pre
       -ftree-vrp

Sie können diesen Befehl zwicken und Sie verwenden, um die Optionen zu untersuchen verengen zu helfen.

       ...
       Alternatively you can discover which binary optimizations are
       enabled by -O3 by using:

               gcc -c -Q -O3 --help=optimizers > /tmp/O3-opts
               gcc -c -Q -O2 --help=optimizers > /tmp/O2-opts
               diff /tmp/O2-opts /tmp/O3-opts Φ grep enabled

Wenn Sie die culpret Optimierung finden, sollten Sie die cout die nicht benötigen.

Wenn dies für Sie möglich ist, könnten Sie versuchen, Ihren Code aufteilen in:

  • die Bibliothek, die Sie mit allen Optimierungen kompiliert testen möchten, eingeschaltet
  • ein Testprogramm, dinamically die Bibliothek Verknüpfung mit Optimierungen ausgeschaltet

Andernfalls könnten Sie eine andere Optimierungsstufe angeben (es sieht aus wie Sie gcc verwenden ...) für den Test functio n mit dem optimize Attribute (siehe http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html#Function-Attributes ) .

Sie können eine Dummy-Funktion in einer separaten CPP-Datei erstellen, die nichts tut, sondern nehmen als Argument unabhängig von der Art des Berechnungsergebnisses ist. Dann können Sie diese Funktion mit den Ergebnissen Ihrer Berechnung aufrufe, gcc zwingen den Zwischencode zu erzeugen, und die einzige Strafe sind die Kosten für eine Funktion aufgerufen wird (was nicht Ihre Ergebnisse verzerren sollte, es sei denn Sie nennen es ein Los! ).

#include <iostream>

// Mark coords as extern.
// Compiler is now NOT allowed to optimise away coords
// This it can not remove the loop where you initialise it.
// This is because the code could be used by another compilation unit
extern double coords[500][3];
double coords[500][3];

int main()
{

//perform a simple initialization of all coordinates:
for (int i=0; i<500; ++i)
 {
   coords[i][0] = 3.23;
   coords[i][1] = 1.345;
   coords[i][2] = 123.998;
 }


std::cout << "hello world !"<< std::endl;
return 0;
}

Bearbeiten : die einfachste Sache, die Sie einfach tun können, ist, die Daten in irgendeiner Art und Weise unechter verwenden, nachdem die Funktion und außerhalb des Benchmarks ausgeführt wird. Wie,

StartBenchmarking(); // ie, read a performance counter
for (int i=0; i<500; ++i)
 {
   coords[i][0] = 3.23;
   coords[i][1] = 1.345;
   coords[i][2] = 123.998;
 }
StopBenchmarking(); // what comes after this won't go into the timer

// this is just to force the compiler to use coords
double foo;
for (int j = 0 ; j < 500 ; ++j )
{
  foo += coords[j][0] + coords[j][1] + coords[j][2]; 
}
cout << foo;

Was in diesen Fällen für mich manchmal funktioniert, ist das in vitro Test innerhalb einer Funktion und übergeben Sie die Benchmark-Datensatz durch flüchtig Zeiger zu verbergen. Dies teilt den Compiler, dass es nicht nachfolgende Schreibvorgänge auf diese Zeiger zusammenfallen muß (weil sie sein könnte zB Memory-Mapped I / O). Also,

void test1( volatile double *coords )
{
  //perform a simple initialization of all coordinates:
  for (int i=0; i<1500; i+=3)
  {
    coords[i+0] = 3.23;
    coords[i+1] = 1.345;
    coords[i+2] = 123.998;
  }
}

Aus irgendeinem Grund habe ich noch nicht herausgefunden es muss nicht immer in MSVC arbeiten, aber es oft nicht - Blick auf die Bestückungsleistung sicher zu sein. Denken Sie auch daran, dass volatile wird einige Compiler-Optimierungen Folie (es verbietet, den Compiler von der Einhaltung des Zeigers Inhalt in Registern und Kräften schreibt in der Programmreihenfolge auftreten), so ist dies nur vertrauenswürdig, wenn Sie es für die Verwendung von letzter Schreib aus Daten.

In der Regel in-vitro-Tests, wie dies sehr nützlich ist, so lange wie Sie sich erinnern, dass es nicht die ganze Geschichte. Ich teste in der Regel meine neue mathematische Routinen in Isolation wie diese, so dass ich schnell nur die Cache-und Pipeline-Eigenschaften meines Algorithmus auf konsistente Daten iterieren auf kann.

Der Unterschied zwischen Retorten Profilierung wie diese und es in der „realen Welt“ laufen bedeutet, dass Sie wild Eingabedatensätze variiert bekommen (manchmal am besten Fall, manchmal schlimmsten Fall manchmal pathologisch), wird der Cache in einem unbekannten sein Zustand auf die Funktion der Eingabe und man andere Threads haben kann auf dem Bus hämmern; so sollten Sie einige Benchmarks auf diese Funktion ausführen in vivo und wenn Sie fertig sind.

Ich weiß nicht, ob GCC eine ähnliche Funktion hat, aber mit VC ++ Sie verwenden können:

#pragma optimize

, um selektiv drehen Optimierungen an / aus. Wenn GCC ähnliche Fähigkeiten hat, können Sie mit voller Optimierung bauen konnten und es einfach ausschalten, wo notwendig, um sicherzustellen, dass Ihr Code aufgerufen wird.

Nur ein kleines Beispiel für eine unerwünschte Optimierung:

#include <vector>
#include <iostream>

using namespace std;

int main()
{
double coords[500][3];

//perform a simple initialization of all coordinates:
for (int i=0; i<500; ++i)
 {
   coords[i][0] = 3.23;
   coords[i][1] = 1.345;
   coords[i][2] = 123.998;
 }


cout << "hello world !"<< endl;
return 0;
}

Wenn Sie den Code aus Kommentar „doubeln coords [500] [3]“ bis zum Ende des for-Schleife wird es erzeugt genau den gleichen Assembler-Code (nur versucht, mit g ++ 4.3.2). Ich weiß, dass dieses Beispiel viel zu einfach ist, und ich war dieses Verhalten zu zeigen, mit einem std :: vector einer einfachen „Koordinaten“ Struktur nicht in der Lage.

Aber ich denke, dieses Beispiel zeigt noch, dass einige Optimierungen Fehler in der Benchmark vorstellen können, und ich wollte ein paar Überraschungen dieser Art zu vermeiden, wenn in einer Bibliothek neuen Code einzuführen. Es ist leicht vorstellbar, dass der neue Kontext könnte einige Optimierungen verhindern und zu einer sehr ineffizienten Bibliothek führen.

Das gleiche sollte auch mit virtuellen Funktionen anwenden (aber ich es hier nicht beweisen). Wird in einem Kontext, in dem ein statischer Link, um den Job tun würde, ich bin ziemlich zuversichtlich, dass anständige Compiler sollte die zusätzliche Indirektion Aufruf für die virtuelle Funktion beseitigen. Ich kann diesen Aufruf in einer Schleife versuchen und schließen daraus, dass eine virtuelle Funktion aufrufen ist nicht so eine große Sache. Dann werde ich es hunderttausend mal in einem Kontext nennen, wo der Compiler kann nicht erraten, was der genaue Typ des Zeigers sein und hat eine 20% ige Erhöhung der Zeit des Laufens ...

Beim Start aus einer Datei lesen. in Ihrem Code, sagen, wenn (Eingang == "x") cout << result_of_benchmark;

Der Compiler wird nicht in der Lage sein, um die Berechnung zu beseitigen, und wenn Sie die Eingabe gewährleisten ist nicht „x“, werden Sie nicht Benchmark der Iostream.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top