$ time foo
real 0m0.003s
user 0m0.000s
sys 0m0.004s
$
¿Qué significan 'real', 'usuario' y 'sys' en la salida del tiempo?
¿Cuál es significativo al comparar mi aplicación?
fuente
$ time foo
real 0m0.003s
user 0m0.000s
sys 0m0.004s
$
¿Qué significan 'real', 'usuario' y 'sys' en la salida del tiempo?
¿Cuál es significativo al comparar mi aplicación?
Estadísticas de tiempo de proceso real, usuario y sistema
Una de estas cosas no es como la otra. Real se refiere al tiempo transcurrido real; Usuario y Sys se refieren al tiempo de CPU utilizado solo por el proceso.
Real es la hora del reloj de pared: tiempo desde el inicio hasta el final de la llamada. Este es todo el tiempo transcurrido, incluidos los segmentos de tiempo utilizados por otros procesos y el tiempo que el proceso pasa bloqueado (por ejemplo, si está esperando que se complete la E / S).
Usuario es la cantidad de tiempo de CPU gastado en código de modo de usuario (fuera del núcleo) dentro del proceso. Este es solo el tiempo real de CPU utilizado en la ejecución del proceso. Otros procesos y el tiempo que el proceso pasa bloqueado no cuentan para esta cifra.
Sys es la cantidad de tiempo de CPU que se pasa en el núcleo dentro del proceso. Esto significa ejecutar el tiempo de CPU gastado en las llamadas del sistema dentro del núcleo, a diferencia del código de la biblioteca, que todavía se ejecuta en el espacio del usuario. Al igual que 'usuario', este es solo el tiempo de CPU utilizado por el proceso. Consulte a continuación una breve descripción del modo kernel (también conocido como modo 'supervisor') y el mecanismo de llamada del sistema.
User+Sys
le dirá cuánto tiempo real de CPU usó su proceso. Tenga en cuenta que esto Real
ocurre en todas las CPU, por lo que si el proceso tiene varios subprocesos (y este proceso se ejecuta en una computadora con más de un procesador), podría exceder el tiempo de reloj de pared informado (que generalmente ocurre). Tenga en cuenta que en el resultado estas cifras incluyen el tiempo User
y el Sys
tiempo de todos los procesos secundarios (y sus descendientes), así como cuándo podrían haberse recopilado, por ejemplo, mediante wait(2)
o waitpid(2)
, aunque las llamadas al sistema subyacentes devuelven las estadísticas del proceso y sus elementos secundarios por separado.
Orígenes de las estadísticas reportadas por time (1)
Las estadísticas informadas por time
se recopilan de varias llamadas al sistema. 'Usuario' y 'Sys' provienen de wait (2)
( POSIX ) o times (2)
( POSIX ), dependiendo del sistema en particular. 'Real' se calcula a partir de una hora de inicio y finalización recopilada de la gettimeofday (2)
llamada. Dependiendo de la versión del sistema, también se pueden recopilar otras estadísticas como el número de cambios de contexto time
.
En una máquina multiprocesador, un proceso multiproceso o un proceso que bifurca a los niños podría tener un tiempo transcurrido menor que el tiempo total de la CPU, ya que diferentes subprocesos o procesos pueden ejecutarse en paralelo. Además, las estadísticas de tiempo informadas provienen de diferentes orígenes, por lo que los tiempos registrados para tareas de ejecución muy cortas pueden estar sujetos a errores de redondeo, como muestra el ejemplo dado por el póster original.
Una breve introducción sobre el modo Kernel vs. Usuario
En Unix, o en cualquier sistema operativo de memoria protegida, el modo 'Kernel' o 'Supervisor' se refiere a un modo privilegiado en el que la CPU puede operar. Ciertas acciones privilegiadas que podrían afectar la seguridad o la estabilidad solo pueden realizarse cuando la CPU está operando en este modo Estas acciones no están disponibles para el código de la aplicación. Un ejemplo de tal acción podría ser la manipulación de la MMU para obtener acceso al espacio de direcciones de otro proceso. Normalmente, el código de modo de usuario no puede hacer esto (con buena razón), aunque puede solicitar memoria compartida del núcleo, lo que podríaser leído o escrito por más de un proceso. En este caso, la memoria compartida se solicita explícitamente desde el núcleo a través de un mecanismo seguro y ambos procesos deben adjuntarse explícitamente para poder usarla.
El modo privilegiado generalmente se conoce como modo 'núcleo' porque el núcleo lo ejecuta la CPU que se ejecuta en este modo. Para cambiar al modo kernel, debe emitir una instrucción específica (a menudo llamada trampa ) que cambia la CPU para que se ejecute en modo kernel y ejecuta código desde una ubicación específica contenida en una tabla de salto. Por razones de seguridad, no puede cambiar al modo kernel y ejecutar código arbitrario: las trampas se administran a través de una tabla de direcciones en las que no se puede escribir a menos que la CPU se ejecute en modo supervisor. Atrapa con un número de trampa explícito y la dirección se busca en la tabla de salto; el núcleo tiene un número finito de puntos de entrada controlados.
Las llamadas al 'sistema' en la biblioteca C (particularmente las descritas en la Sección 2 de las páginas del manual) tienen un componente de modo de usuario, que es lo que realmente llama desde su programa C. Detrás de escena, pueden emitir una o más llamadas del sistema al núcleo para realizar servicios específicos como E / S, pero también tienen código ejecutándose en modo de usuario. También es bastante posible emitir directamente una trampa al modo kernel desde cualquier código de espacio de usuario si lo desea, aunque es posible que deba escribir un fragmento de lenguaje ensamblador para configurar los registros correctamente para la llamada.
Más acerca de 'sys'
Hay cosas que su código no puede hacer desde el modo de usuario, como asignar memoria o acceder al hardware (HDD, red, etc.). Estos están bajo la supervisión del núcleo, y solo ellos pueden hacerlos. Algunas operaciones como malloc
o fread
/ fwrite
invocarán estas funciones del núcleo y luego contarán como tiempo 'sys'. Desafortunadamente, no es tan simple como "cada llamada a malloc se contará en el tiempo 'sys'". La llamada a malloc
realizará un procesamiento propio (todavía contado en el tiempo del 'usuario') y luego en algún punto del camino puede llamar a la función en el núcleo (contado en el tiempo 'sys'). Después de regresar de la llamada del kernel, habrá más tiempo en 'usuario' y luegomalloc
volverá a su código En cuanto a cuándo ocurre el cambio, y cuánto se gasta en modo kernel ... no se puede decir. Depende de la implementación de la biblioteca. Además, otras funciones aparentemente inocentes también podrían usar malloc
y similares en el fondo, que nuevamente tendrá algo de tiempo en 'sys'.
Para ampliar la respuesta aceptada , solo quería proporcionar otra razón por la cual real
≠ user
+ sys
.
Tenga en cuenta que real
representa el tiempo transcurrido real, mientras que user
y sys
valores representan el tiempo de ejecución de la CPU. Como resultado, en un sistema multinúcleo, el tiempo user
y / o sys
tiempo (así como su suma) pueden exceder el tiempo real. Por ejemplo, en una aplicación Java que estoy ejecutando para la clase, obtengo este conjunto de valores:
real 1m47.363s
user 2m41.318s
sys 0m4.013s
real
excedente user
y sys
total? La sobrecarga del sistema operativo, como el cambio de contexto de subproceso puede ser?
• real : el tiempo real empleado en ejecutar el proceso de principio a fin, como si lo midiera un humano con un cronómetro
• usuario : el tiempo acumulado empleado por todas las CPU durante el cálculo
• sys : el tiempo acumulado que pasan todas las CPU durante las tareas relacionadas con el sistema, como la asignación de memoria.
Tenga en cuenta que a veces user + sys puede ser mayor que real, ya que varios procesadores pueden funcionar en paralelo.
sys
¿Se pasa tiempo de CPU en llamadas al sistema (y manejadores de fallas de página?)
real
a menudo se describe como tiempo de "reloj de pared".
Ejemplos mínimos de POSIX C ejecutables
Para hacer las cosas más concretas, quiero ejemplificar algunos casos extremos time
con algunos programas mínimos de prueba C.
Todos los programas se pueden compilar y ejecutar con:
gcc -ggdb3 -o main.out -pthread -std=c99 -pedantic-errors -Wall -Wextra main.c
time ./main.out
y han sido probados en Ubuntu 18.10, GCC 8.2.0, glibc 2.28, Linux kernel 4.18, laptop ThinkPad P51, CPU Intel Core i7-7820HQ (4 núcleos / 8 hilos), 2x Samsung M471A2K43BB1-CRC RAM (2x 16GiB).
dormir
El sueño no ocupado no cuenta en ninguno user
o sys
solo real
.
Por ejemplo, un programa que duerme un segundo:
#define _XOPEN_SOURCE 700
#include <stdlib.h>
#include <unistd.h>
int main(void) {
sleep(1);
return EXIT_SUCCESS;
}
produce algo como:
real 0m1.003s
user 0m0.001s
sys 0m0.003s
Lo mismo vale para los programas bloqueados cuando IO está disponible.
Por ejemplo, el siguiente programa espera a que el usuario ingrese un carácter y presione enter:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("%c\n", getchar());
return EXIT_SUCCESS;
}
Y si espera aproximadamente un segundo, se genera como en el ejemplo de reposo algo así como:
real 0m1.003s
user 0m0.001s
sys 0m0.003s
Por esta razón, time
puede ayudarlo a distinguir entre los programas vinculados a la CPU y a las E / S: ¿Qué significan los términos "CPU vinculados" y "E / S atados"?
Hilos múltiples
El siguiente ejemplo realiza niters
iteraciones de trabajo inútil puramente vinculado a la CPU en nthreads
subprocesos:
#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 + código de trama .
Luego graficamos wall, user y sys en función del número de subprocesos para un 10 ^ 10 iteraciones fijas en mi 8 CPU hyperthread:
Del gráfico, vemos que:
para una aplicación de núcleo único intensivo de CPU, el muro y el usuario son casi lo mismo
para 2 núcleos, el usuario mide aproximadamente 2 veces la pared, lo que significa que el tiempo del usuario se cuenta en todos los subprocesos.
El usuario básicamente se duplicó, y mientras que la pared se mantuvo igual.
esto continúa hasta 8 subprocesos, lo que coincide con mi número de hyperthreads en mi computadora.
Después de 8, el muro comienza a aumentar también, ¡porque no tenemos CPU adicionales para poner más trabajo en un período de tiempo determinado!
La relación se estabiliza en este punto.
Tenga en cuenta que este gráfico es tan claro y simple porque el trabajo está puramente vinculado a la CPU: si estuviera vinculado a la memoria, obtendríamos una caída en el rendimiento mucho antes con menos núcleos porque los accesos a la memoria serían un cuello de botella como se muestra en What Qué significan los términos "enlazado a la CPU" y "enlazado de E / S"?
Sys trabajo pesado con sendfile
La carga de trabajo de sys más pesada que se me ocurrió fue usar el sendfile
, que realiza una operación de copia de archivos en el espacio del kernel: copie un archivo de una manera sana, segura y eficiente
Así que imaginé que este en el núcleo memcpy
será una operación intensiva de la CPU.
Primero inicializo un gran archivo aleatorio de 10GiB con:
dd if=/dev/urandom of=sendfile.in.tmp bs=1K count=10M
Luego ejecuta el código:
#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;
}
que proporciona básicamente el tiempo del sistema como se esperaba:
real 0m2.175s
user 0m0.001s
sys 0m1.476s
También tenía curiosidad por ver si time
distinguiría entre syscalls de diferentes procesos, así que intenté:
time ./sendfile.out sendfile.in1.tmp sendfile.out1.tmp &
time ./sendfile.out sendfile.in2.tmp sendfile.out2.tmp &
Y el resultado fue:
real 0m3.651s
user 0m0.000s
sys 0m1.516s
real 0m4.948s
user 0m0.000s
sys 0m1.562s
El tiempo del sistema es casi el mismo para ambos que para un solo proceso, pero el tiempo del muro es mayor porque los procesos compiten por un acceso de lectura de disco probable.
Por lo tanto, parece que de hecho explica qué proceso inició un trabajo de núcleo dado.
Código fuente de Bash
Cuando lo hace solo time <cmd>
en Ubuntu, usa la palabra clave Bash como se puede ver en:
type time
que salidas:
time is a shell keyword
Entonces grep fuente en el código fuente Bash 4.19 para la cadena de salida:
git grep '"user\b'
lo que nos lleva a la función execute_cmd.ctime_command
, que usa:
gettimeofday()
y getrusage()
si ambos están disponiblestimes()
de otra maneratodos los cuales son llamadas al sistema Linux y funciones POSIX .
Código fuente de GNU Coreutils
Si lo llamamos como:
/usr/bin/time
entonces usa la implementación GNU Coreutils.
Este es un poco más complejo, pero la fuente relevante parece estar en resuse.c y lo hace:
wait3
llamada BSD no POSIX si está disponibletimes
y de lo gettimeofday
contrarioReal muestra el tiempo total de respuesta para un proceso; mientras que el usuario muestra el tiempo de ejecución de las instrucciones definidas por el usuario y Sys es el tiempo para ejecutar las llamadas al sistema.
El tiempo real también incluye el tiempo de espera (el tiempo de espera para E / S, etc.)
time
, haga que haga algo que tome al menos un segundo.time
es una palabra clave bash. Por lo que escribirman time
es que no le da una página del manual para la fiestatime
, más bien se está dando a la página del manual/usr/bin/time
. Esto me ha hecho tropezar.