Cosa significano "reale", "utente" e "sys" nell'output del tempo (1)?


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

Cosa significano "reale", "utente" e "sys" nell'output del tempo?

Quale è significativo nel benchmarking della mia app?


2
come posso accedere a uno solo di essi? per esempio solo in tempo reale?
Mojtaba Ahmadi,

1
@ConcernedOfTunbridgeWells
Mojtaba Ahmadi


7
Se il tuo programma esce così velocemente, nessuno di questi è significativo, è solo un sovraccarico di avvio. Se vuoi misurare l'intero programma con time, fallo fare qualcosa che richiederà almeno un secondo.
Peter Cordes,

5
È davvero importante notare che timeè una parola chiave bash. Quindi digitazione man timeè non ti dà una pagina di manuale per la bash time, piuttosto sta dando la pagina man per /usr/bin/time. Questo mi ha fatto inciampare.
irritable_phd_syndrom,

Risposte:


2065

Statistiche sul tempo di processo reale, dell'utente e del sistema

Una di queste cose non è come le altre. Reale si riferisce al tempo trascorso effettivo; Utente e sistema si riferiscono al tempo della CPU utilizzato solo dal processo.

  • Reale è l'ora dell'orologio da parete - tempo dall'inizio alla fine della chiamata. Questo è tutto il tempo trascorso, compresi gli intervalli di tempo utilizzati da altri processi e il tempo trascorso dal processo bloccato (ad esempio se è in attesa del completamento dell'I / O).

  • L'utente è la quantità di tempo della CPU trascorso nel codice in modalità utente (al di fuori del kernel) all'interno del processo. Questo è solo il tempo CPU effettivo utilizzato nell'esecuzione del processo. Altri processi e tempi di blocco bloccati non contano ai fini di questa cifra.

  • Sys è la quantità di tempo CPU trascorso nel kernel all'interno del processo. Ciò significa eseguire il tempo della CPU impiegato nelle chiamate di sistema all'interno del kernel, al contrario del codice della libreria, che è ancora in esecuzione nello spazio utente. Come "utente", questo è solo il tempo CPU utilizzato dal processo. Vedi sotto per una breve descrizione della modalità kernel (nota anche come modalità 'supervisore') e del meccanismo di chiamata del sistema.

User+Systi dirà quanto tempo effettivo della CPU ha usato il tuo processo. Si noti che ciò è presente su tutte le CPU, quindi se il processo ha più thread (e questo processo è in esecuzione su un computer con più di un processore) potrebbe potenzialmente superare il tempo di clock riportato da Real(che di solito si verifica). Si noti che nell'output queste cifre includono il tempo Usere Systutti i processi figlio (e i loro discendenti) e anche quando avrebbero potuto essere raccolti, ad esempio da wait(2)o waitpid(2), sebbene le chiamate di sistema sottostanti restituiscano le statistiche per il processo e i suoi figli separatamente.

Origini delle statistiche riportate da time (1)

Le statistiche riportate da timevengono raccolte da varie chiamate di sistema. 'Utente' e 'Sys' provengono da wait (2)( POSIX ) o times (2)( POSIX ), a seconda del sistema specifico. 'Reale' viene calcolato dall'ora di inizio e di fine acquisita dalla gettimeofday (2)chiamata. A seconda della versione del sistema, possono anche essere raccolte varie altre statistiche come il numero di switch di contesto time.

Su una macchina a più processori, un processo a più thread o un processo di fork dei figli potrebbe avere un tempo trascorso inferiore al tempo totale della CPU, poiché diversi thread o processi possono essere eseguiti in parallelo. Inoltre, le statistiche sui tempi riportate provengono da origini diverse, quindi i tempi registrati per attività molto brevi possono essere soggetti ad errori di arrotondamento, come mostra l'esempio fornito dal poster originale.

Un breve primer sulla modalità Kernel vs. User

