Künstliche Intelligenz

Effizientes C++: Die versteckten Kosten des Rückgabedatentyps auto


Im heutigen Beitrag möchte ich mich mit dem Schreiben von effizientem C++-Code befassen. Dieses Thema lässt sich nicht in einem einzigen Beitrag behandeln. Zum Start möchte ich mich auf die Steuerung der Zeit konzentrieren, die der Compiler benötigt, und dabei ein Element der Sprache herausgreifen: auto.

Weiterlesen nach der Anzeige




Andreas Fertig ist erfahrener C++-Trainer und Berater, der weltweit Präsenz- sowie Remote-Kurse anbietet. Er engagiert sich im C++-Standardisierungskomitee und spricht regelmäßig auf internationalen Konferenzen. Mit C++ Insights ( hat er ein international anerkanntes Tool entwickelt, das C++-Programmierenden hilft, C++ noch besser zu verstehen.

Ich halte die Typinferenz auto für eine großartige Erweiterung der Sprache. Mit der C++14-Version von auto als Rückgabedatentyp ist es ein guter Weg, flexiblere und akkuratere Funktionen zu schreiben. Allerdings geht das zu Kosten eines längeren Kompiliervorgangs.

Aber beginnen wir ohne auto als Rückgabedatentyp. Hier ist eine Dummy-Funktion, die nichts Interessantes macht:


int Fun(bool b, int val)
{
  if(b) {
    return val * 2;
  } else {
    return val / 2 * 3;
  }
}


Ich brauchte für das Beispiel lediglich eine Funktion mit einem Rumpf, der für den Compiler nicht völlig trivial war. Da es im heutigen Beitrag um effizientes C++ geht, möchte ich über die Zeit sprechen, die zum Kompilieren dieser wenigen Zeilen Code erforderlich ist. Dabei geht es nicht unbedingt um die absoluten Zahlen, da diese auf verschiedenen Rechnern unterschiedlich sein werden.

Wie kannst du deinen Compiler überhaupt messen? Eine Möglichkeit ist der Linux-Befehl time. Dieser liefert jedoch nur wenige Informationen, die nicht dabei helfen, deine Build-Zeiten zu verbessern. Wenn dir ein Build langsam erscheint, benötigst du kein Tool, das dir sagt, dass du recht hast, sondern ein Tool, das dir zeigt, wo eine mögliche Beschleunigung möglich ist.

Wenn time nicht das richtige Tool ist, was dann? Ganz einfach: dein Compiler! Zumindest Clang. Er verfügt über die hilfreiche Befehlszeilenoption -ftime-trace. Damit kann Clang für jede Objektdatei eine .json-Datei mit zahlreichen Daten erstellen. Du kannst die Informationen beispielsweise mit Chrome und seinem Tracing Viewer untersuchen. Öffne einfach einen Tab und gib chrome://tracing ein. Lade dann die JSON-Datei. Für den obigen Code auf meinem Rechner mit Clang 19 sieht die Visualisierung so aus:

Weiterlesen nach der Anzeige



Screenshot von Chrome mit den Tracing-Ergebnissen von Clang

Ein Wort zu meiner Vorgehensweise: Ich gehe davon aus, dass die Funktion Fun in einer Header-Datei steht und in der aktuellen Übersetzungseinheit nicht verwendet wird. Einfachheitshalber habe ich keinen anderen Code eingefügt oder verwendet. Ich habe eine .cpp-Datei mit dem oben gezeigten Code wie folgt compiliert:


clang++ -c a.cpp -ftime-trace


Zurück zur Visualisierung, auch wenn sie etwas kompakt ist. Die obere Leiste mit der Aufschrift ExecuteCompiler benötigt 6,499 ms.

Hier ist eine modifizierte Version des vorherigen Codes. Die einzige Änderung, die ich vorgenommen habe, ist die auto statt int als Rückgabedatentyp.


auto Fun(bool b, int val)
{
  if(b) {
    return val * 2;
  } else {
    return val / 2 * 3;
  }
}


Wenn ich diesen Code messe, erhalte ich folgende Ergebnisse:



Dieses Mal benötigt ExecuteCompiler 8,114 ms. Das sind ungefähr 1,5 ms mehr. Ja, das stimmt, das ist nur ein einzelner Datenpunkt. Vielleicht haben wir es hier mit Jitter zu tun. Das kann sein. Vielleicht gibt es Aufrufe, bei denen die beiden Zahlen näher beieinander liegen, aber es ist unwahrscheinlich, dass sie jemals gleich werden. Ich bin mir hier sicher, weil im zweiten Trace ein zusätzlicher Schritt erscheint: ParseFunctionDefinition. Dieser Schritt ist nur vorhanden, wenn man auto als Rückgabedatentyp für die Funktion verwendet oder die Funktion tatsächlich aufruft. Aber ich gehe davon aus, dass Fun in einer Header-Datei deklariert ist.

Das zweite Codebeispiel benötigt wegen ParseFunctionDefinition länger zum Kompilieren. Mit auto von C++14 als Rückgabedatentyp muss der Compiler die Funktionsdefinition nachschlagen, wenn er die Deklaration analysiert. Ohne auto verschiebt der Compiler die Analyse des Funktionsrumpfs, bis er wirklich benutzt wird.

Wenn du die Dauer des Kompiliervorgangs optimieren möchtest, solltest du auf Funktionen achten, die auto als Rückgabedatentyp in Header-Dateien verwenden.


(rme)



Source link

Beliebt

Die mobile Version verlassen