2244 Stimmen

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

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

Was tun real , user y sys in der Ausgabe der Zeit bedeuten?

Welches ist für das Benchmarking meiner Anwendung sinnvoll?

4 Stimmen

19 Stimmen

Wenn Ihr Programm so schnell beendet wird, ist nichts davon aussagekräftig, es ist alles nur Start-Overhead. Wenn Sie das ganze Programm messen wollen mit time und lassen Sie ihn etwas tun, das mindestens eine Sekunde dauert.

28 Stimmen

Es ist wirklich wichtig zu beachten, dass time ist ein Bash-Schlüsselwort. Wenn Sie also eingeben man time es pas eine Manpage für die bash time sondern gibt die Manpage für /usr/bin/time . Das hat mich aus dem Konzept gebracht.

2647voto

Echtzeit-, Benutzer- und Systemprozesszeitstatistiken

Das eine ist nicht wie das andere. Real bezieht sich auf die tatsächlich verstrichene Zeit; User und Sys beziehen sich auf die verwendete CPU-Zeit nur durch den Prozess.

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

  • Benutzer ist der Anteil der CPU-Zeit, der für Code im Benutzermodus (außerhalb des Kernels) aufgewendet wird innerhalb den Prozess. Dies ist nur die tatsächliche CPU-Zeit, die für die Ausführung des Prozesses verwendet wird. Andere Prozesse und die Zeit, in der der Prozess blockiert ist, werden bei dieser Zahl nicht berücksichtigt.

  • Sys ist die im Kernel des Prozesses verbrachte CPU-Zeit. Dies bedeutet, dass die für Systemaufrufe aufgewendete CPU-Zeit ausgeführt wird innerhalb des Kernels, im Gegensatz zu Bibliothekscode, der immer noch im User-Space läuft. Wie bei "user" handelt es sich nur um CPU-Zeit, die vom Prozess genutzt wird. Eine kurze Beschreibung des Kernel-Modus (auch als "Supervisor"-Modus bekannt) und des Systemaufrufmechanismus finden Sie weiter unten.

User+Sys sagt Ihnen, 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 hat (und dieser Prozess auf einem Computer mit mehr als einem Prozessor läuft), könnte er möglicherweise die Zeit überschreiten, die von Real (was normalerweise der Fall ist). Beachten Sie, dass diese Zahlen in der Ausgabe auch die User y Sys Zeit aller Kindprozesse (und ihrer Nachkommen) sowie den Zeitpunkt, zu dem sie hätten abgeholt werden können, z. B. durch wait(2) o waitpid(2) obwohl die zugrunde liegenden Systemaufrufe die Statistiken für den Prozess und seine Kinder getrennt zurückgeben.

Ursprünge der Statistiken, die von time (1)

Die Statistiken, die von time werden von verschiedenen Systemaufrufen gesammelt. User" und "Sys" stammen aus wait (2) ( POSIX ) oder times (2) ( POSIX ), je nach dem jeweiligen System. Real" wird aus einer Start- und Endzeit berechnet, die aus der gettimeofday (2) anrufen. Je nach Version des Systems können auch verschiedene andere Statistiken wie die Anzahl der Kontextwechsel von time .

Auf einem Multiprozessor-Rechner kann die verstrichene Zeit eines Prozesses mit mehreren Threads oder eines Prozesses, der sich von Kindern abspaltet, kleiner sein als die gesamte CPU-Zeit, da verschiedene Threads oder Prozesse parallel laufen können. Außerdem stammen die gemeldeten Zeitstatistiken aus unterschiedlichen Quellen, so dass die für sehr kurz laufende Tasks erfassten Zeiten Rundungsfehler aufweisen können, wie das vom ursprünglichen Poster angeführte Beispiel zeigt.

Eine kurze Einführung in den Kernel- und Benutzermodus