Su Unix o su qualsiasi sistema operativo a memoria protetta, la modalità "Kernel" o "Supervisore" si riferisce a una modalità privilegiata in cui la CPU può operare. Alcune azioni privilegiate che potrebbero influenzare la sicurezza o la stabilità possono essere eseguite solo quando la CPU è in funzione questa modalità; queste azioni non sono disponibili per il codice dell'applicazione. Un esempio di tale azione potrebbe essere la manipolazione della MMU per ottenere l'accesso allo spazio degli indirizzi di un altro processo. Normalmente, il codice in modalità utente non può farlo (con una buona ragione), sebbene possa richiedere memoria condivisa dal kernel, il che potrebbeessere letto o scritto da più di un processo. In questo caso, la memoria condivisa viene esplicitamente richiesta al kernel attraverso un meccanismo sicuro ed entrambi i processi devono collegarsi esplicitamente ad essa per poterla utilizzare.

La modalità privilegiata viene generalmente definita modalità "kernel" poiché il kernel viene eseguito dalla CPU in esecuzione in questa modalità. Per passare alla modalità kernel è necessario emettere un'istruzione specifica (spesso chiamata trap ) che commuta la CPU in esecuzione in modalità kernel ed esegue il codice da una posizione specifica contenuta in una tabella di salto. Per motivi di sicurezza, non è possibile passare alla modalità kernel ed eseguire codice arbitrario: i trap sono gestiti attraverso una tabella di indirizzi in cui non è possibile scrivere a meno che la CPU non sia in esecuzione in modalità supervisore. Trap con un numero trap esplicito e l'indirizzo viene cercato nella tabella di salto; il kernel ha un numero finito di punti di ingresso controllati.

Le chiamate di "sistema" nella libreria C (in particolare quelle descritte nella Sezione 2 delle pagine man) hanno un componente in modalità utente, che è ciò che in realtà chiami dal tuo programma C. Dietro le quinte, possono inviare una o più chiamate di sistema al kernel per eseguire servizi specifici come l'I / O, ma hanno anche il codice in esecuzione in modalità utente. È anche possibile emettere direttamente una trap alla modalità kernel da qualsiasi codice dello spazio utente, se lo si desidera, anche se potrebbe essere necessario scrivere uno snippet di linguaggio assembly per impostare correttamente i registri per la chiamata.

Altro su "sys"

Ci sono cose che il tuo codice non può fare dalla modalità utente - cose come l'allocazione della memoria o l'accesso all'hardware (HDD, rete, ecc.). Questi sono sotto la supervisione del kernel e solo lui può farli. Alcune operazioni come malloco fread/ fwriteinvocheranno queste funzioni del kernel e che quindi verranno conteggiate come "sys". Sfortunatamente non è così semplice come "ogni chiamata a malloc verrà conteggiata nel tempo di" sys ". La chiamata a malloceseguirà una sua elaborazione (ancora conteggiata nel tempo "utente") e poi da qualche parte lungo il modo in cui può chiamare la funzione nel kernel (conteggiata nel tempo "sys"). Dopo essere tornato dalla chiamata del kernel, ci sarà ancora del tempo in "user" e poimalloctornerà al tuo codice. Per quanto riguarda quando avviene lo switch e quanto viene speso in modalità kernel ... non si può dire. Dipende dall'implementazione della libreria. Inoltre, potrebbero anche essere usate altre funzioni apparentemente innocenti malloce simili in background, che avranno di nuovo un po 'di tempo in "sys".


15
Il tempo speso dai processi figlio conta in real / sys?
Ron,

1
@ron - Secondo la pagina man di Linux, aggrega i tempi 'c' con i tempi di processo, quindi penso che lo faccia. Tuttavia, i tempi dei genitori e dei figli sono disponibili separatamente dalle chiamate dei tempi (2). Suppongo che la versione di Solaris / SysV di tempo (1) faccia qualcosa di simile.
ConcernedOfTunbridgeWells il

3
Utente + Sys consente di misurare l'utilizzo della CPU di un processo. Puoi usarlo per confrontare le prestazioni. Ciò è particolarmente utile per il codice multi-thread in cui più di un core della CPU potrebbe lavorare su un calcolo.
Preoccupato di

