it-swarm.com.de

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

$ 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?

1559
Iraimbilanja

Echtzeit-, Benutzer- und Sys-Prozesszeitstatistik

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

  • Real ist die Uhrzeit der Wanduhr - Zeit vom Beginn bis zum Ende des Anrufs. Dies ist die gesamte verstrichene Zeit einschließlich der Zeitscheiben, die von anderen Prozessen verwendet werden, und der Zeit, die der Prozess blockiert (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 aufgewendet wird. Dies ist nur die tatsächliche CPU-Zeit, die zur Ausführung des Prozesses benötigt wird. Andere Prozesse und die Zeit, in der der Prozess blockiert ist, zählen nicht zu dieser Zahl.

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

User+Sys gibt an, wie viel CPU-Zeit Ihr Prozess tatsächlich verbraucht hat. Beachten Sie, dass dies für alle CPUs gilt. Wenn der Prozess also mehrere Threads enthält (und dieser Prozess auf einem Computer mit mehr als einem Prozessor ausgeführt wird), kann die von Real angegebene Wanduhrzeit möglicherweise überschritten werden (was normalerweise der Fall ist). . Beachten Sie, dass diese Zahlen in der Ausgabe die User und Sys Zeit aller untergeordneten Prozesse (und ihrer Nachkommen) enthalten, auch wenn sie hätten gesammelt werden können, z. von wait(2) oder waitpid(2), obwohl die zugrunde liegenden Systemaufrufe die Statistiken für den Prozess und seine untergeordneten Elemente separat zurückgeben.

Herkunft der von time (1) gemeldeten Statistiken

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

Auf einem Multiprozessor-Computer kann die verstrichene Zeit eines Multithread-Prozesses oder eines untergeordneten Prozesses kürzer sein als die gesamte CPU-Zeit, da möglicherweise verschiedene Threads oder Prozesse parallel ausgeführt werden. Die angegebenen Zeitstatistiken stammen außerdem aus unterschiedlichen Quellen, sodass die für sehr kurz laufende Aufgaben erfassten Zeiten Rundungsfehlern unterliegen können, wie das Beispiel auf dem Originalposter zeigt.

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

Unter Unix oder einem Betriebssystem mit geschütztem Speicher bezieht sich "Kernel" oder "Supervisor" mode auf einen privilegierten Modus , den die CPU verwenden kann arbeiten in. Bestimmte privilegierte Aktionen, die die Sicherheit oder Stabilität beeinträchtigen könnten, können nur ausgeführt werden, wenn die CPU in diesem Modus arbeitet. 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 user-mode code dies nicht (aus gutem Grund), obwohl es Shared Memory vom Kernel anfordern kann, der könnte von mehr als einem 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 zu verwenden.

Der privilegierte Modus wird normalerweise als "Kernel" -Modus bezeichnet, da der Kernel von der CPU ausgeführt wird, die in diesem Modus ausgeführt wird. Um in den Kernel-Modus zu wechseln, müssen Sie eine bestimmte Anweisung (oft als trap bezeichnet) absetzen, die die CPU in den Kernel-Modus versetzt und führt Code von einem bestimmten Ort in einer Sprungtabelle aus. Aus Sicherheitsgründen können Sie nicht in den Kernelmodus wechseln und beliebigen Code ausführen. Die Traps werden über eine Adressentabelle verwaltet, die nicht vorhanden sein kann geschrieben, es sei denn, die CPU läuft im Supervisor-Modus. Sie fangen mit einer expliziten Trap-Nummer ein 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) haben eine Benutzermodus-Komponente, die Sie 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 immer noch Code, der im Benutzermodus ausgeführt wird. Es ist auch durchaus möglich, von jedem Benutzerbereichscode aus direkt einen Trap in den Kernel-Modus zu setzen, obwohl Sie möglicherweise ein Snippet der Assembler-Sprache schreiben müssen, um die Register für den Aufruf korrekt einzurichten.

Mehr über 'sys'

Es gibt Dinge, die Ihr Code im Benutzermodus nicht ausführen kann - beispielsweise die Zuweisung von Speicher oder den Zugriff auf Hardware (Festplatte, Netzwerk usw.). Diese stehen unter der Aufsicht des Kernels und können nur von ihm ausgeführt werden. Einige Operationen wie malloc oder fread/fwrite rufen diese Kernelfunktionen auf und diese zählen dann als 'sys'-Zeit. Leider ist das nicht so einfach: "Jeder Anruf bei malloc wird in sys-Zeit gezählt." Der Aufruf von malloc führt eine eigene Verarbeitung durch (wird immer noch in Benutzerzeit gezählt) und ruft dann möglicherweise irgendwo auf dem Weg die Funktion im Kernel auf (wird in Syszeit gezählt). Nachdem Sie vom Kernel-Aufruf zurückgekehrt sind, verbleibt noch etwas Zeit in 'user' und dann kehrt malloc zu Ihrem Code zurück. Wann der Wechsel stattfindet und wie viel davon im Kernel-Modus ausgegeben wird, können Sie nicht sagen. Dies hängt von der Implementierung der Bibliothek ab. Andere scheinbar unschuldige Funktionen könnten auch malloc und dergleichen im Hintergrund verwenden, was dann wieder einige Zeit in 'sys' haben wird.

