Blog 5 Min. Lesezeit

Auswirkungen von Thrashing auf die C++-Performance: Doxygen-Analyse

Diesen Artikel teilen
Thrashing Impact on C++ Performance: Doxygen Analysis

Wenn die auf Ihrem Rechner laufenden Prozesse mehr Speicher anfordern, als physisch verfügbar ist, beginnt der Kernel, Speicherseiten auf die Festplatte auszulagern und wieder einzulesen. Dadurch wird genügend physischer Speicher freigegeben, um die RAM-Anforderungen des anfordernden Prozesses zu erfüllen.

Eine übermäßige Nutzung des Swappings wird als Thrashing bezeichnet und ist unerwünscht, weil sie die Gesamtleistung des Systems deutlich reduziert – vor allem, weil Festplatten wesentlich langsamer als RAM sind.

Muss Ihre Anwendung große Datenmengen verarbeiten, kann Thrashing auftreten und die Anwendung drastisch verlangsamen. Es gibt zwei Lösungen: Entweder optimieren Sie die Anwendung für eine effizientere Speichernutzung oder Sie erweitern den physischen RAM des Systems.

Sehen wir uns an, welche Lösung Doxygen verwendet, um die Speichernutzung zu optimieren und Thrashing zu vermeiden.

Doxygen ist der De-facto-Standard zur Erzeugung von Dokumentation aus annotiertem C++-Quellcode, unterstützt aber auch zahlreiche andere verbreitete Programmiersprachen wie C, Objective-C, C#, PHP, Java und Python. Ein Dank gilt Dimitri van Heesch für seinen großen Einsatz bei Entwicklung und Pflege des Projekts.

Doxygen verwendet die Quelldateien als Eingabe, analysiert sie, extrahiert die benötigten Daten und speichert das Ergebnis in Instanzen der Klassen DirDef, FileDef, NamespaceDef, ClassDef und MemberDef. Sie alle erben von der Klasse Definition.

doxy7

Diese Klasseninstanzen werden anschließend zur Erzeugung der Dokumentation verwendet. Den meisten Speicher benötigen Informationen über Methoden und Variablen, die durch die Klasse MemberDef repräsentiert werden. Abhängig von der Anzahl der Methoden und Variablen in den verarbeiteten Projekten kann die Gesamtgröße dieser Instanzen auf mehr als 1 GB anwachsen.

Bei manchen Projekten beeinträchtigt das Speichern all dieser Instanzen im Arbeitsspeicher die Systemleistung, und die Dokumentationserzeugung kann viele Stunden dauern.

Wie optimiert Doxygen den Speicherverbrauch?

Doxygen verwendet eine Cache-basierte Lösung. Ein Cache ist eine verbreitete Methode zur Optimierung der Speichernutzung. Die benötigten Daten werden in einem Cache gehalten. Dieser enthält zahlreiche Slots mit jeweils bestimmten Daten. Überschreitet der Cache eine festgelegte Größe, werden einige Slots freigegeben und ihre Daten auf die Festplatte verschoben. Werden sie erneut benötigt, lädt man sie wieder in den Arbeitsspeicher.

Bei Doxygen ist der Algorithmus sehr einfach:

  • Es wird ein Cache mit 65.535 Slots definiert.
  • Wenn eine MemberDef-Instanz erzeugt werden muss, prüft Doxygen, ob ein Cache-Slot verfügbar ist. Ist dies der Fall, wird die Instanz im Arbeitsspeicher erzeugt. Andernfalls wird sie in einer Datendatei auf der Festplatte gespeichert und eine Indexdatei mit ihrer Position aktualisiert.
  • Wenn Doxygen auf eine MemberDef-Instanz zugreifen muss, prüft es zunächst, ob sie im Cache vorhanden ist. Falls nicht, ermittelt Doxygen anhand der Indexdatei ihre Position in der Datendatei und lädt sie von der Festplatte.

Die Performance des Caches hängt von folgenden Faktoren ab:

  • Der Container: Das kann eine Queue, ein Array, eine Liste oder ein eigener Container sein. Die Wahl des Containers kann die Cache-Performance beeinflussen.
  • Die maximale Größe des Caches.
  • Der Algorithmus zum Freigeben von Cache-Einträgen. Erreicht der Cache seine maximale Größe, muss entschieden werden, welche Einträge entfernt werden. Beispielsweise können Sie:
    • die zuerst geladenen Slots freigeben.
    • die zuletzt geladenen Slots freigeben.
    • die am seltensten verwendeten Slots freigeben.

1. Der Container

Doxygen definiert die Klasse ObjCache als verkettete Liste von CacheNode-Objekten. Diese Klasse ist dafür verantwortlich, Instanzen zum Cache hinzuzufügen und daraus zu entfernen.

doxy1

So deklariert Doxygen seinen Cache:

Doxygen::symbolCache   =new ObjCache(16+cacheSize);// 16 -> room for 65536 elements, 

2. Cache-Größe

Doxygen liest die maximale Cache-Größe aus der Konfigurationsdatei:

int cacheSize =Config_getInt("SYMBOL_CACHE_SIZE");

Diesen Parameter konfigurierbar zu machen ist sinnvoll, da sich auf Rechnern mit viel physischem Speicher die Cache-Größe erhöhen und damit die Cache-Performance verbessern lässt. In neueren Doxygen-Versionen wurde dieser Parameter jedoch aus der Konfigurationsdatei entfernt und durch einen Standardwert ersetzt.

3. Algorithmus zum Freigeben von Cache-Einträgen

Hier ist der Doxygen-Quellcodeausschnitt, der Cache-Einträge freigibt, sobald der Cache seine maximale Größe erreicht:

doxy6

Wie im makeResident -Methodencode beschrieben, der sehr gut kommentiert ist, wird bei vollem Cache der am längsten nicht verwendete Eintrag entfernt.

Diese Methode wird für nahezu alle MemberDef -Methoden aufgerufen. Sie wird jedes Mal ausgeführt, wenn auf den MemberDef -Zustand zugegriffen werden muss, um zu prüfen, ob dieses Member geladen ist. Falls nicht, lädt Doxygen es und entfernt das am längsten nicht verwendete Member aus dem Cache.

Auswirkungen des Cache-Einsatzes

Ein Cache kann die Anwendungsleistung verbessern. Doch bringt Caching eine wirklich relevante Optimierung oder handelt es sich nur um eine Mikrooptimierung, die die zusätzliche Komplexität nicht rechtfertigt?

Bevor wir Clang als C/C++-Parser für unser Produkt einsetzten, verwendeten wir in der ersten Version Doxygen und führten zahlreiche Tests mit unterschiedlichen Cache-Größen durch. Als wir den Cache deaktivierten und einige C++-Projekte mit dieser modifizierten Version analysierten, stieg die Parsing-Zeit deutlich – teilweise von 5 auf 25 Minuten. Bei großen Projekten dauerte der Vorgang Stunden und beeinträchtigte die Systemleistung erheblich.

Fazit

Ein Cache kann die Performance einer Anwendung bei der Verarbeitung großer Datenmengen erheblich verbessern. Es lohnt sich, die Cache-Implementierungen von Open-Source-Projekten zu untersuchen, bevor Sie entscheiden, wie Sie Caching in Ihren eigenen Anwendungen umsetzen.

Diesen Artikel teilen