1
Non proprio sull'argomento, tuttavia: l'esecuzione di "\ time <cmd>" è interessante - fornisce maggiori dettagli: (perdona la formattazione scadente nel commento): $ 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.00utente 0.01sistema 0: 00.02 CPU 95% scaduta (0avgtext + 0avgdata 2160maxresident) k 0inputs + 0outputs (0major + 103minor) pagefaults 0swaps $
kaiwan

1
(È uscito di caratteri nel commento precedente quindi): Più dettagli? Usa perf [1], [2]. [1] perf.wiki.kernel.org/index.php/Main_Page [2] brendangregg.com/perf.html
kaiwan

286

Per espandere la risposta accettata , volevo solo fornire un altro motivo per cui realuser+ sys.

Tenete a mente che realrappresenta il tempo trascorso effettivo, mentre usere sysvalori rappresentano il tempo di esecuzione della CPU. Di conseguenza, su un sistema multicore, l'ora usere / o sys(così come la loro somma) possono effettivamente superare il tempo reale. Ad esempio, su un'app Java in esecuzione per la classe ottengo questo set di valori:

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

11
Mi ero sempre chiesto di questo. Dato che so che i miei programmi sono a thread singolo, la differenza tra utente e tempo reale deve essere il sovraccarico della VM, giusto?
Quantum7,

9
non necessariamente; Sun JVM su macchine Solaris e JVM di Apple su Mac OS X riescono a utilizzare più di un core anche nelle app a thread singolo. Se fai un esempio di un processo java, vedrai che cose come la garbage collection vengono eseguite su thread separati (e anche alcune altre cose che non ricordo nella parte superiore della mia testa). Non so se vuoi davvero definire "sovraccarico VM".
lensovet

4
Immagino che la quantità di voti positivi ti abbia dato abbastanza reputazione ora: D. Cosa ne pensi del realsuperamento usere del systotale? L'overhead del sistema operativo come il cambio di contesto del thread potrebbe essere?
Muhammad Gelbana,

19
Un altro potenziale problema potrebbe essere l'I / O: se l'applicazione impiega molto tempo in attesa di ricevere un file o un flusso, ovviamente il tempo reale supererebbe notevolmente il tempo utente / sistema perché non viene utilizzato alcun tempo della CPU in attesa di accesso a un file o qualcosa di simile.
Lensovet,

1
@MuhammadGelbana: ciò può accadere se l'applicazione è bloccata per qualsiasi motivo. Ad esempio, se è in attesa su connessioni I / O, IPC o socket rimarrà inattivo, non accumulando tempo CPU fino al ritorno della chiamata di blocco.
ConcernedOfTunbridgeWells

41

reale : il tempo effettivo impiegato nell'esecuzione del processo dall'inizio alla fine, come se fosse misurato da un essere umano con un cronometro

utente : il tempo cumulativo impiegato da tutte le CPU durante il calcolo

sys : il tempo cumulativo impiegato da tutte le CPU durante le attività relative al sistema come l'allocazione della memoria.

Si noti che a volte user + sys potrebbe essere maggiore di reale, poiché più processori potrebbero funzionare in parallelo.


sysè il tempo della CPU impiegato nelle chiamate di sistema (e gestori di errori di pagina?)
Peter Cordes,

1
realviene spesso descritto come "orologio da parete".
Peter Cordes,

30

Esempi di POSIX C eseguibili minimi

Per rendere le cose più concrete, voglio esemplificare alcuni casi estremi timecon alcuni programmi di test C minimi.

Tutti i programmi possono essere compilati ed eseguiti con:

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

e sono stati testati in Ubuntu 18.10, GCC 8.2.0, glibc 2.28, kernel Linux 4.18, laptop ThinkPad P51, CPU Intel Core i7-7820HQ (4 core / 8 thread), 2x Samsung M471A2K43BB1-CRC RAM (2x 16GiB).

dormire

Il sonno non occupato non viene conteggiato in nessuno dei due usero sys, solo real.

Ad esempio, un programma che dorme per un secondo:

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

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