Unter Unix oder einem anderen Betriebssystem mit geschütztem Speicher, Kernel" oder "Supervisor Modus bezieht sich auf eine privilegierter Modus in dem die CPU arbeiten kann. Bestimmte privilegierte Aktionen, die die Sicherheit oder Stabilität beeinträchtigen könnten, können nur durchgeführt werden, wenn die CPU in diesem Modus arbeitet; diese Aktionen sind für den Anwendungscode nicht verfügbar. Ein Beispiel für eine solche Aktion könnte die Manipulation der MMU um Zugriff auf den Adressraum eines anderen Prozesses zu erhalten. Normalerweise, Benutzermodus Code kann dies (aus gutem Grund) nicht tun, obwohl er Folgendes anfordern kann gemeinsamer Speicher vom Kernel, der podría von mehr als einem Prozess gelesen oder geschrieben werden. In diesem Fall wird der gemeinsame Speicher explizit vom Kernel über einen sicheren Mechanismus angefordert, und beide Prozesse müssen sich explizit an ihn binden, um ihn nutzen zu können.

Der privilegierte Modus wird gewöhnlich als "Kernel"-Modus bezeichnet, da der Kernel von der in diesem Modus laufenden CPU ausgeführt wird. Um in den Kernel-Modus zu wechseln, müssen Sie eine bestimmte Anweisung (oft als Falle ), der die CPU in den Kernel-Modus versetzt und führt Code von einer bestimmten Stelle aus, die in einer Sprungtabelle gespeichert ist. Aus Sicherheitsgründen können Sie nicht in den Kernel-Modus 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 läuft. Die Traps werden mit einer expliziten Trap-Nummer versehen und die Adresse wird in der Sprungtabelle nachgeschlagen; der Kernel hat eine endliche Anzahl von kontrollierten Einstiegspunkten.

Die "System"-Aufrufe in der C-Bibliothek (insbesondere die in Abschnitt 2 der Handbuchseiten beschriebenen) haben eine Benutzermodus-Komponente, d.h. das, was Sie tatsächlich von Ihrem C-Programm aus aufrufen. Hinter den Kulissen geben sie vielleicht einen oder mehrere Systemaufrufe an den Kernel aus, um bestimmte Dienste wie E/A auszuführen, aber sie haben auch noch Code, der im Benutzermodus läuft. Es ist auch möglich, von einem beliebigen User-Space-Code aus direkt einen Trap in den Kernel-Modus auszulösen, auch wenn Sie dazu ein Stück Assembler 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 - Dinge wie die Zuweisung von Speicher oder der Zugriff auf Hardware (Festplatte, Netzwerk usw.). Diese Dinge fallen unter die Aufsicht des Kernels, und nur dieser kann sie ausführen. Einige Operationen wie malloc ou fread / fwrite ruft diese Kernel-Funktionen auf, und das zählt dann als 'sys'-Zeit. Leider ist es nicht so einfach wie "jeder Aufruf von malloc wird als 'sys'-Zeit gezählt". Der Aufruf von malloc wird einige eigene Verarbeitungen vornehmen (die immer noch in der 'user'-Zeit gezählt werden) und dann irgendwann die Funktion im Kernel aufrufen (in der 'sys'-Zeit gezählt). Nach der Rückkehr vom Kernel-Aufruf verbleibt noch etwas Zeit in 'user' und dann malloc kehrt zu Ihrem Code zurück. Wann der Wechsel stattfindet und wie viel Zeit im Kernel-Modus verbracht wird, kann man nicht sagen. Das hängt von der Implementierung der Bibliothek ab. Auch andere scheinbar unschuldige Funktionen verwenden möglicherweise malloc und dergleichen im Hintergrund, die dann wieder etwas Zeit in 'sys' haben werden.

22 Stimmen

Zählt die von Kindprozessen verbrachte Zeit zu real/sys?

1 Stimmen

@ron - Laut der Linux-Manpage werden die 'c'-Zeiten mit den Prozesszeiten aggregiert, also glaube ich, dass es funktioniert. Die übergeordneten Zeiten und die untergeordneten Zeiten sind jedoch separat vom times(2)-Aufruf verfügbar. Ich vermute, die Solaris/SysV-Version von time(1) macht etwas Ähnliches.

0 Stimmen

