Das Oprofile-Paket sammelt statistische Laufzeit-Informationen moderner Prozessoren und wertet sie aus. Das erlaubt das Profiling komplexer, interagierender Programme und Bibliotheken und ermöglicht somit eine Analyse des gesamten Systemverhaltens.
Die Zeiten, in denen ein Entwickler allein durch das Lesen von Quellcode alle Performance-Engpässe in seinem Programm aufspüren konnte, sind vorbei. Heutige Programme sind oft sehr komplex, weshalb er bei der Optimierung auf genaue Messwerte aus dem Einsatz in der realen Welt zurückgreifen muss, falls er Modifikationen vornehmen will, die signifikante Performance-Verbesserungen bringen. Die üblicherweise stattfindende Kooperation von Programmen mit mehreren Threads, die eventuell noch in einer Mehrprozessorumgebung laufen, erschwert die Analyse erheblich und verlangt nach neuen Lösungen.
CPU zählt mit
Oprofile bietet die Möglichkeit, bei bestimmten Ereignissen bis zu mehrere tausend Mal pro Sekunde spezifische Systemzustände zu protokollieren. Im einfachsten Fall stammen diese Events vom Instruction Pointer der CPU. Damit kann der Entwickler feststellen, welche Programmteile am häufigsten ausgeführt werden, eventuell einen Bottleneck aufspüren und dann für Optimierungen sorgen. Dieses periodische Sampling von Events und Zuständen wird auf Kernel-Ebene entweder durch einen SystemTimer realisiert oder – wesentlich effizienter – durch so genannte Performance Counter, die sich in fast allen modernen Prozessoren finden.
Gegenüber klassischen Lösungen, etwa dem speziellen Instrumentieren der Programme durch einen Compiler (zum Beispiel mit »gcc -pg«), hat eine solche Implementierung mehrere Vorteile: Es ist keine erneute Übersetzung der Programme und Bibliotheken aus den Quellcodes nötig und weil auch kein zusätzlicher Instrumenten-Ballast in den Programmen steckt, beeinträchtigt auch kein Messcode den normalen Ablauf. Während des Profilings erhöht sich die Systemlast allerdings zwischen 1 und 10 Prozent. Dieser Wert hängt von der gewählten Sampling-Frequenz und der übrigen Systemauslastung ab.
Die zu untersuchenden Programme benötigen für die reine Performance-Messung nicht einmal Debug-Symbole (»gcc -g«). Um allerdings die ermittelten Messdaten spezifischen Stellen im Quellcode zuordnen zu können, ist das Kompilieren mit Debug-Symbolen sinnvoll. Da eine einzige Instanz das gesamte System, inklusive aufgerufener Programme, Hintergrundprozesse und Kernel protokolliert, kann der Entwickler auch Engpässe erkennen, die sich erst aus dem komplexen Zusammenspiel mehrerer Komponenten ergeben.
Viele CPUs unterstützt
Dank der Unterstützung durch Performance Counter lassen sich detaillierte Daten sogar zu hochgradig systeminternen Aspekten sammeln: L1-, L2, TLB-, Cache-Misses und -Hits, nicht ausgerichtete (misaligned) Referenzen, Pipeline Flush, Pipeline Stall, parallel ausgeführte Instruktionen (Pairing) und je nach Prozessor viele weitere Details. Die Features zur jeweiligen CPU sind der Herstellerreferenz [2] oder den Beschreibungen in »/usr/share/oprofile« zu entnehmen. Oprofile unterstützt nicht nur die 32-Bit-Prozessoren von Intel und AMD, sondern auch Alpha, IA-64, PowerPC, Mips sowie Xscale-ARM-CPUs.
Neben dem Kernelcode zum Erzeugen der Events besteht Oprofile aus einem Userspace-Daemon, der die Daten einsammelt und zur späteren Analyse speichert. Tools zur Analyse der Daten sind im Paket ebenfalls enthalten. Sie können die Ereignisse sowohl einzelnen Assembler-Instruktionen wie auch C- und C++-Code zuordnen und in diesen als Kommentar anfügen. Das Oprofile-Paket enthält außerdem ein grafisches Frontend, das auf dem Qt-Toolkit basiert (Abbildung 1). Programmierer bei Red Hat haben rudimentären Oprofile-Support auch in die Eclipse-IDE integriert [3].

