'real','user'和'sys'在time(1)的输出中意味着什么?

问题描述 投票:1518回答:5
$ time foo
real        0m0.003s
user        0m0.000s
sys         0m0.004s
$

“真实”,“用户”和“系统”在时间输出中意味着什么?

在对我的应用进行基准测试时哪一个有意义?

unix time benchmarking
5个回答
1834
投票

Real,User和Sys处理时间统计信息

其中一件事与另一件事情不同。实际是指实际经过的时间; User和Sys指的是仅由进程使用的CPU时间。

  • 真实是挂钟时间 - 从通话开始到结束的时间。这是所有经过的时间,包括其他进程使用的时间片和进程花费的时间(例如,如果它等待I / O完成)。
  • 用户是进程内用户模式代码(内核外)花费的CPU时间。这只是执行过程时使用的实际CPU时间。流程花费的其他流程和时间不计入此数字。
  • Sys是进程内核中花费的CPU时间。这意味着在内核中执行系统调用所花费的CPU时间,而不是仍在用户空间中运行的库代码。与'user'一样,这只是进程使用的CPU时间。有关内核模式(也称为“超级用户”模式)和系统调用机制的简要说明,请参见下文。

User+Sys将告诉您进程使用的实际CPU时间。请注意,这是跨所有CPU的,因此如果进程有多个线程(并且此进程在具有多个处理器的计算机上运行),则可能会超过Real报告的挂钟时间(通常会发生)。请注意,在输出中,这些数字包括所有子进程(及其后代)的UserSys时间以及它们可能被收集的时间,例如通过wait(2)waitpid(2),尽管底层系统调用会分别返回进程及其子进程的统计信息。

time (1)报道的统计数据的起源

time报告的统计数据来自各种系统调用。 “用户”和“系统”来自wait (2)POSIX)或times (2)POSIX),具体取决于特定系统。 '真实'是根据从gettimeofday (2)电话收集的开始和结束时间计算出来的。根据系统的版本,time也可以收集各种其他统计信息,例如上下文切换的数量。

在多处理器计算机上,多线程进程或分叉子进程的进程可能会比CPU总时间小 - 因为不同的线程或进程可能并行运行。此外,报告的时间统计来自不同的来源,因此对于非常短的运行任务记录的时间可能受到舍入误差的影响,如原始海报给出的示例所示。

关于内核与用户模式的简要介绍

在Unix或任何受保护的内存操作系统上,'Kernel' or 'Supervisor'模式指的是CPU可以运行的privileged mode。某些可能影响安全性或稳定性的特权操作只能在CPU以此模式运行时才能完成。这些操作不适用于应用程序代码。此类操作的一个示例可能是操纵MMU以访问另一个进程的地址空间。通常,user-mode代码不能这样做(有充分理由),尽管它可以从内核请求shared memory,它可以由多个进程读取或写入。在这种情况下,通过安全机制从内核显式请求共享内存,并且两个进程必须显式附加到它才能使用它。

特权模式通常称为“内核”模式,因为内核由在此模式下运行的CPU执行。为了切换到内核模式,你必须发出一个特定的指令(通常称为trap),它将CPU切换到在内核模式下运行,并从跳转表中保存的特定位置运行代码。出于安全原因,您无法切换到内核模式并执行任意代码 - 陷阱通过无法写入的地址表进行管理,除非CPU以管理员模式运行。使用显式陷阱编号进行陷阱,并在跳转表中查找地址;内核具有有限数量的受控入口点。

C库中的“系统”调用(特别是手册页第2节中描述的那些)具有用户模式组件,这是您实际从C程序调用的组件。在幕后,他们可以向内核发出一个或多个系统调用来执行特定服务(如I / O),但是他们仍然可以在用户模式下运行代码。如果需要,也可以从任何用户空间代码直接向内核模式发出陷阱,尽管您可能需要编写汇编语言片段来为调用正确设置寄存器。