Um die akzeptierte Antwort zu erweitern, wollte ich nur einen weiteren Grund angeben, warum realuser + sys.

Beachten Sie, dass real die tatsächlich verstrichene Zeit darstellt, während user und sys die CPU-Ausführungszeit darstellen. Infolgedessen kann auf einem Multicore-System die user und/oder sys -Zeit (sowie deren Summe) tatsächlich überschreiten die Echtzeit. Beispielsweise erhalte ich in einer Java -Anwendung, die ich für die Klasse ausführe, die folgenden Werte:

real    1m47.363s
user    2m41.318s
sys     0m4.013s
251
lensovet

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

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 ist, da möglicherweise mehrere Prozessoren gleichzeitig arbeiten.

26
varun

Beispiele für minimal ausführbares POSIX C

Um die Dinge konkreter zu machen, möchte ich einige extreme Fälle von time mit 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 getestet 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 16 GiB).

Schlaf

Nicht beschäftigter Schlaf zählt weder in user noch in sys, nur in real.

Zum Beispiel ein Programm, das für eine Sekunde schläft:

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

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

GitHub upstream .

gibt so etwas aus wie:

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

Dasselbe gilt für Programme, die blockiert werden, wenn IO verfügbar wird.

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 upstream .

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 kann time Ihnen helfen, zwischen CPU- und IO gebundenen Programmen zu unterscheiden: Was bedeuten die Begriffe "CPU-gebunden" und "E/A-gebunden"?

Mehrere Threads

Im folgenden Beispiel werden niters Iterationen mit unnötiger CPU-Last für nthreads Threads 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:

enter image description here

Plotdaten .

In der Grafik sehen wir Folgendes:

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

  • bei 2 Kernen hat 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 die Wand gleich blieb.

  • dies dauert bis zu 8 Threads, was meiner Anzahl von Hyperthreads in meinem Computer entspricht.

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

    Das Verhältnis Plateaus an dieser Stelle.

Sys schwere Arbeit mit sendfile

Die schwerste sys-Auslastung, die ich finden konnte, war die Verwendung von sendfile, die eine Dateikopieroperation im Kernelspeicher ausführt: Kopieren einer Datei auf eine vernünftige, sichere und effiziente Weise

Also stellte ich mir vor, dass dieser In-Kernel memcpy eine 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 upstream .

das gibt im Grunde meistens Systemzeit wie erwartet:

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

Ich war auch neugierig zu sehen, ob time zwischen Systemaufrufen verschiedener Prozesse unterscheiden würde, also habe ich versucht:

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 sys-Zeit ist für beide ungefähr gleich wie für einen einzelnen Prozess, aber die Wall-Zeit ist größer, da die Prozesse wahrscheinlich um Lesezugriff auf die Festplatte konkurrieren.

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

Bash-Quellcode

Wenn Sie unter Ubuntu nur time <cmd> ausführen, wird das Schlüsselwort Bash verwendet.

type time

welche Ausgänge:

time is a Shell keyword

Also greifen wir auf den Quellcode von Bash 4.19 für die Ausgabezeichenfolge zu:

git grep '"user\b'

was uns zu execute_cmd.c function time_command führt, das verwendet:

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

dies sind alle Linux-Systemaufrufe und POSIX-Funktionen .

GNU Coreutils-Quellcode

Wenn wir es nennen als:

/usr/bin/time

dann wird die GNU Coreutils-Implementierung verwendet.

Dieses ist ein bisschen komplexer, aber die relevante Quelle scheint bei resuse.c zu sein und es tut:

  • ein Nicht-POSIX-BSD wait3 -Aufruf, falls dieser verfügbar ist
  • times andernfalls gettimeofday

Real zeigt die Gesamtdurchlaufzeit für einen Prozess an. während User die Ausführungszeit für benutzerdefinierte Anweisungen anzeigt und Sys die Zeit für die Ausführung von Systemaufrufen ist!

Echtzeit beinhaltet auch die Wartezeit (Wartezeit für I/O etc.)

14
susenj