GitHub a monte .

produce qualcosa come:

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

Lo stesso vale per i programmi bloccati su IO che diventano disponibili.

Ad esempio, il seguente programma attende che l'utente inserisca un carattere e premi Invio:

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

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

GitHub a monte .

E se aspetti circa un secondo, viene emesso proprio come nell'esempio sleep qualcosa come:

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

Per questo motivo timepuò aiutarti a distinguere tra i programmi CPU e IO associati: che cosa significano i termini "CPU bound" e "I / O bound"?

Discussioni multiple

L'esempio seguente esegue nitersiterazioni di inutili lavori legati esclusivamente alla CPU sui nthreadsthread:

#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 + codice trama .

Quindi tracciamo wall, user e sys in funzione del numero di thread per 10 iterazioni fisse sulla mia 8 CPU hyperthread:

inserisci qui la descrizione dell'immagine

Traccia dati .

Dal grafico, vediamo che:

  • per un'applicazione single core ad uso intensivo di CPU, wall e user sono praticamente uguali

  • per 2 core, l'utente è circa 2x wall, il che significa che il tempo dell'utente viene conteggiato su tutti i thread.

    l'utente ha praticamente raddoppiato, e mentre wall è rimasto lo stesso.

  • questo continua fino a 8 thread, che corrisponde al mio numero di hyperthread sul mio computer.

    Dopo 8, anche il wall inizia ad aumentare, perché non abbiamo CPU extra per dedicare più lavoro in un determinato periodo di tempo!

    Il rapporto plateau a questo punto.

Si noti che questo grafico è solo così chiaro e semplice perché il lavoro è puramente legato alla CPU: se fosse legato alla memoria, si otterrebbe un calo delle prestazioni molto prima con meno core perché gli accessi alla memoria sarebbero un collo di bottiglia, come mostrato in Cosa cosa significano i termini "CPU bound" e "I / O bound"?

Sys lavoro pesante con sendfile

Il carico di lavoro di sistema più pesante che potevo inventare era usare il sendfile, che fa un'operazione di copia di file nello spazio del kernel: copiare un file in modo sano, sicuro ed efficiente

Quindi ho immaginato che questo in-kernel memcpysarà un'operazione intensiva per la CPU.

Per prima cosa inizializzo un grande file casuale da 10GiB con:

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

Quindi eseguire il codice:

#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 a monte .

che dà sostanzialmente principalmente il tempo di sistema come previsto:

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

Ero anche curioso di vedere se timedistinguesse tra syscalls di diversi processi, quindi ho provato:

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

E il risultato è stato:

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

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

Il tempo di sistema è più o meno lo stesso sia per un singolo processo, ma il tempo di wall è maggiore perché i processi sono in competizione per l'accesso probabile alla lettura del disco.

Quindi sembra che in realtà spieghi quale processo ha avviato un determinato lavoro del kernel.

Codice sorgente di Bash

Quando lo fai solo time <cmd>su Ubuntu, usa la parola chiave Bash come si può vedere da:

type time

che produce:

time is a shell keyword

Quindi grep source nel codice sorgente di Bash 4.19 per la stringa di output:

git grep '"user\b'

che ci porta alla funzione execute_cmd.ctime_command , che utilizza:

  • gettimeofday()e getrusage()se entrambi sono disponibili
  • times() altrimenti

tutte sono chiamate di sistema Linux e funzioni POSIX .

Codice sorgente GNU Coreutils

Se lo chiamiamo come:

/usr/bin/time

quindi utilizza l'implementazione GNU Coreutils.

Questo è un po 'più complesso, ma la fonte rilevante sembra essere resuse.c e lo fa:

  • una wait3chiamata BSD non POSIX se disponibile
  • timese gettimeofdayaltrimenti

14

Il reale mostra il tempo di inversione totale per un processo; mentre l'utente mostra il tempo di esecuzione per le istruzioni definite dall'utente e Sys è il momento di eseguire le chiamate di sistema!

Il tempo reale include anche il tempo di attesa (il tempo di attesa per I / O ecc.)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.