@ron Ich habe die Antwort soeben bearbeitet: Die von Kindprozessen und deren Nachkommen verbrachte Zeit zählt nur wenn die Zeit mit wait(2) oder waitpid(2) und rekursiv mit den Nachfahren hätte erfasst werden können. Dies impliziert, dass die Kindprozesse beendet sein müssen. Vergleichen Sie zum Beispiel time sh -c 'foo & sleep 1' et time sh -c 'foo & sleep 2' , wobei foo ist ein Befehl, der zwischen 1 und 2 Sekunden CPU-Zeit benötigt. Der erste Befehl gibt etwas um 0 aus.

372voto

lensovet Punkte 5059

Zur Erweiterung der akzeptierte Antwort Ich wollte nur einen weiteren Grund nennen, warum real != user + sys .

Bitte beachten Sie, dass real steht für die tatsächlich verstrichene Zeit, während user y sys Werte stellen die CPU-Ausführungszeit dar. Infolgedessen wird auf einem Multicore-System die user und/oder sys Zeit (wie auch ihre Summe) können tatsächlich übersteigen die Echtzeit. Bei einer Java-Anwendung, die ich für den Unterricht ausführe, erhalte ich beispielsweise diesen Satz von Werten:

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

17 Stimmen

Das habe ich mich schon immer gefragt. Da ich weiß, dass meine Programme single threaded sind, muss der Unterschied zwischen Benutzer- und Echtzeit der VM-Overhead sein, richtig?

20 Stimmen

Nicht notwendigerweise; die Sun JVM auf Solaris-Maschinen sowie Apples JVM auf Mac OS X schaffen es, mehr als einen Kern zu verwenden, sogar in Anwendungen mit einem Thread. Wenn Sie sich einen Java-Prozess ansehen, werden Sie feststellen, dass Dinge wie die Garbage Collection in separaten Threads ablaufen (und auch einige andere Dinge, an die ich mich aus dem Stegreif nicht erinnere). Ich weiß allerdings nicht, ob man das wirklich als "VM-Overhead" bezeichnen sollte.

0 Stimmen

@Quantum7 - nein, nicht unbedingt. Siehe meinen Beitrag oben. Real ist verstrichene Zeit, user und sys sind akkumulierte Zeitscheibenstatistiken aus der CPU-Zeit, die der Prozess tatsächlich nutzt.

83voto

varun Punkte 4196

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

- Benutzer : Die kumulierte Zeit, die von allen CPUs während der Berechnung verbraucht wird

- sys : Die kumulative Zeit, die von allen CPUs während systembezogener Aufgaben wie der Speicherzuweisung verbracht wird.

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

5 Stimmen

real wird oft als "Wanduhr"-Zeit bezeichnet.

1 Stimmen

Oder, in meinem Fall, manchmal real ist mehr als die user+sys Bei mir liegt es daran, dass so viele parallele Prozesse gleichzeitig laufen. 87.02 real 14.12 user 5.20 sys 41.30 real 7.03 user 3.20 sys 2387.46 real 750.67 user 282.80 sys 2.60 real 7.22 user 3.35 sys

65voto

Minimale lauffähige POSIX-C-Beispiele

Um die Dinge konkreter zu machen, möchte ich ein paar Extremfälle aufzeigen time mit einigen minimalen C-Testprogrammen.

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

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.

sleep Syscall

Nicht-ausgelasteter Schlaf, wie er von der sleep syscall zählt nur in real , aber nicht für user o sys .

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

gibt etwa so etwas aus:

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

Das Gleiche gilt für Programme, die auf IO blockiert sind und verfügbar werden.

Das folgende Programm wartet zum Beispiel 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 vorgelagert .

Und wenn man etwa eine Sekunde wartet, gibt es genau wie beim Sleep-Beispiel so etwas wie:

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

Aus diesem Grund time kann Ihnen helfen, zwischen CPU- und IO-gebundenen Programmen zu unterscheiden: Was bedeuten die Begriffe "CPU gebunden" und "I/O gebunden"?

Mehrere Threads

