Was bedeuten "real", "user" und "sys" in der Ausgabe der Zeit (1)?

1749
$ time foo
real        0m0.003s
user        0m0.000s
sys         0m0.004s
$

Was bedeuten "real", "user" und "sys" in der Ausgabe der Zeit?

Welches ist beim Benchmarking meiner App sinnvoll?

Rayryeng
quelle
2
Wie kann ich nur auf einen von ihnen zugreifen? zum Beispiel nur in Echtzeit?
Mojtaba Ahmadi
1
@ConcernedOfTunbridgeWells
Mojtaba Ahmadi
2
@ Casillass Real - stackoverflow.com/questions/2408981/…
ConcernedOfTunbridgeWells
7
Wenn Ihr Programm so schnell beendet wird, ist keines davon von Bedeutung. Es ist alles nur ein Startaufwand. Wenn Sie das gesamte Programm messen möchten time, lassen Sie es etwas tun, das mindestens eine Sekunde dauert.
Peter Cordes
5
Es ist wirklich wichtig zu beachten, dass dies timeein Bash-Schlüsselwort ist. So Typisierung man timeist nicht Ihnen eine man - Seite für die Bash geben time, sondern es gibt das Manpage /usr/bin/time. Das hat mich gestolpert.
irritable_phd_syndrom

Antworten:

2062

Real-, User- und Sys-Prozesszeitstatistiken

Eines dieser Dinge ist nicht wie das andere. Real bezieht sich auf die tatsächlich verstrichene Zeit; Benutzer und System beziehen sich auf die CPU-Zeit, die nur vom Prozess verwendet wird.

  • Real ist die Wanduhrzeit - Zeit vom Beginn bis zum Ende des Anrufs. Dies ist die gesamte verstrichene Zeit, einschließlich der von anderen Prozessen verwendeten Zeitscheiben und der Zeit, die der Prozess blockiert verbringt (z. B. wenn er auf den Abschluss der E / A wartet).

  • Benutzer ist die CPU-Zeit, die im Benutzermoduscode (außerhalb des Kernels) innerhalb des Prozesses verbracht wird. Dies ist nur die tatsächliche CPU-Zeit, die zur Ausführung des Prozesses verwendet wird. Andere Prozesse und die Zeit, die der Prozess blockiert verbringt, zählen nicht für diese Zahl.

  • Sys ist die CPU-Zeit, die im Kernel innerhalb des Prozesses verbracht wird. Dies bedeutet, dass die CPU-Zeit, die für Systemaufrufe im Kernel aufgewendet wird, ausgeführt wird , im Gegensatz zum Bibliothekscode, der noch im Benutzerbereich ausgeführt wird. Wie bei 'Benutzer' ist dies nur die vom Prozess verwendete CPU-Zeit. Im Folgenden finden Sie eine kurze Beschreibung des Kernel-Modus (auch als "Supervisor" -Modus bezeichnet) und des Systemaufrufmechanismus.

User+SysHier erfahren Sie, wie viel CPU-Zeit Ihr Prozess tatsächlich verwendet hat. Beachten Sie, dass dies für alle CPUs gilt. Wenn der Prozess also mehrere Threads hat (und dieser Prozess auf einem Computer mit mehr als einem Prozessor ausgeführt wird), kann er möglicherweise die von Real(normalerweise auftretende) Wanduhrzeit überschreiten . Beachten Sie, dass in der Ausgabe diese Zahlen die umfassen Userund SysZeit aller Child - Prozesse (und ihre Nachkommen) , als auch , wenn sie erhoben wurden , könnten zB durch wait(2)oder waitpid(2), obwohl die zugrunde liegenden Systemaufrufe die Statistiken für den Prozess und seine Kinder getrennt zurück.

Ursprung der Statistiken von time (1)

