Что означают 'real', 'user' и 'sys' в выводе времени (1)? - PullRequest
1510 голосов
/ 17 февраля 2009
$ time foo
real        0m0.003s
user        0m0.000s
sys         0m0.004s
$

Что означают «реальные», «пользователь» и «sys» в выводе времени?

Какой из них имеет значение при сравнении моего приложения?

Ответы [ 5 ]

1801 голосов
/ 17 февраля 2009

Статистика реального, пользовательского и системного времени

Одна из этих вещей не похожа на другую. Реальное относится к фактическому прошедшему времени; Пользователь и 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 или любой другой операционной системе с защищенной памятью режим 'Kernel' или 'Supervisor' относится к привилегированному режиму , в котором может работать ЦП. Некоторые привилегированные действия, которые может повлиять на безопасность или стабильность может быть сделано только тогда, когда процессор работает в этом режиме; эти действия недоступны для кода приложения. Примером такого действия может быть манипулирование MMU для получения доступа к адресному пространству другого процесса. Обычно код пользовательского режима не может этого сделать (по уважительной причине), хотя он может запросить общей памяти из ядра, которое может быть прочитано или записано более одного процесса. В этом случае общая память запрашивается у ядра через безопасный механизм, и оба процесса должны явно подключиться к ней, чтобы использовать ее.

Привилегированный режим обычно называется режимом «ядро», поскольку ядро ​​выполняется процессором, работающим в этом режиме. Чтобы переключиться в режим ядра, необходимо выполнить специальную инструкцию (часто называемую trap ), которая переключает ЦП на работу в режиме ядра и запускает код из определенного расположение в таблице переходов. По соображениям безопасности вы не можете переключиться в режим ядра и выполнить произвольный код - управление ловушками осуществляется через таблицу адресов, в которую невозможно записать, если процессор не работает в режиме супервизора. Вы ловите с явным номером ловушки, и адрес ищется в таблице переходов; ядро имеет конечное число контролируемых точек входа.

Системные вызовы в библиотеке C (особенно те, которые описаны в Разделе 2 справочных страниц) содержат компонент пользовательского режима, который вы фактически вызываете из своей программы на C. За кулисами они могут отправлять ядру один или несколько системных вызовов для выполнения определенных услуг, таких как ввод-вывод, но у них все еще есть код, работающий в пользовательском режиме. При желании также вполне можно напрямую перевести ловушку в режим ядра из любого кода пользовательского пространства, хотя вам может потребоваться написать фрагмент ассемблера, чтобы правильно настроить регистры для вызова. Страницу, описывающую системные вызовы, предоставляемые ядром Linux, и соглашения по настройке регистров можно найти здесь .

Подробнее о 'sys'

Есть вещи, которые ваш код не может сделать из пользовательского режима - например, выделение памяти или доступ к оборудованию (жесткий диск, сеть и т. Д.). Они находятся под наблюдением ядра, и только он может сделать это. Некоторые операции, которые вы выполняете (например, malloc или fread / fwrite), будут вызывать эти функции ядра, которые затем будут считаться временем 'sys'. К сожалению, это не так просто, как «каждый вызов malloc будет учитываться во время 'sys'". Вызов malloc выполнит некоторую собственную обработку (все еще учитывается во время «пользователя»), а затем где-то по пути он может вызвать функцию в ядре (считая во время «sys»). После возврата из вызова ядра, у пользователя будет еще некоторое время, а затем malloc вернется к вашему коду. Относительно того, когда происходит переключение, и сколько его тратится в режиме ядра ... вы не можете сказать. Это зависит от реализации библиотеки. Кроме того, другие, казалось бы, невинные функции могут также использовать malloc и т.п. в фоновом режиме, что снова будет иметь некоторое время в 'sys'.

241 голосов
/ 29 апреля 2010

Чтобы подробнее остановиться на принятом ответе , я просто хотел привести еще одну причину, по которой realuser + sys.

Имейте в виду, что real представляет фактическое истекшее время, в то время как значения user и sys представляют время выполнения ЦП. В результате в многоядерной системе время user и / или sys (а также их сумма) может на самом деле превышать реального времени. Например, в приложении Java, которое я запускаю для класса, я получаю следующий набор значений:

real    1m47.363s
user    2m41.318s
sys     0m4.013s
22 голосов
/ 24 ноября 2017

real : фактическое время, потраченное на запуск процесса от начала до конца, как если бы оно было измерено человеком с секундомером

пользователь : совокупное время, потраченное всеми процессорами во время вычисления

sys : совокупное время, затраченное всеми процессорами на системные задачи, такие как выделение памяти.

Обратите внимание, что иногда user + sys может быть больше реального, так как несколько процессоров могут работать параллельно.

14 голосов
/ 05 ноября 2010

Реальное показывает общее время оборота для процесса; в то время как пользователь показывает время выполнения пользовательских инструкций а Sys - время для выполнения системных вызовов!

Реальное время также включает время ожидания (время ожидания ввода-вывода и т. Д.)

12 голосов

Примеры минимального запуска 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, ноутбуке ThinkPad P51, процессоре Intel Core i7-7820HQ (4 ядра / 8 потоков), 2x оперативной памяти Samsung M471A2K43BB1-CRC (2x 16 ГБ) ).

сон

Не занятой сон не учитывается ни в user, ни в sys, только real.

Например, программа, которая спит секунду:

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

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

GitHub upstream .

выводит что-то вроде:

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

То же самое относится и к программам, заблокированным при вводе-выводе.

Например, следующая программа ждет, пока пользователь введет символ, и нажмите клавишу ввода:

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

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

GitHub upstream .

И если вы хотите на одну секунду, выводит, как в примере сна, что-то вроде:

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 + код сюжета .

Затем мы отображаем wall, user и sys как функцию количества потоков для фиксированных 10 ^ 10 итераций на моем 8-поточном процессоре с гиперпотоками:

enter image description here

График данных .

Из графика видно, что:

  • для одноядерных приложений, интенсивно использующих процессор, настенные и пользовательские примерно одинаковы

  • для 2 ядер, пользователь примерно в 2 раза больше стены, что означает, что время пользователя учитывается во всех потоках.

    пользователь в основном удвоился, а стена осталась прежней.

  • это продолжается до 8 потоков, что соответствует моему числу гиперпотоков в моем компьютере.

    После 8 стена также начинает увеличиваться, потому что у нас нет лишних процессоров, чтобы выполнять больше работы за данный промежуток времени!

    Соотношение плато на данный момент.

Sys тяжелая работа с sendfile

Самой тяжелой рабочей нагрузкой sys, которую я мог придумать, было использование sendfile, который выполняет операцию копирования файла в пространстве ядра: Копирование файла разумным, безопасным и эффективным способом

Итак, я представлял, что этот memcpy в ядре будет загружать процессор.

Сначала я инициализирую большой случайный файл 10 ГБ:

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;
}

GitHub upstream .

, что дает в основном системное время, как и ожидалось:

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

Время sys примерно одинаково для обоих процессов, но для одного времени больше, потому что процессы конкурируют за доступ к чтению с диска.

Похоже, что на самом деле он учитывает, какой процесс запустил данную работу ядра.

Исходный код 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 в противном случае
...