Что означают 'real', 'user' и 'sys' в выводе времени (1)?
$ time foo
real 0m0.003s
user 0m0.000s
sys 0m0.004s
$
Что означает "реальный", "пользователь" и "sys" в выводе времени?
Какой из них имеет смысл при сравнении моего приложения?
8 ответов
Real, User и Sys обрабатывают статистику времени
Одна из этих вещей не похожа на другую. Реальное относится к фактическому прошедшему времени; Пользователь и Sys ссылаются на процессорное время, используемое только процессом.
Реальное время настенные часы - время от начала до конца разговора. Это все прошедшее время, включая временные интервалы, используемые другими процессами, и время, которое процесс блокирует (например, если он ожидает завершения ввода-вывода).
Пользователь - это количество процессорного времени, потраченного на код пользовательского режима (вне ядра) в процессе. Это только фактическое время процессора, используемое при выполнении процесса. Другие процессы и время, затраченное процессом заблокировано, не учитываются в этом показателе.
Sys - это количество процессорного времени, проведенного в ядре в процессе. Это означает выполнение процессорного времени, затраченного на системные вызовы в ядре, в отличие от библиотечного кода, который все еще выполняется в пользовательском пространстве. Как и "пользователь", это только время процессора, используемое процессом. Ниже приведено краткое описание режима ядра (также известного как режим 'supervisor') и механизма системных вызовов.
User+Sys
скажет вам сколько фактического процессорного времени ваш процесс использовал. Обратите внимание, что это относится ко всем процессорам, поэтому, если процесс имеет несколько потоков (и этот процесс выполняется на компьютере с более чем одним процессором), он может потенциально превысить время настенных часов, сообщаемое Real
(что обычно происходит). Обратите внимание, что в выходных данных эти цифры включают User
а также Sys
время всех дочерних процессов (и их потомков), а также когда они могли быть собраны, например, wait(2)
или же waitpid(2)
хотя базовые системные вызовы возвращают статистику для процесса и его дочерних элементов отдельно.
Происхождение статистики, представленной time (1)
Статистика, представленная time
собраны из различных системных вызовов. "Пользователь" и "Sys" происходят из wait (2)
( POSIX) или times (2)
( POSIX), в зависимости от конкретной системы. "Реальное" рассчитывается по времени начала и окончания, полученному из gettimeofday (2)
вызов. В зависимости от версии системы, различные другие статистические данные, такие как количество переключений контекста, также могут быть собраны time
,
На многопроцессорной машине многопоточный процесс или дочерний процесс может иметь истекшее время меньше, чем общее время ЦП, поскольку разные потоки или процессы могут работать параллельно. Кроме того, статистические данные о времени поступают из разных источников, поэтому время, записанное для очень коротких задач, может быть подвержено ошибкам округления, как показано в примере, приведенном в оригинальном постере.
Краткое руководство по режиму "Ядро против пользователя"
В Unix или любой другой операционной системе с защищенной памятью режим "Ядро" или "Супервизор" относится к привилегированному режиму, в котором может работать ЦПУ. Некоторые привилегированные действия, которые могут повлиять на безопасность или стабильность, могут выполняться только тогда, когда ЦП работает в этот режим; эти действия недоступны для кода приложения. Примером такого действия может быть манипулирование MMU для получения доступа к адресному пространству другого процесса. Обычно код пользовательского режима не может сделать это (по уважительной причине), хотя он может запрашивать общую память у ядра, которая может быть прочитана или записана несколькими процессами. В этом случае общая память запрашивается у ядра через безопасный механизм, и оба процесса должны явно подключаться к ней, чтобы использовать ее.
Привилегированный режим обычно называется режимом "ядро", поскольку ядро выполняется процессором, работающим в этом режиме. Чтобы переключиться в режим ядра, вы должны выполнить специальную инструкцию (часто называемую прерыванием), которая переключает ЦП на работу в режиме ядра и запускает код из определенного места, содержащегося в таблице переходов. По соображениям безопасности вы не можете переключиться в режим ядра и выполнить произвольный код - управление ловушками осуществляется через таблицу адресов, в которую невозможно записать, если процессор не работает в режиме супервизора. Вы ловите с явным номером ловушки, и адрес ищется в таблице переходов; ядро имеет конечное число контролируемых точек входа.
"Системные" вызовы в библиотеке C (особенно те, которые описаны в Разделе 2 справочных страниц) содержат компонент пользовательского режима, который вы фактически вызываете из своей программы на C. За кулисами они могут отправлять ядру один или несколько системных вызовов для выполнения определенных услуг, таких как ввод-вывод, но у них все еще есть код, работающий в пользовательском режиме. При желании также вполне можно напрямую перевести ловушку в режим ядра из любого кода пользовательского пространства, хотя вам может потребоваться написать фрагмент ассемблера, чтобы правильно настроить регистры для вызова. Страницу, описывающую системные вызовы, предоставляемые ядром Linux, и соглашения по настройке регистров можно найти здесь.
Подробнее о 'sys'
Есть вещи, которые ваш код не может сделать из пользовательского режима - например, выделение памяти или доступ к оборудованию (жесткий диск, сеть и т. Д.). Они находятся под наблюдением ядра, и только он может сделать это. Некоторые операции, которые вы делаете (например, malloc
или же fread
/ fwrite
) вызовет эти функции ядра и будет считаться временем 'sys'. К сожалению, это не так просто, как "каждый вызов malloc будет учитываться во время 'sys'". Призыв к malloc
выполнит некоторую собственную обработку (все еще учитывается во "пользовательском" времени), а затем где-нибудь по пути он может вызвать функцию в ядре (считая во "системном" времени). После возврата из вызова ядра будет еще некоторое время в 'user', а затем malloc
вернусь к вашему коду. Относительно того, когда происходит переключение, и сколько его тратится в режиме ядра... вы не можете сказать. Это зависит от реализации библиотеки. Кроме того, другие, казалось бы, невинные функции могут также использовать malloc
и тому подобное на заднем плане, который снова будет иметь некоторое время в 'sys'.
Чтобы расширить принятый ответ, я просто хотел представить другую причину, почему real
≠ user
+ sys
,
Имейте в виду, что real
представляет фактическое прошедшее время, в то время как user
а также sys
значения представляют время выполнения процессора. В результате в многоядерной системе user
и / или sys
время (а также их сумма) может фактически превышать реальное время. Например, в приложении Java, которое я запускаю для класса, я получаю этот набор значений:
real 1m47.363s
user 2m41.318s
sys 0m4.013s
• реальное: фактическое время, потраченное на запуск процесса от начала до конца, как если бы оно измерялось человеком с секундомером
• пользователь: совокупное время, затраченное всеми процессорами во время вычислений.
• sys: совокупное время, потраченное всеми процессорами на системные задачи, такие как выделение памяти.
Обратите внимание, что иногда user + sys может быть больше реального, так как несколько процессоров могут работать параллельно.
Минимальные выполнимые примеры POSIX C
Чтобы сделать вещи более конкретными, я хочу привести в пример несколько крайних случаев time
с некоторыми минимальными программами тестирования C.
Все программы могут быть скомпилированы и запущены с:
gcc -ggdb3 -o main.out -pthread -std=c99 -pedantic-errors -Wall -Wextra main.c'
time ./main.out
и были протестированы в Ubuntu 18.10, GCC 8.2.0, glibc 2.28, ядре Linux 4.18.
спать
Не занятой сон не учитывается user
или же sys
, только real
,
Например, программа, которая спит на секунду:
#define _XOPEN_SOURCE 700
#include <stdlib.h>
#include <unistd.h>
int main(void) {
sleep(1);
return EXIT_SUCCESS;
}
выводит что-то вроде:
real 0m1.003s
user 0m0.001s
sys 0m0.003s
То же самое относится и к программам, заблокированным на IO.
Например, следующая программа ждет, когда пользователь введет символ и нажмет ввод:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("%c\n", getchar());
return EXIT_SUCCESS;
}
И если вы хотите на одну секунду, выводит так же, как пример сна что-то вроде:
real 0m1.003s
user 0m0.001s
sys 0m0.003s
Несколько потоков
Следующий пример делает niters
итерации бесполезной тяжелой работы процессора nthreads
потоки:
#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 + сюжетный код.
Затем мы отображаем стену, пользователя и sys как функцию времени для фиксированных 10^10 итераций на моем 8-ми процессоре с гиперпоточностью:
Из графика мы видим, что:
для одноядерного приложения, интенсивно использующего процессор, стена и пользователь примерно одинаковы
для 2 ядер пользователь примерно в 2 раза больше стены, что означает, что пользовательское время учитывается во всех потоках
это продолжается до 8 потоков, что соответствует моему числу гиперпотоков.
Это немного ниже, чем в 8 раз, потому что другие процессы в моей системе должны конкурировать за время потока
После 8 это плато, потому что у нас нет лишних процессоров, чтобы выполнять больше работы за определенное время!
Sys тяжелая работа
Самая тяжелая рабочая нагрузка sys, которую я мог придумать, заключалась в использовании sendfile
, который выполняет операцию копирования файла в пространстве ядра: копирование файла в разумном, безопасном и эффективном виде
Так что я представил, что это в ядре memcpy
будет интенсивная загрузка процессора.
Сначала я инициализирую большой 10GiB случайный файл:
dd if=/dev/urandom of=sendfile.in.tmp bs=1K count=10M
Затем запустите код:
#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;
}
что дает в основном системное время, как и ожидалось:
real 0m2.175s
user 0m0.001s
sys 0m1.476s
Мне также было любопытно посмотреть, если time
будет различать системные вызовы разных процессов, поэтому я попытался:
time ./sendfile.out sendfile.in1.tmp sendfile.out1.tmp &
time ./sendfile.out sendfile.in2.tmp sendfile.out2.tmp &
И результат был:
real 0m3.651s
user 0m0.000s
sys 0m1.516s
real 0m4.948s
user 0m0.000s
sys 0m1.562s
Время системы примерно одинаково для обоих процессов, но время задержки больше, потому что процессы конкурируют за доступ к чтению с диска.
Таким образом, кажется, что он действительно учитывает, какой процесс начал работу с ядром.
Исходный код Bash
Когда вы просто time <cmd>
в Ubuntu используется ключевое слово Bash, как видно из:
type time
какие выводы:
time is a shell keyword
Итак, мы ищем исходный код в исходном коде Bash 4.19 для выходной строки:
git grep '"user\b'
что приводит нас к функции execute_cmd.c time_command
, который использует:
gettimeofday()
а такжеgetrusage()
если оба доступныtimes()
иначе
все это системные вызовы Linux и функции POSIX.
Исходный код GNU Coreutils
Если мы называем это как:
/usr/bin/time
затем он использует реализацию GNU Coreutils.
Это немного сложнее, но соответствующий источник, кажется, находится в `resuse.c, и это делает:
- не POSIX BSD
wait3
позвоните, если это доступно times
а такжеgettimeofday
иначе
Real показывает общее время оборота процесса; пока User показывает время выполнения пользовательских инструкций, а Sys - время выполнения системных вызовов!
Реальное время включает также время ожидания (время ожидания ввода-вывода и т. Д.)
Проще говоря, мне нравится думать об этом так:
real
это фактическое количество времени, которое потребовалось для выполнения команды (как если бы вы засекли время с помощью секундомера)user
а такжеsys
насколько "работают"CPU
пришлось сделать, чтобы выполнить команду. Эта "работа" выражается в единицах времени.
Вообще говоря:
user
сколько работыCPU
сделал для запуска, чтобы запустить код командыsys
сколько работыCPU
приходилось выполнять задачи типа "системные накладные расходы" (такие как выделение памяти, файловый ввод-вывод и т. д.), чтобы поддерживать выполняющуюся команду
Поскольку эти последние два раза учитывают выполненную "работу", они не включают время, которое поток мог потратить на ожидание (например, ожидание другого процесса или завершения дискового ввода-вывода).
real
, Однако, является мерой фактического выполнения, а не "работа", так же включать в себя любое время, затраченное на ожидание.
Я хочу упомянуть еще один сценарий, когда реальное время намного больше, чем user + sys. Я создал простой сервер, который долго отвечает
real 4.784
user 0.01s
sys 0.01s
проблема в том, что в этом сценарии процесс ожидает ответа, которого нет ни на сайте пользователя, ни в системе.
Нечто подобное происходит, когда вы запускаете
find
команда. В этом случае время тратится в основном на запрос и получение ответа от SSD.
Следует упомянуть, что, по крайней мере, на моем процессоре AMD Ryzen
user
всегда больше, чем
real
в многопоточной программе.
например.
real 0m5.815s
user 0m8.213s
sys 0m0.473s