Die von gemeldeten Statistiken timestammen aus verschiedenen Systemaufrufen. 'User' und 'Sys' kommen je nach System von wait (2)( POSIX ) oder times (2)( POSIX ). 'Real' wird aus einer Start- und Endzeit berechnet, die aus dem gettimeofday (2)Anruf ermittelt wurde. Abhängig von der Version des Systems können auch verschiedene andere Statistiken wie die Anzahl der Kontextwechsel erfasst werden time.

Auf einem Multiprozessor-Computer kann ein Multithread-Prozess oder ein Prozess, der untergeordnete Elemente teilt, eine verstrichene Zeit haben, die kleiner als die gesamte CPU-Zeit ist, da verschiedene Threads oder Prozesse parallel ausgeführt werden können. Außerdem stammen die gemeldeten Zeitstatistiken aus unterschiedlichen Quellen, sodass Zeiten, die für sehr kurze Aufgaben aufgezeichnet wurden, Rundungsfehlern unterliegen können, wie das Beispiel auf dem Originalplakat zeigt.

Eine kurze Einführung in den Kernel vs. User-Modus

Unter Unix oder einem Betriebssystem mit geschütztem Speicher bezieht sich der Modus "Kernel" oder "Supervisor" auf einen privilegierten Modus , in dem die CPU arbeiten kann. Bestimmte privilegierte Aktionen, die die Sicherheit oder Stabilität beeinträchtigen können, können nur ausgeführt werden, wenn die CPU in Betrieb ist dieser Modus; Diese Aktionen stehen dem Anwendungscode nicht zur Verfügung. Ein Beispiel für eine solche Aktion könnte die Manipulation der MMU sein , um Zugriff auf den Adressraum eines anderen Prozesses zu erhalten. Normalerweise kann der Benutzermoduscode dies nicht tun (aus gutem Grund), obwohl er gemeinsam genutzten Speicher vom Kernel anfordern kann , was möglich istdurch mehr als einen Prozess gelesen oder geschrieben werden. In diesem Fall wird der gemeinsam genutzte Speicher über einen sicheren Mechanismus explizit vom Kernel angefordert, und beide Prozesse müssen explizit eine Verbindung zu ihm herstellen, um ihn verwenden zu können.

Der privilegierte Modus wird normalerweise als "Kernel" -Modus bezeichnet, da der Kernel von der in diesem Modus ausgeführten CPU ausgeführt wird. Um in den Kernelmodus zu wechseln, müssen Sie eine bestimmte Anweisung (oft als Trap bezeichnet ) ausgeben , die die CPU auf die Ausführung im Kernelmodus umschaltet und Code von einem bestimmten Ort in einer Sprungtabelle ausführt. Aus Sicherheitsgründen können Sie nicht in den Kernelmodus wechseln und beliebigen Code ausführen. Die Traps werden über eine Adresstabelle verwaltet, in die nur geschrieben werden kann, wenn die CPU im Supervisor-Modus ausgeführt wird. Sie fangen mit einer expliziten Trap-Nummer und die Adresse wird in der Sprungtabelle nachgeschlagen. Der Kernel hat eine endliche Anzahl kontrollierter Einstiegspunkte.

Die 'System'-Aufrufe in der C-Bibliothek (insbesondere die in Abschnitt 2 der Manpages beschriebenen) verfügen über eine Benutzermodus-Komponente, die Sie tatsächlich von Ihrem C-Programm aus aufrufen. Hinter den Kulissen können sie einen oder mehrere Systemaufrufe an den Kernel senden, um bestimmte Dienste wie E / A auszuführen, aber sie haben auch weiterhin Code, der im Benutzermodus ausgeführt wird. Es ist auch durchaus möglich, von jedem Benutzerbereichscode aus direkt einen Trap für den Kernelmodus auszugeben, obwohl Sie möglicherweise einen Ausschnitt der Assemblersprache schreiben müssen, um die Register für den Aufruf korrekt einzurichten.

Mehr über 'sys'

