Mythen der Blog-Leser

Modernes C++  –  8 Kommentare

Ich war sehr neugierig, welche weiteren Mythen mir meine Leser schicken würden. Insbesondere die Leser auf diesem Blog waren ziemlich aktiv. Ich erhielt ein paar E-Mails und beobachtete eine lebhafte Diskussion auf heise Developer.

Bevo ich zu den mir zugeschickten Mythen schreibe, möchte ich zuerst die letzte Regel der C++ Core Guidelines vorstellen. Hier ist der letzte Mythos:

NR.7: Don’t: Make all data members protected

Daten, die als "protected" deklariert sind, machen dein Programm anspruchsvoller und fehleranfälliger. Falls in der Basisklasse protected-Daten zum Einsatz kommen, lässt sich nicht mehr über abgeleitete Klassen in Isolation nachdenken. Damit brichst du die Kapselung. Du musst dir immer Gedanken zu ganzen Klassenhierarchien machen.

Das bedeutet, diese drei Fragen müssen in der Regel beantwortet werden.

  1. Muss ich einen Konstruktor implementieren, um das protected-Datum richtig zu initialisieren?
  2. Welchen Wert besitzen das protected-Datum, wenn ich es verwende?
  3. Welche Funktionalität wird in Mitleidenschaft gezogen, wenn ich das protected-Datum verändere?

Natürlich wird die Beantwortung dieser Fragen umso anspruchsvoller, je mehr die Klassenhierarchie in die Tiefe wächst.

Streng genommen ist ein protected-Datum eine globale Variable in der Klassenhierarchie. Das erste Gebot der Softwareentwicklung lautet aber: Vermeide globale Daten.

Nun zu den Mythen:

In C++ geschriebene Programme brauchen mehr Speicher und CPU-Zeit als in C geschriebenen Programme (Gunter Königsmann)

Ehrlich gesagt, gegen diesen Mythos zu argumentieren, ist ziemlich schwierig und im Allgemeinen nicht möglich, wenn es um die C++-Bibliothek geht. Dies ist der Grund, dass ich nur ein paar Beobachtungen am Ende dieses Kapitels präsentiere. Zuerst einmal kommen aber die harten Fakten auf den Tisch. Der "Technical Report on C++ Performance" hilft mir dabei sehr.

Technical report on C++ performance

2006 verfasst die Working Group WG 21 das Dokument ISO/IEC TR 18015. Der Titel hört sich sehr sperrig an. Tatsächlich ist das Dokument die erste Wahl, wenn es darum geht, die Performanz von C++ zu analysieren. Diesen folgenden Aspekt bringt das Dokument direkt auf den Punkt:

  • to give the reader a model of time and space overheads implied by use of various C++ language and library features,
  • to debunk widespread myths about performance problems,
  • to present techniques for use of C++ in applications where performance matters, and
  • to present techniques for implementing C++ Standard language and library facilties to yield efficient code.

Verfasser des gut 200-seitigen Reports sind so bekannte Namen wie Dave Abrahams, Howard Hinnand, Dietmar Kühl, Dan Saks, Bill Seymour, Bjarne Stroustrup und Detlef Vollmann.

In dem Dokument selbst geht es um die C++-Sprachmerkmale, deren Kosten und Verwendung, dem Erzeugen effizienter Bibliotheken in C++, der Nutzung von C++ in Embedded-Systemen und einer Schnittstelle in C++, um mit Hardware zu kommunizieren. Insbesondere auf die C++-Sprachmerkmale, deren Kosten und Verwendung will ich genauer eingehen.

C++-Sprachmerkmale, Kosten und Verwendung

Bei ihrer Analyse greifen die Autoren auf drei Computerarchitekturen mit fünf verschiedenen C++-Compilern zurück. Die Compiler rufen sie mit unterschiedlichen Optimisierungsstufen auf. Die Ergebnisse, die ich deutlich vereinfachend darstelle, sind sehr aufschlussreich.

Namensräume

  • Besitzen keinen signifikanten Einfluss auf die Größe des Programms oder sein Zeitverhalten.

Typkonvertierungs-Operatoren

  • Die C++-Casts const_cast, static_cast und reinterpret_cast unterscheiden sich weder in Größe noch Zeitverhalten von ihrem C-Pendant.
  • Der zur Laufzeit ausgeführte dynamic_cast besitzt einigen Kosten. (Anmerkung: Diese Konvertierung besitzt kein Pendant in C.)

Klasse

  • Eine Klasse (class) ohne virtuelle Funktionen ist genauso groß wie eine Struktur (struct).
  • Eine Klasse mit virtuellen Funktionen besitzt die Kosten eines Zeigers und einer virtuellen Funktionstabelle (virtual function table). Dies sind typischerweise 2 bis 4 Byte.