关于'sys'的更多信息

您的代码无法通过用户模式执行某些操作 - 例如分配内存或访问硬件(HDD,网络等)。这些都在内核的监督下,只有它才能做到。像malloc orfread / fwrite这样的操作会调用这些内核函数,然后将计为'sys'时间。不幸的是,它并不像“每次调用malloc都会计入'sys'时间”那么简单。对malloc的调用会对它自己进行一些处理(仍然计入'用户'时间),然后在它可能调用内核函数的某个地方(在'sys'时间内计算)。从内核调用返回后,'user'中会有更多时间,然后malloc将返回到您的代码。至于何时发生切换,以及在内核模式下花费了多少......你不能说。这取决于库的实现。此外,其他看似无辜的功能也可能在后台使用malloc等,这将在'sys'中再次有一些时间。


246
投票

为了扩展accepted answer,我只是想提供realuser + sys的另一个原因。

请记住,real表示实际经过的时间,而usersys值表示CPU执行时间。因此,在多核系统上,user和/或sys时间(以及它们的总和)实际上可以超过实时。例如,在我正在为类运行的Java应用程序中,我得到以下值:

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

24
投票

•real:从开始到结束运行过程所花费的实际时间,就好像是由带有秒表的人测量的

•user:计算期间所有CPU花费的累计时间

•sys:所有CPU在系统相关任务(如内存分配)期间所累积的时间。

请注意,有时user + sys可能大于real,因为多个处理器可能并行工作。


14
投票

Real表示流程的总周转时间;用户显示用户定义指令的执行时间,而Sys用于执行系统调用的时间!

实时包括等待时间(I / O的等待时间等)


14
投票

最小的可运行POSIX C示例

为了使事情更具体,我想用一些最小的C测试程序来举例说明time的一些极端情况。

所有程序都可以编译和运行:

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 CPU(4核/ 8线程),2x Samsung M471A2K43BB1-CRC RAM(2x 16GiB)中进行了测试。

睡觉

非忙碌的睡眠不计入usersys,只计算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

对于阻止IO的程序变得可用也是如此。

例如,以下程序等待用户输入字符并按Enter键:

#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线程执行无用CPU重载的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 + plot code

然后我们绘制wall,user和sys作为我的8个超线程CPU上固定的10 ^ 10次迭代的线程数的函数:

enter image description here

Plot data

从图中,我们看到:

  • 对于CPU密集型单核应用程序,墙和用户大致相同
  • 对于2个核心,用户大约是2倍墙,这意味着用户时间在所有线程中计算。 用户基本上加倍,而墙壁保持不变。
  • 这最多延续8个线程,这与我计算机中的超线程数相匹配。 在8之后,墙也开始增加,因为我们没有任何额外的CPU来在给定的时间内完成更多的工作! 此时的比例稳定。

Sys与sendfile的工作量很大

我能想到的最重的系统工作负载是使用sendfile,它在内核空间上执行文件复制操作:Copy a file in a sane, safe and efficient way

所以我想象这个内核中的memcpy将是一个CPU密集型操作。

首先,我用以下内容初始化一个大的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;
}

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源代码

当您在Ubuntu上执行time <cmd>时,它使用Bash关键字,如下所示:

type time

哪个输出:

time is a shell keyword

所以我们在输出字符串的Bash 4.19源代码中grep source:

git grep '"user\b'

这导致我们使用execute_cmd.c函数time_command,它使用:

  • 如果两者都有,gettimeofday()getrusage()
  • times()否则

所有这些都是Linux system callsPOSIX functions

GNU Coreutils源代码

如果我们称之为:

/usr/bin/time

然后它使用GNU Coreutils实现。

这个有点复杂,但相关的来源似乎是在resuse.c,它确实:

  • 一个非POSIX BSD wait3调用,如果有的话
  • 否则timesgettimeofday
© www.soinside.com 2019 - 2024. All rights reserved.