Es gibt Dinge, die Ihr Code im Benutzermodus nicht tun kann - beispielsweise das Zuweisen von Speicher oder den Zugriff auf Hardware (Festplatte, Netzwerk usw.). Diese stehen unter der Aufsicht des Kernels und können von ihm allein ausgeführt werden. Einige Operationen wie mallocoder fread/ fwriterufen diese Kernelfunktionen auf und das zählt dann als 'sys'-Zeit. Leider ist es nicht so einfach wie "jeder Anruf bei malloc wird in 'sys' Zeit gezählt". Der Aufruf mallocvon führt eine eigene Verarbeitung durch (immer noch in der 'Benutzer'-Zeit gezählt) und ruft dann irgendwo auf dem Weg die Funktion im Kernel auf (in' sys'-Zeit gezählt). Nach der Rückkehr vom Kernel-Aufruf bleibt in 'user' noch etwas Zeitmallocwird zu Ihrem Code zurückkehren. Wann der Wechsel stattfindet und wie viel davon im Kernel-Modus ausgegeben wird, kann man nicht sagen. Dies hängt von der Implementierung der Bibliothek ab. Es könnten auch andere scheinbar unschuldige Funktionen mallocund dergleichen im Hintergrund verwendet werden, die dann wieder etwas Zeit in 'sys' haben werden.

ConcernedOfTunbridgeWells
quelle
15
Zählt die Zeit, die untergeordnete Prozesse verbringen, zu real / sys?
Ron
1
@ron - Laut der Linux-Manpage werden die 'c'-Zeiten mit den Prozesszeiten aggregiert, also denke ich, dass dies der Fall ist. Die Eltern- und Kinderzeiten sind jedoch getrennt von den Zeiten (2) verfügbar. Ich denke, die Solaris / SysV-Version von time (1) macht etwas Ähnliches.
ConcernedOfTunbridgeWells
3
Mit User + Sys können Sie die CPU-Auslastung eines Prozesses messen. Sie können es verwenden, um die Leistung zu bewerten. Dies ist besonders nützlich für Multithread-Code, bei dem möglicherweise mehr als ein CPU-Kern an einer Berechnung arbeitet.
ConcernedOfTunbridgeWells
1
Trotzdem nicht genau zum Thema: Das Ausführen von "\ time <cmd>" ist interessant - es bietet mehr Details: (verzeihen Sie schlechte Formatierung im Kommentar): $ time ps PID TTY TIME CMD 9437 pts / 19 00:00:00 bash 11459 pts / 19 00:00:00 ps real 0m0.025s user 0m0.004s sys 0m0.018s $ \ time ps PID TTY TIME CMD 9437 pts / 19 00:00:00 bash 11461 pts / 19 00:00:00 time 11462 pts / 19 00:00:00 ps 0.00user 0.01system 0: 00.02elapsed 95% CPU (0avgtext + 0avgdata 2160maxresident) k 0inputs + 0outputs (0major + 103minor) pagefaults 0swaps $
kaiwan
1
(Im vorherigen Kommentar sind die Zeichen ausgegangen) Also: Mehr Details? Verwenden Sie perf [1], [2]. [1] perf.wiki.kernel.org/index.php/Main_Page [2] brendangregg.com/perf.html
kaiwan
286

Um die akzeptierte Antwort zu erweitern , wollte ich nur einen weiteren Grund realangeben, warum ≠ user+ sys.

Beachten Sie, dass dies realdie tatsächlich verstrichene Zeit darstellt, während userund die sysWerte die CPU-Ausführungszeit darstellen. Infolgedessen kann auf einem Multicore-System die userund / oder sysZeit (sowie deren Summe) tatsächlich die Echtzeit überschreiten . In einer Java-App, die ich für die Klasse ausführe, erhalte ich beispielsweise folgende Werte:

real    1m47.363s
user    2m41.318s
sys     0m4.013s
Lensovet
quelle
11
Ich hatte mich immer darüber gewundert. Da ich weiß, dass meine Programme Single-Threaded sind, muss der Unterschied zwischen Benutzer und Echtzeit der VM-Overhead sein, richtig?
Quantum7
9
nicht unbedingt; Die Sun JVM auf Solaris-Computern sowie die JVM von Apple unter Mac OS X können selbst in Single-Threaded-Apps mehr als einen Kern verwenden. Wenn Sie ein Beispiel für einen Java-Prozess ausführen, werden Sie feststellen, dass Dinge wie die Speicherbereinigung in separaten Threads ausgeführt werden (und einige andere Dinge, an die ich mich nicht mehr erinnern kann). Ich weiß nicht, ob Sie diesen "VM-Overhead" wirklich nennen wollen.
Lensovet
4
Ich denke, die Anzahl der Up-Votes hat Ihnen jetzt genug Ruf gegeben: D. Was denkst du über realÜberschreitung userund sysGesamtheit? Betriebssystem-Overhead wie Thread-Kontextwechsel kann sein?
Muhammad Gelbana
19
Ein weiteres potenzielles Problem könnte E / A sein: Wenn Ihre Anwendung viel Zeit damit verbringt, auf den Empfang einer Datei oder eines Streams zu warten, würde die Echtzeit offensichtlich die Benutzer- / Systemzeit erheblich überschreiten, da während des Wartens auf den Zugriff keine CPU-Zeit verwendet wird zu einer Datei oder ähnlichem.
Lensovet
1
@MuhammadGelbana - Dies kann passieren, wenn die Ausführung der Anwendung aus irgendeinem Grund blockiert ist. Wenn es beispielsweise auf E / A-, IPC- oder Socket-Verbindungen wartet, bleibt es inaktiv und sammelt keine CPU-Zeit, bis der blockierende Aufruf zurückkehrt.
ConcernedOfTunbridgeWells
41

real : Die tatsächliche Zeit, die für die Ausführung des Prozesses von Anfang bis Ende aufgewendet wurde, als würde sie von einem Menschen mit einer Stoppuhr gemessen

Benutzer : Die kumulierte Zeit, die alle CPUs während der Berechnung verbracht haben

sys : Die kumulierte Zeit, die alle CPUs für systembezogene Aufgaben wie die Speicherzuweisung aufgewendet haben.

Beachten Sie, dass user + sys manchmal größer als real sein kann, da mehrere Prozessoren parallel arbeiten können.

varun
quelle
sysWird die CPU-Zeit für Systemaufrufe (und Seitenfehlerbehandlungsroutinen?) aufgewendet?
Peter Cordes
1
realwird oft als "Wanduhr" bezeichnet.
Peter Cordes
30

Minimal ausführbare POSIX C-Beispiele

Um die Dinge konkreter zu machen, möchte ich einige extreme Fälle timemit einigen minimalen C-Testprogrammen veranschaulichen .

Alle Programme können kompiliert und ausgeführt werden mit:

gcc -ggdb3 -o main.out -pthread -std=c99 -pedantic-errors -Wall -Wextra main.c
time ./main.out

und wurden in Ubuntu 18.10, GCC 8.2.0, glibc 2.28, Linux-Kernel 4.18, ThinkPad P51-Laptop, Intel Core i7-7820HQ-CPU (4 Kerne / 8 Threads), 2x Samsung M471A2K43BB1-CRC-RAM (2x 16GiB) getestet.

Schlaf

Nicht beschäftigter Schlaf zählt weder in usernoch sysnur real.

Zum Beispiel ein Programm, das eine Sekunde lang schläft:

#define _XOPEN_SOURCE 700
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    sleep(1);
    return EXIT_SUCCESS;
}

GitHub stromaufwärts .

gibt so etwas aus wie:

real    0m1.003s
user    0m0.001s
sys     0m0.003s

