Möchten Sie die Laufzeit eines Prozesses und vieles mehr analysieren? Der Linux-Befehl `time` liefert detaillierte Zeitstatistiken und ermöglicht es Ihnen, die von Ihren Programmen beanspruchten Ressourcen präzise zu überwachen.
Der Befehl ‚time‘ und seine Variationen
Es existieren zahlreiche Linux-Distributionen und unterschiedliche Unix-ähnliche Betriebssysteme. Jedes von ihnen verwendet eine spezifische Standard-Befehlsshell. Die gebräuchlichste Shell in modernen Linux-Distributionen ist die Bash-Shell, aber es gibt auch andere, wie die Z-Shell (zsh) und die Korn-Shell (ksh).
Diese Shells haben alle ihre eigene Implementierung des `time`-Befehls, entweder als integrierten Befehl oder als reserviertes Wort. Wenn Sie `time` in ein Terminal eingeben, nutzt die Shell ihren internen Befehl, anstatt die GNU-Version von `time` zu verwenden, die normalerweise Teil der Linux-Distribution ist.
Wir bevorzugen die GNU-Variante des Befehls `time`, da sie mehr Optionen und somit mehr Flexibilität bietet.
Welche Version von ‚time‘ wird genutzt?
Mit dem Befehl `type` können Sie die aktive Version von `time` ermitteln. `type` zeigt Ihnen, ob die Shell den Befehl intern verarbeitet oder an die GNU-Binärdatei weiterleitet.
Geben Sie im Terminal `type time` ein und bestätigen Sie mit der Eingabetaste.
type time
Hier sehen wir, dass `time` in der Bash-Shell ein reserviertes Wort ist. Dies bedeutet, dass die Bash-Shell standardmäßig ihren eigenen `time`-Befehl verwendet.
type time
Auch in der Z-Shell (zsh) ist `time` ein reserviertes Wort, weshalb standardmäßig die internen Shell-Routinen verwendet werden.
type time
In der Korn-Shell ist `time` ebenfalls ein Schlüsselwort, was dazu führt, dass auch hier anstelle der GNU-Version von `time` eine interne Routine zum Einsatz kommt.
Verwendung der GNU-Version von ‚time‘
Wenn Ihre Shell eine interne `time`-Funktion besitzt, müssen Sie explizit angeben, dass Sie die GNU-Version verwenden möchten. Dafür gibt es mehrere Methoden:
- Geben Sie den vollständigen Pfad zur Binärdatei an, z. B. `/usr/bin/time`. Sie können diesen Pfad mit `which time` ermitteln.
- Nutzen Sie den Befehl `command time`.
- Verwenden Sie einen Backslash, wie in `\time`.
Der Befehl `which time` liefert den Speicherort der Binärdatei.
Wir können dies testen, indem wir `/usr/bin/time` direkt als Befehl verwenden, um die GNU-Version auszuführen. Dies funktioniert, und der `time`-Befehl gibt eine Meldung aus, dass wir keine Befehlszeilenparameter angegeben haben, mit denen er arbeiten kann.
Auch die Eingabe `command time` funktioniert, und wir erhalten die gleichen Informationen von `time`. Der Befehl `command` weist die Shell an, den nachfolgenden Befehl nicht intern zu verarbeiten, sondern extern zu behandeln.
Die Verwendung eines Backslash vor dem Befehlsnamen entspricht der Nutzung von `command` vor dem Befehlsnamen.
Die einfachste Methode, die GNU-Version von `time` sicherzustellen, ist die Nutzung des Backslash.
\time
time
`time` ruft die Shell-Version auf, während `\time` die binäre Version verwendet.
Anwendung des Befehls ‚time‘
Lassen Sie uns einige Programme zeitlich messen. Wir verwenden hierzu zwei Programme namens `loop1` und `loop2`, die aus `loop1.c` und `loop2.c` erzeugt wurden. Sie sind inhaltlich nicht relevant, sondern dienen lediglich zur Demonstration der Auswirkungen von Code-Ineffizienz.
Dies ist der Quellcode von `loop1.c`. Die Länge der Zeichenkette wird außerhalb der beiden verschachtelten Schleifen ermittelt.
#include "stdio.h" #include "string.h" #include "stdlib.h" int main (int argc, char* argv[]) { int i, j, len, count=0; char szString[]="etoppc.com-etoppc.com-etoppc.com-etoppc.com-etoppc.com-etoppc.com"; // get length of string once, outside of loops len = strlen( szString ); for (j=0; jDer Quellcode von `loop2.c`. Hier wird die Länge der Zeichenkette in jedem Durchlauf der äußeren Schleife neu berechnet. Diese Ineffizienz sollte sich in den Messungen bemerkbar machen.
#include "stdio.h" #include "string.h" #include "stdlib.h" int main (int argc, char* argv[]) { int i, j, count=0; char szString[]="etoppc.com-etoppc.com-etoppc.com-etoppc.com-etoppc.com-etoppc.com"; for (j=0; jLassen Sie uns das Programm `loop1` starten und mit `time` seine Performance messen.
time ./loop1
Nun führen wir den gleichen Prozess mit `loop2` durch.
time ./loop2
Wir haben zwei Ergebnissätze erhalten, allerdings in einem eher unübersichtlichen Format. Daran können wir später etwas ändern. Zunächst wollen wir uns einige Informationen aus den Ergebnissen genauer ansehen.
Wenn Programme ausgeführt werden, wechseln sie zwischen zwei Ausführungsmodi: Benutzermodus und Kernelmodus.
Kurz gesagt, ein Prozess im Benutzermodus kann nicht direkt auf Hardware oder den Speicher außerhalb seines eigenen Adressraums zugreifen. Um auf diese Ressourcen zuzugreifen, muss der Prozess Anfragen an den Kernel senden. Wenn der Kernel die Anfrage genehmigt, wechselt der Prozess in den Kernelmodus, bis die Anfrage bearbeitet ist. Danach kehrt er wieder in den Benutzermodus zurück.
Die Ergebnisse von `loop1` zeigen, dass es 0,09 Sekunden im Benutzermodus verbracht hat. Die Zeit im Kernelmodus ist entweder null oder zu gering, um nach dem Runden registriert zu werden. Die Gesamtzeit betrug 0,1 Sekunden. `loop1` wurde während der Gesamtlaufzeit durchschnittlich 89% der CPU-Zeit zugewiesen.
Die Ausführung des ineffizienten Programms `loop2` dauerte dreimal länger. Die Gesamtzeit beträgt 0,3 Sekunden. Die Verarbeitungszeit im Benutzermodus betrug 0,29 Sekunden. Es wurde keine Zeit im Kernelmodus registriert. `loop2` wurden während seiner Ausführung durchschnittlich 96% der CPU-Zeit zugeteilt.
Ausgabe formatieren
Die Ausgabe von `time` lässt sich durch eine Formatzeichenkette anpassen. Die Formatzeichenkette kann Text und Formatbezeichner enthalten. Eine Liste der Formatbezeichner finden Sie in der Manpage für `time`. Jeder Formatbezeichner steht für eine bestimmte Information.
Beim Ausdruck der Zeichenkette werden die Formatbezeichner durch die tatsächlichen Werte ersetzt, die sie darstellen. Der Formatbezeichner für den Prozentsatz der CPU-Auslastung ist beispielsweise der Buchstabe `P`. Um `time` zu signalisieren, dass ein Formatbezeichner kein normaler Buchstabe ist, wird ihm ein Prozentzeichen vorangestellt, wie in `%P`. Betrachten wir ein Beispiel.
Die Option `-f` (Formatstring) wird genutzt, um `time` mitzuteilen, dass es sich beim folgenden Argument um eine Formatzeichenkette handelt.
Unsere Formatzeichenkette gibt zunächst die Zeichen "Programm:" und den Namen des Programms (sowie alle Befehlszeilenparameter) aus. Der Formatbezeichner `%C` steht für "Name und Befehlszeilenargumente des zeitlich gemessenen Befehls". Das `\n` bewirkt den Übergang zur nächsten Zeile.
Es gibt eine Vielzahl von Formatbezeichnern, bei denen die Groß- und Kleinschreibung beachtet werden muss. Achten Sie also auf die korrekte Eingabe.
Als Nächstes geben wir "Gesamtzeit:" gefolgt vom Wert der gesamten verstrichenen Zeit für diesen Programmlauf (dargestellt durch `%E`) aus.
Wir verwenden `\n`, um eine weitere neue Zeile zu erzeugen. Dann folgt "Benutzermodus (s)" mit dem Wert der im Benutzermodus verbrachten CPU-Zeit, gekennzeichnet durch `%U`.
Mit `\n` erzeugen wir eine weitere Zeile, in der wir die Kernel-Zeit ausgeben. Hierzu nutzen wir die Zeichen "Kernel Mode (s)" und den Formatbezeichner `%S` für die im Kernelmodus verbrachte CPU-Zeit.
Abschließend geben wir "nCPU:" gefolgt von einem `\n` für eine neue Zeile aus, um den Titel für den kommenden Datenwert zu kennzeichnen. Der Formatbezeichner `%P` gibt den durchschnittlichen Prozentsatz der CPU-Zeit an, die vom zeitlich gemessenen Prozess genutzt wird.
Die gesamte Formatzeichenkette wird in Anführungszeichen gesetzt. Wir hätten `\t` Zeichen einfügen können, um Tabulatoren in die Ausgabe zu setzen und die Werte besser auszurichten.
time -f "Program: %C\nTotal time: %E\nUser Mode (s) %U\nKernel Mode (s) %S\nCPU: %P" ./loop1
Ausgabe in eine Datei umleiten
Um die Zeiten Ihrer Tests festzuhalten, können Sie die Ausgabe von `time` in eine Datei umleiten. Verwenden Sie hierzu die Option `-o` (Ausgabe). Die Ausgabe Ihres Programms wird weiterhin im Terminal angezeigt. Nur die Ausgabe von `time` wird in die Datei umgeleitet.
Wir führen den Test erneut aus und speichern die Ausgabe in der Datei `test_results.txt`:
time -o test_results.txt -f "Program: %C\nTotal time: %E\nUser Mode (s) %U\nKernel Mode (s) %S\nCPU: %P" ./loop1cat test_results.txt
Die Programmausgabe von `loop1` wird im Terminal angezeigt und die Ergebnisse von `time` werden in die Datei `test_results.txt` geschrieben.
Um die nächsten Ergebnisse in derselben Datei zu erfassen, nutzen Sie die Option `-a` (anhängen):
time -o test_results.txt -a -f "Program: %C\nTotal time: %E\nUser Mode (s) %U\nKernel Mode (s) %S\nCPU: %P" ./loop2cat test_results.txt
Es sollte nun klar sein, warum wir den Formatbezeichner `%C` verwendet haben, um den Namen des Programms in der Formatstring-Ausgabe zu inkludieren.
Fazit
Der Befehl `time` ist ein nützliches Werkzeug für Programmierer und Entwickler zur Optimierung ihres Codes. Er ist aber auch für jeden Anwender geeignet, der bei jedem Programmstart etwas mehr über die Prozesse im Hintergrund erfahren möchte.