Das folgende Beispiel zeigt niters Iterationen von nutzloser, rein CPU-gebundener Arbeit an nthreads Themen:

#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 + Plot-Code .

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

enter image description here

Plot-Daten .

Aus dem Diagramm geht hervor, dass:

  • bei einer CPU-intensiven Einzelkernanwendung sind Wand und Benutzer ungefähr gleich groß

  • für 2 Kerne ist der Benutzer etwa 2x Wand, was bedeutet, dass die Benutzerzeit über alle Threads hinweg gezählt wird.

    Die Zahl der Nutzer hat sich im Wesentlichen verdoppelt, während die Wand gleich geblieben ist.

  • dies geht weiter bis zu 8 Threads, was der Anzahl der Hyperthreads in meinem Computer entspricht.

    Nach 8 beginnt die Wand ebenfalls zu wachsen, da wir keine zusätzlichen CPUs mehr haben, um mehr Arbeit in einer bestimmten Zeitspanne zu erledigen!

    An diesem Punkt stagniert das Verhältnis.

Beachten Sie, dass dieses Diagramm nur deshalb so klar und einfach ist, weil die Arbeit rein CPU-gebunden ist: Wäre sie speichergebunden, dann würden wir bei weniger Kernen viel früher einen Leistungsabfall erleben, weil die Speicherzugriffe ein Engpass wären, wie unter Was bedeuten die Begriffe "CPU gebunden" und "I/O gebunden"?

Durch eine schnelle Überprüfung des Verhältnisses Wand < Benutzer lässt sich einfach feststellen, dass ein Programm multithreaded ist, und je näher dieses Verhältnis an der Anzahl der Kerne liegt, desto effektiver ist die Parallelisierung, z. B.:

Schwere Arbeit mit Sys sendfile

Die schwerste Systemauslastung, die mir einfiel, war die Verwendung der sendfile der einen Dateikopiervorgang im Kernelbereich durchführt: Kopieren einer Datei auf eine vernünftige, sichere und effiziente Weise

Ich stellte mir also vor, dass dieses In-Kernel memcpy ist ein rechenintensiver Vorgang.

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

was im Wesentlichen die erwartete Systemzeit ergibt:

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

Ich war auch neugierig, ob time zwischen Syscalls verschiedener Prozesse unterscheiden würde, also habe ich es 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 Systemzeit ist bei beiden etwa gleich wie bei einem einzelnen Prozess, aber die Wandzeit ist größer, weil die Prozesse wahrscheinlich um den Lesezugriff auf die Festplatte konkurrieren.

Es scheint also tatsächlich eine Rolle zu spielen, welcher Prozess eine bestimmte Kernelarbeit gestartet hat.

Bash-Quellcode

Wenn Sie nur time <cmd> unter Ubuntu das Bash-Schlüsselwort, wie Sie sehen können:

type time

die ausgibt:

time is a shell keyword

Also suchen wir mit grep source im Quellcode der Bash 4.19 nach dem Ausgabestring:

git grep '"user\b'

Das führt uns zu execute_cmd.c Funktion time_command die verwendet:

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

die alle Linux-Systemaufrufe y POSIX-Funktionen .

GNU Coreutils-Quellcode

Wenn wir es so nennen:

/usr/bin/time

dann verwendet es die GNU Coreutils-Implementierung.

Diese Frage ist etwas komplexer, aber die relevante Quelle findet sich unter wiederverwenden.c und das tut sie auch:

Um die Dinge konkreter zu machen, möchte ich ein paar Extremfälle aufzeigen time mit einigen minimalen C-Testprogrammen.

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

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.

schlafen

Nicht-geschäftiger Schlaf zählt ebenfalls nicht mit user o sys nur 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 vorgelagert .

gibt etwas wie:

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

Das Gleiche gilt für Programme, die auf IO blockiert sind und verfügbar werden.

Das folgende Programm wartet zum Beispiel 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 vorgelagert .

Und wenn man etwa eine Sekunde wartet, gibt es genau wie beim Sleep-Beispiel so etwas wie:

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