Gleiches gilt für Programme, die auf E / A blockiert sind und verfügbar werden.

Das folgende Programm wartet beispielsweise darauf, dass der Benutzer ein Zeichen eingibt und die Eingabetaste drückt:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    printf("%c\n", getchar());
    return EXIT_SUCCESS;
}

GitHub stromaufwärts .

Und wenn Sie ungefähr eine Sekunde warten, wird genau wie im Schlafbeispiel Folgendes ausgegeben:

real    0m1.003s
user    0m0.001s
sys     0m0.003s

Aus diesem Grund timekönnen Sie zwischen CPU- und E / A-gebundenen Programmen unterscheiden: Was bedeuten die Begriffe "CPU-gebunden" und "E / A-gebunden"?

Mehrere Threads

Im folgenden Beispiel werden nitersIterationen nutzloser, rein CPU-gebundener Arbeit an nthreadsThreads ausgeführt:

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

uint64_t niters;

void* my_thread(void *arg) {
    uint64_t *argument, i, result;
    argument = (uint64_t *)arg;
    result = *argument;
    for (i = 0; i < niters; ++i) {
        result = (result * result) - (3 * result) + 1;
    }
    *argument = result;
    return NULL;
}

int main(int argc, char **argv) {
    size_t nthreads;
    pthread_t *threads;
    uint64_t rc, i, *thread_args;

    /* CLI args. */
    if (argc > 1) {
        niters = strtoll(argv[1], NULL, 0);
    } else {
        niters = 1000000000;
    }
    if (argc > 2) {
        nthreads = strtoll(argv[2], NULL, 0);
    } else {
        nthreads = 1;
    }
    threads = malloc(nthreads * sizeof(*threads));
    thread_args = malloc(nthreads * sizeof(*thread_args));

    /* Create all threads */
    for (i = 0; i < nthreads; ++i) {
        thread_args[i] = i;
        rc = pthread_create(
            &threads[i],
            NULL,
            my_thread,
            (void*)&thread_args[i]
        );
        assert(rc == 0);
    }

    /* Wait for all threads to complete */
    for (i = 0; i < nthreads; ++i) {
        rc = pthread_join(threads[i], NULL);
        assert(rc == 0);
        printf("%" PRIu64 " %" PRIu64 "\n", i, thread_args[i]);
    }

    free(threads);
    free(thread_args);
    return EXIT_SUCCESS;
}

GitHub Upstream + Plotcode .

Dann zeichnen wir wall, user und sys als Funktion der Anzahl der Threads für feste 10 ^ 10 Iterationen auf meiner 8-Hyperthread-CPU:

Geben Sie hier die Bildbeschreibung ein

Plotdaten .

Aus der Grafik sehen wir Folgendes:

  • Bei einer CPU-intensiven Single-Core-Anwendung sind Wand und Benutzer ungefähr gleich

  • Bei 2 Kernen beträgt der Benutzer ungefähr 2x Wand, was bedeutet, dass die Benutzerzeit über alle Threads hinweg gezählt wird.

    Benutzer im Grunde verdoppelt, und während Wand gleich blieb.

  • Dies setzt bis zu 8 Threads fort, was meiner Anzahl von Hyperthreads in meinem Computer entspricht.

    Nach 8 beginnt auch die Wand zu wachsen, da wir keine zusätzlichen CPUs haben, um in einer bestimmten Zeit mehr Arbeit zu leisten!

    Das Verhältnis Plateaus an diesem Punkt.

Beachten Sie, dass dieses Diagramm nur so klar und einfach ist, weil die Arbeit rein CPU-gebunden ist: Wenn es speichergebunden wäre, würden wir mit weniger Kernen viel früher einen Leistungsabfall erzielen, da die Speicherzugriffe einen Engpass darstellen würden, wie unter Was gezeigt bedeuten die Begriffe "CPU-gebunden" und "E / A-gebunden"?