Abbildung 1: Die einfache grafische Oberfläche »oprof_start« lässt den Anwender aus den verfügbaren Event-Masken des Prozessors auswählen.
Konfiguration
Zu Beginn muss Oprofile initialisiert und konfiguriert werden. Die folgenden Befehle beenden bereits laufende Profiling-Prozesse und setzen vorher gesammelte Daten zurück:
opcontrol --shutdown opcontrol --reset
Anschließend gilt es, Oprofile zu konfigurieren und unter anderem das Sampling-Intervall sowie die Events, bei denen Daten gesammelt werden sollen, vorzugeben. Falls gewünscht, kann der Entwickler auch das Kernel-Image angeben, um eine Aufschlüsselung der Kernelsymbole zu erhalten:
opcontrol --setup --vmlinux=/boot/vmlinux --event=CPU_CLK_UNHALTED:500000:0:1:1 --separate=library
Die Option »–vmlinux« gibt die Datei mit dem Kernel-Image vor. Ohne ein solches erwartet Oprofile stattdessen die Option »–no-vmlinux«. Das im obigen Beispiel spezifizierte Event »CPU_CLK_UNHALTED« steht für den CPU-Clock-Zyklus, der aktiv ist, wenn die CPU nicht angehalten ist. Die Befehlszeile legt fest, dass Oprofile jedes 500000ste Event protokolliert, bei 2 GHz erhebt es also 4000 Male pro Sekunde einen Datensatz. Die nachfolgenden Werte haben je nach Event eine andere Bedeutung. So lässt sich zum Beispiel festgelegen, ob Events im Kernel oder im Userspace protokolliert werden sollen, hier mit »1:1« beides.
Eine vollständige Liste der im System verfügbaren Events liefert der Aufruf »opcontrol –list-events«. Den Prozess der Datenerfassung startet »opcontrol –start«. Jetzt kann der Entwickler einfach die Programme starten, deren Performance er analysieren möchte. Nach hinreichender Zeit stoppt er das Logging mit »opcontrol –shutdown«.
Auswertung
Zur Auswertung der gesammelten Daten bringt Oprofile zwei Tools mit, die diese Arbeit weitgehend erledigen. Beispielsweise liefert »oreport« einen Überblick, welche Programme während der Events zu welchem Anteil gelaufen sind:
opreport -t 0.5 --exclude-dependent
Dabei gibt »-t 0.5« den Schwellenwert an, zu welchem Prozentanteil der Gesamtlaufzeit ein Executable mindestens laufen muss, um noch in der Liste zu erscheinen. Die Option »–exclude-dependent« unterdrückt die Aufschlüsselung der Bibliotheken. Als Beispiel führt Listing 1 die nach obigem Kommando ausgegebenen Daten für die Übersetzung des Xterm-Quellcode durch den Paketmanager von T2 auf.
|
Listing 1: »opreport -t |
|---|
01 samples| %| 02 ------------------ 03 247599 47.3952 no-vmlinux 04 187116 35.8176 cc1 05 22943 4.3917 ld 06 20397 3.9044 bash 07 13553 2.5943 gawk 08 5598 1.0716 sort 09 3862 0.7393 bzip2 10 3561 0.6816 as 11 3188 0.6102 ccache 12 2735 0.5235 sed |
Am meisten CPU-Zeit (47 Prozent) hat also der Kernel verbraucht. 35 Prozent beanspruchte der C-Compiler »cc1«, danach folgen der Linker »ld«, die Shell »bash« sowie »awk« und »sort«, die in den T2-Skripten zur Aufbereitung von Informationen dienen. Nur 0,7 Prozent der Gesamtzeit hat das Entpacken des Bzip-2-Archivs beansprucht. Vernachlässigbar gering sind die Anteile des Assemblers »as«, des Compiler Cache »ccache« sowie der von »sed«.
Welche Teile des Kernels hier so viele Ressourcen verbrauchen, lässt sich mit der Vmlinux-Datei des Kernel-Build genauer untersuchen. Anhand einer solchen Auflistung kann der Entwickler sich nun gezielt Komponenten mit hohem Anteil zur detaillierteren Analyse herauspicken und sie optimieren.
In der Praxis
Um die weiteren Analysemöglichkeiten von Oprofile darzustellen, dient als Beispiel ein isolierteres Problem: das Schärfen eines Bildes mit Imagemagick. Das Tool Convert soll eine 26 MByte große Tiff-Datei mit 16 Bit pro Farbkanal mit dem Unsharp-Mask-Filter bearbeiten:
convert -unsharp 4 test.tif test.jpg
Das dauert selbst auf einem AMD Turion 64 mit 1,6 GHz immerhin 4,4 Sekunden. Ohne »–exclude-dependent« listet »oreport« detailliert die Event-Anteile der jeweiligen Bibliotheken auf. Um sich auf die zu optimierende Komponente zu konzentrieren, lässt sich die Ausgabe durch die Angabe eines Binary eingrenzen. Listing 2 zeigt das Ergebnis.
|
Listing 2: »opreport -t 5 |
|---|
01 samples| %| 02 ------------------ 03 421144 56.2019 libMagick.so.10.0.2 04 140434 18.7410 libc-2.3.90.so 05 94560 12.6191 libjpeg.so.62.0.0 06 87580 11.6876 libtiff.so.3.7.4 |
Statistiken im Quellcode einblenden
Gut 56 Prozent der Zeit verbringt das Programm nach diesem Ergebnis in »libMagick.so«. Die konkreten Funktionen und Methoden, kurz die Symbole, liefert Opreport mit der Option »-l«, wie in Listing 3 zu sehen ist. Hat der Entwickler sich einmal für die zu analysierenden Symbole entschieden, kommentiert »opannotate« den Quellcode mit den jeweiligen Sample-Anteilen pro Source-Zeile (Listing 5).
Falls keine Sourcen vorhanden sind, bleibt noch die Option »–assembly«. Damit kommentiert das Programm den disassemblierte Maschinencode. Wenn bei komplexen Ausdrücken die Statistik pro Zeilennummer zu ungenau ist und die exakten Instruktionen ausfindig gemacht werden müssen, hilft es manchmal, die Parameter »–source« und »–assembly« zu kombinieren. Damit versieht das Tool detaillierte Assembler-Listings mit Orientierungshilfen. Einen Auszug daraus zeigt Listing 4.
|
Listing 3: »opreport -t |
|---|
01 samples % image name symbol name 02 249586 33.3074 libMagick.so.10.0.2 BlurImageChannel 03 140434 18.7410 libc-2.3.90.so (no symbols) 04 94560 12.6191 libjpeg.so.62.0.0 (no symbols) 05 87580 11.6876 libtiff.so.3.7.4 (no symbols) 06 40025 5.3414 libMagick.so.10.0.2 UnsharpMaskImageChannel |
|
Listing 4: »opannotate -t |
|---|
01 :static inline Quantum RoundToQuantum(const MagickRealType value)
02 :{
03 : if (value < 0.0)
04 912 0.0585 : 8d8b3: ucomisd %xmm1,%xmm2
05 966 0.0620 : 8d8b7: ja 8d8d0 <BlurImageChannel+0x660>
06 : return(0);
07 : if (value >= (MagickRealType) QuantumRange)
08 1009 0.0647 : 8d8b9: mov $0xffffffff,%eax
09 : 8d8be: ucomisd %xmm5,%xmm1
10 949 0.0609 : 8d8c2: jae 8d8d0 <BlurImageChannel+0x660>
11 1135 0.0728 : 8d8c4: addsd 669028(%rip),%xmm1
12 // ...
|
|
Listing 5: »opannotate -t |
|---|
01 MagickExport Image *BlurImageChannel(const Image *image,
02 : const ChannelType channel,const double radius,const double sigma,
03 : ExceptionInfo *exception)
04 :{ /* BlurImageChannel total: 249586 33.3074 */
05
06 // ...
07
08 12844 1.7140 : for (i=0; i < (long) width; i++)
09 : {
10 : alpha=1.0;
11 4237 0.5654 : if (((channel & OpacityChannel) != 0) &&
12 : (image->matte != MagickFalse))
13 : {
14 : alpha=((MagickRealType) QuantumRange-pixels[i].opacity)/
15 : QuantumRange;
16 482 0.0643 : pixel.opacity+=(*k)*pixels[i].opacity;
17 : }
18 4345 0.5798 : if ((channel & RedChannel) != 0)
19 21447 2.8621 : pixel.red+=(*k)*alpha*pixels[i].red;
20 4473 0.5969 : if ((channel & GreenChannel) != 0)
21 14589 1.9469 : pixel.green+=(*k)*alpha*pixels[i].green;
22 4469 0.5964 : if ((channel & BlueChannel) != 0)
23 17881 2.3862 : pixel.blue+=(*k)*alpha*pixels[i].blue;
24 9616 1.2833 : if (((channel & IndexChannel) != 0) &&
25 : (image->colorspace == CMYKColorspace))
26 : pixel.index+=(*k)*alpha*indexes[x+(pixels-p)+i];
27 10803 1.4417 : gamma+=(*k)*alpha;
28 : k++;
29 : }
|
Die Verwendung anderer Profiling Counter Events, etwa Cache Misses, verläuft analog, wie am obigen Beispiel beschrieben. Nach Angabe des entsprechenden Eventnamens mit »oprofile –setup –event«, kann der Entwickler »opreport« und »opannotate« entsprechend benutzen, um die gesammelten Daten seinem Quellcode zuzuordnen.
Oprofile liefert durch seine umfassende Datensammlung Aufschluss darüber, womit der oder die Prozessoren ihre Zeit verbringen. Diese Informationen erlauben es Entwicklern, Performance-Engpässe zu ermitteln und sich zur Analyse auf jene eng eingegrenzten Bereiche zu konzentrieren, die besonders viel Rechenzeit beanspruchen oder einen Engpass bilden.
Klassenbester
Im Gegensatz zu entsprechenden anderen Tools für solche Arbeiten, etwa Systemtap [4], ist Oprofile so weit ausgereift, dass es stabil läuft und verlässliche Daten liefert, die Linux-Programmierern als Grundlage zur Optimierung dienen können. Wenn mit Hilfe von Oprofile problematische Stellen im Code ausgemacht sind, ist es hilfreich, zunächst einen Blick in die Optimierungsanleitung von Intel zu werfen [2], bevor man sich blindlings an Änderungen der eingesetzten Algorithmen macht. (ofr)
|
Infos |
|---|
|
[1] Oprofile: [http://oprofile.sourceforge.net] [2] Intel Architecture Optimization Manual: [http://www.intel.com/design/pentium/MANUALS/24281603.pdf] [3] Eclipse Oprofile: [http://sourceforge.net/projects/eclipseoprofile] [4] Systemtap: [http://sourceware.org/systemtap] |
|
Der Autor |
|---|
|
René Rebe studiert Informatik an der TFH-Berlin und hat Linux leider erst im Jahr 1997 entdeckt. Er arbeitet bei Open-Source-Projekten wie T2 und Sane mit. |