Aus diesem Grund time kann Ihnen helfen, zwischen CPU- und IO-gebundenen Programmen zu unterscheiden: Was bedeuten die Begriffe "CPU gebunden" und "I/O gebunden"?

Mehrere Threads

Das folgende Beispiel zeigt niters Iterationen von nutzloser, rein CPU-gebundener Arbeit an nthreads Themen:

#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 + Plot-Code .

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

enter image description here

Plot-Daten .

Aus dem Diagramm geht hervor, dass:

  • bei einer CPU-intensiven Einzelkernanwendung sind Wand und Benutzer ungefähr gleich groß

  • für 2 Kerne ist der Benutzer etwa 2x Wand, was bedeutet, dass die Benutzerzeit über alle Threads hinweg gezählt wird.

    Die Zahl der Nutzer hat sich im Wesentlichen verdoppelt, während die Wand gleich geblieben ist.

  • dies geht weiter bis zu 8 Threads, was der Anzahl der Hyperthreads in meinem Computer entspricht.

    Nach 8 beginnt die Wand ebenfalls zu wachsen, da wir keine zusätzlichen CPUs mehr haben, um mehr Arbeit in einer bestimmten Zeitspanne zu erledigen!

    An diesem Punkt stagniert das Verhältnis.

Beachten Sie, dass dieses Diagramm nur deshalb so klar und einfach ist, weil die Arbeit rein CPU-gebunden ist: Wäre sie speichergebunden, dann würden wir bei weniger Kernen viel früher einen Leistungsabfall erleben, weil die Speicherzugriffe ein Engpass wären, wie unter Was bedeuten die Begriffe "CPU gebunden" und "I/O gebunden"?

Durch eine schnelle Überprüfung des Verhältnisses Wand < Benutzer lässt sich einfach feststellen, dass ein Programm multithreaded ist, und je näher dieses Verhältnis an der Anzahl der Kerne liegt, desto effektiver ist die Parallelisierung, z. B.:

Schwere Arbeit mit Sys sendfile

Die schwerste Systemauslastung, die mir einfiel, war die Verwendung der sendfile der einen Dateikopiervorgang im Kernelbereich durchführt: Kopieren einer Datei auf eine vernünftige, sichere und effiziente Weise

Ich stellte mir also vor, dass dieser In-Kernel memcpy ist ein rechenintensiver Vorgang.

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

was im Wesentlichen die erwartete Systemzeit ergibt:

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

Ich war auch neugierig, ob time zwischen Syscalls verschiedener Prozesse unterscheiden würde, also habe ich es 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 Systemzeit ist bei beiden etwa gleich wie bei einem einzelnen Prozess, aber die Wandzeit ist größer, weil die Prozesse wahrscheinlich um den Lesezugriff auf die Festplatte konkurrieren.

Es scheint also tatsächlich eine Rolle zu spielen, welcher Prozess eine bestimmte Kernelarbeit gestartet hat.

Bash-Quellcode

Wenn Sie nur time <cmd> unter Ubuntu das Bash-Schlüsselwort, wie Sie sehen können:

type time

die ausgibt:

time is a shell keyword

Wir suchen also mit grep source im Quellcode der Bash 4.19 nach dem Ausgabestring:

git grep '"user\b'

Das führt uns zu execute_cmd.c Funktion time_command die verwendet:

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

die alle Linux-Systemaufrufe y POSIX-Funktionen .

GNU Coreutils Quellcode

Wenn wir es so nennen:

/usr/bin/time

dann verwendet es die GNU Coreutils-Implementierung.

Diese Frage ist etwas komplexer, aber die relevante Quelle findet sich unter wiederverwenden.c und das tut sie auch:

  • ein Nicht-POSIX-BSD wait3 anrufen, wenn das verfügbar ist
  • times y gettimeofday ansonsten

17voto

susenj Punkte 161

Real zeigt die Gesamtdurchlaufzeit für einen Prozess an; User zeigt die Ausführungszeit für benutzerdefinierte Anweisungen an und Sys die Zeit für die Ausführung von Systemaufrufen angibt!

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X