Sys schwere Arbeit mit sendfile

Die schwerste Systemauslastung, die ich finden konnte, war die Verwendung von sendfile, die einen Dateikopiervorgang im Kernelbereich ausführt : Kopieren Sie eine Datei auf eine vernünftige, sichere und effiziente Weise

Also stellte ich mir vor, dass dieser In-Kernel memcpyeine CPU-intensive Operation sein wird.

Zuerst initialisiere ich eine große 10GiB-Zufallsdatei mit:

dd if=/dev/urandom of=sendfile.in.tmp bs=1K count=10M

Führen Sie dann den Code aus:

#define _GNU_SOURCE
#include <assert.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv) {
    char *source_path, *dest_path;
    int source, dest;
    struct stat stat_source;
    if (argc > 1) {
        source_path = argv[1];
    } else {
        source_path = "sendfile.in.tmp";
    }
    if (argc > 2) {
        dest_path = argv[2];
    } else {
        dest_path = "sendfile.out.tmp";
    }
    source = open(source_path, O_RDONLY);
    assert(source != -1);
    dest = open(dest_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
    assert(dest != -1);
    assert(fstat(source, &stat_source) != -1);
    assert(sendfile(dest, source, 0, stat_source.st_size) != -1);
    assert(close(source) != -1);
    assert(close(dest) != -1);
    return EXIT_SUCCESS;
}

GitHub stromaufwärts .

was im Grunde meistens Systemzeit wie erwartet gibt:

real    0m2.175s
user    0m0.001s
sys     0m1.476s

Ich war auch neugierig, ob ich timezwischen Systemaufrufen verschiedener Prozesse unterscheiden würde, also versuchte ich:

time ./sendfile.out sendfile.in1.tmp sendfile.out1.tmp &
time ./sendfile.out sendfile.in2.tmp sendfile.out2.tmp &

Und das Ergebnis war:

real    0m3.651s
user    0m0.000s
sys     0m1.516s

real    0m4.948s
user    0m0.000s
sys     0m1.562s

Die Systemzeit ist für beide ungefähr gleich wie für einen einzelnen Prozess, aber die Wandzeit ist größer, da die Prozesse wahrscheinlich um den Lesezugriff auf die Festplatte konkurrieren.

Es scheint also tatsächlich zu berücksichtigen, welcher Prozess eine bestimmte Kernelarbeit gestartet hat.

Bash-Quellcode

Wenn Sie nur time <cmd>unter Ubuntu arbeiten, verwenden Sie das Schlüsselwort Bash, wie aus folgendem ersichtlich ist:

type time

welche Ausgänge:

time is a shell keyword

Also grep wir die Quelle im Bash 4.19-Quellcode für die Ausgabezeichenfolge:

git grep '"user\b'

Dies führt uns zur Funktion execute_cmd.ctime_command , die Folgendes verwendet:

  • gettimeofday()und getrusage()wenn beide verfügbar sind
  • times() Andernfalls

All dies sind Linux-Systemaufrufe und POSIX-Funktionen .

GNU Coreutils Quellcode

Wenn wir es nennen als:

/usr/bin/time

Dann wird die Implementierung von GNU Coreutils verwendet.

Dieser ist etwas komplexer, aber die relevante Quelle scheint bei resuse.c zu sein und es tut:

  • ein Nicht-POSIX-BSD- wait3Aufruf, falls verfügbar
  • timesund gettimeofdaysonst
Ciro Santilli 冠状 病毒 审查 六四 事件 法轮功
quelle
14

Real zeigt die gesamte Bearbeitungszeit für einen Prozess an. während Benutzer die Ausführungszeit für benutzerdefinierte Anweisungen anzeigt und Sys für die Ausführung von Systemaufrufen vorgesehen ist!

Echtzeit beinhaltet auch die Wartezeit (die Wartezeit für E / A usw.)

susenj
quelle