Funktionsaufrufe auf Objekten

  • Der Aufruf einer nichtvirtuellen, nichtstatischen, nicht-inline Funktion ist genauso teuer wie der Aufruf einer freien Funktion.
  • Der Aufruf einer virtuellen Funktion ist so teuer wie der Aufruf einer freien Funktion mithilfe eines Zeigers, der in einem Array gespeichert ist.
  • Virtuelle Funktionen eines Klasse-Templates können Kosten verursachen.
  • Das Inlining von Funktionen bringt einen signifikanten Vorteil, reicht aber noch nicht ganz an die Performanz von C-Makros heran.

Mehrfachvererbung

  • Kann Zeit- und/oder Größen-Kosten implizieren.
  • Virtuelle Basisklassen besitzen gegenüber nichtvirtuellen Basisklassen zusätzlichen Kosten.

Run-Time Type Information (RTTI)

  • Pro Klasse werden typischerweise zusätzlich 40 Bytes benötigt.
  • Der typeid-Aufruf ist relativ langsam. Dies scheint aber ein Problem der Güte der Implementierung zu sein.
  • Die Konvertierung zur Laufzeit mit dynamic_cast ist langsam. Der Grund scheint laut dem Report vor allem auch in der Qualität der Implementierung zu liegen.

Ausnahmebehandlung

  • Zwei Strategien für den Umgang mit Ausnahmen haben sich etabliert. Das ist die Code- und die Tabellen-Strategie. Während bei der Code-Strategie zur Laufzeit zusätzliche Datenstrukturen für den Ausführungskontext verwaltet und verschoben werden müssen, wird bei der Tabellen-Strategie der Ausführungskontext in einer Tabelle vorgehalten.
  • Die Code-Strategie besitzt ein Größen-Overhead für den Stack und die Laufzeit. Der Zeit-Overhead ist ca. 6 Prozent, selbst wenn die Ausnahme nicht auftritt.
  • Die Tabellen-Strategie besitzt weder Kosten in der Programmgröße noch in der Laufzeit (Anmerkung: Für die Laufzeit gilt dies nur, wenn keine Ausnahme auftrat). Dafür ist diese Strategie schwieriger zu implementieren.

Templates

  • Für jedes Template-Argument wird ein neues Funktions- oder Klassen-Template erzeugt. Naiver Umgang mit Templates kann daher zu einem deutlichen Anstieg der Codegröße führen. Moderne C++-Compiler können die Anzahl der Template-Instanziierung deutlich reduzieren. In der Standard Template Library wird teilweise oder vollständige Template-Spezialisierung angewandt, um die Anzahl der Template-Instanziierungen zu reduzieren.

Die genauen Details, die exakten Zahlen und alle weiteren Punkte lassen sich in dem Report TR18015.pdf schön nachlesen.

MISRA C++

MISRA C++ (Motor Industry Software Reliability Association) stellt Regeln für das Schreiben von Software in sicherheitskritischen System auf. MISRA C++ wurde ursprünglich für die Automobilindustrie entwickelt, ist aber mittlerweile Industriestandard im Flugzeugbau, Militär und auch im Medizinbereich.

MISRA C++ stellt heraus, warum die Bedeutung von C++ in sicherheitskritischen System immer weiter zunimmt (1.1 The use of C++ in critical systems) :

  • C++ gives good support for high-speed, low-level, input/output operations, which are essential to many embedded systems.
  • The increased complexity of applications make the use of a high-level language more appropriate than assembly language.
  • C++ compilers generate code with similar size and RAM requirements to those of C.

Ein bitterer Wermutstropfen bleibt aber. MISRA C++ basiert auf klassischem C++. Dies ist vereinfachend gesprochen C++98. Modernes C++ hat aber deutlich mehr zu bieten für Embedded-Systeme. Diese Beobachtung gilt leider auch für den bereits vorgestellten Report Technical report on C++ performance.

Ohne ein paar Beobachtungen zu modernem C++ kann ich diesen Artikel nicht beenden. Modernes C++ ist ein Begriff, der meist für die C++-Standards C++11, C++14 und C++17 eingesetzt wird.

Meine Beobachtungen

Die Schwierigkeit, die Features der C++ Bibliothek mit ihrem C Pendant zu vergleichen, besteht vor allem darin, dass ein Vergleich äquivalente Datenstrukturen, Algorithmen oder Programme voraussetzt. Das heißt, damit lässt sich ein C-String mit einem C++-String nicht vergleichen, denn die C++-Variante verwaltet zum Beispiel automatisch ihren Speicher. Dieselbe Beobachtung lässt sich auf einen C-Array und die Container der Standard Template Library anwenden. Diese Punkte halten mich aber nicht davon ab, ein paar Punkte aufzuzählen. Wenn du mehr Details wissen willst, lese den hinterlegten Artikel.

Wie geht's weiter?

Ich hätte nicht gedacht, dass ich fast einen ganzen Artikel benötige, um gegen die Mythen zur Performanz und zum Speicherverbrauch von C++ zu argumentieren. Du kannst dir vorstellen, dass ich weitere Mythen zugeschickt bekommen habe. Auf diese werde ich in meinem nächsten Artikel eingehen. Falls du noch einen Mythos kennst, kannst du ihn mir auch gerne schicken: rainer.grimm@modernescpp.de.