time(1)命令输出中的'real'、'user'和'sys'是什么意思?

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

当我使用time命令时,输出结果中的realusersys分别代表什么意思?在对我的应用程序进行基准测试时,哪一个是有意义的呢?


4
@Casillass Real - https://dev59.com/r3E95IYBdhLWcg3wN7Kv - ConcernedOfTunbridgeWells
19
如果你的程序退出得太快,那么它们中没有一个是有意义的,这只是启动开销。如果你想用time测量整个程序,请让它执行需要至少一秒钟的任务。 - Peter Cordes
28
需要注意的是,time 是 Bash 关键字。因此,输入 man time 命令并不会为 Bash 的 time 提供 man 手册页,而是提供 /usr/bin/time 的手册页。这点曾经让我感到困惑。 - irritable_phd_syndrome
@irritable_phd_syndrome 这个也让我有点困惑。help time 显示了如何使用 bash 的 time 命令。我最后运行了类似于 /usr/bin/time --format "%e" 的命令。 - undefined
8个回答

2627

真实时间、用户时间和系统时间统计

其中有一项与其他不同。真实时间指实际经过的时间;用户时间和系统时间只指进程自身使用的CPU时间。

  • 真实时间是墙上时钟时间——从调用开始到结束的时间。这包括其他进程使用的时间片和进程阻塞的时间(例如,如果它正在等待I/O完成)。

  • 用户时间是进程内部在用户模式代码(内核外)中花费的CPU时间。这仅是执行进程所使用的实际CPU时间。其他进程和进程阻塞的时间不计入此数字。

  • 系统时间是进程内核中花费的CPU时间。这意味着在内核中执行系统调用所花费的CPU时间,而不是在库代码中运行的CPU时间,后者仍在用户空间中运行。与“用户”类似,这仅是进程使用的CPU时间。下面简要描述了内核模式(也称为“监管员”模式)和系统调用机制。

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

time(1)报告的统计信息来源

< p > time 报告的统计数据来自各种系统调用。'User' 和 'Sys' 来自 wait (2) (POSIX) 或 times (2) (POSIX),具体取决于特定的系统。'Real' 是通过从 gettimeofday(2) 调用中获取的开始和结束时间计算出来的。根据系统版本,还可以通过 time 收集其他各种统计信息,例如上下文切换次数。

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

内核模式与用户模式简介

在Unix或任何受保护内存的操作系统中,'内核'或'监管者'模式是指CPU可以运行的特权模式。只有当CPU处于此模式时,才能执行某些可能影响安全性或稳定性的特权操作;这些操作对应用程序代码不可用。这样一个操作的例子可能是操纵MMU以获得对另一个进程地址空间的访问权限。通常,用户模式代码无法做到这一点(理由充分),尽管它可以从内核请求共享内存,该内存可以被多个进程读取或写入。在这种情况下,共享内存通过安全机制明确地从内核请求,并且两个进程都必须明确地附加到它才能使用它。

特权模式通常被称为“内核”模式,因为内核在此模式下由CPU执行。要切换到内核模式,您必须发出特定的指令(通常称为陷阱),将CPU切换到运行在内核模式下并从跳转表中保存的特定位置运行代码。出于安全原因,您不能切换到内核模式并执行任意代码-陷阱通过地址表进行管理,除非CPU正在运行在监管者模式下,否则无法写入。您使用显式陷阱号进行陷阱,然后在跳转表中查找地址;内核具有有限数量的受控入口点。
C库中的“系统”调用(尤其是man页面第2节中描述的那些)具有用户模式组件,这是您实际从C程序中调用的内容。在幕后,它们可能会向内核发出一个或多个系统调用来执行特定服务,例如I/O,但它们仍然在用户模式下运行代码。如果需要,也可以直接从任何用户空间代码发出陷阱以进入内核模式,尽管您可能需要编写一小段汇编语言代码以正确设置寄存器。
关于“sys”的更多信息

你的代码有些事情是无法在用户模式下完成的,比如分配内存或访问硬件(硬盘、网络等)。这些由内核监管,只有内核才能完成。一些操作,例如mallocfread/fwrite将调用这些内核函数,并且这将被计算为“sys”时间。不幸的是,事情并不像“每次调用malloc都会计入'sys'时间”这么简单。调用malloc将执行一些自己的处理(仍然计入'user'时间),然后可能沿途调用内核函数(计入'sys'时间)。从内核调用返回后,还需要一些时间来计入'user',然后malloc将返回到您的代码中。至于何时发生切换以及多少时间在内核模式下度过…您不能说。它取决于库的实现。此外,其他看似无害的函数也可能在后台使用malloc等,这将再次消耗一些'sys'时间。


21
子进程所花费的时间是否计入实际时间/系统时间? - ron
5
User+Sys 可以测量进程的 CPU 使用情况,您可以用它来进行性能基准测试。这对于多线程代码特别有用,因为可能会有多个 CPU 核心在计算上工作。 - ConcernedOfTunbridgeWells
2
虽然不是很相关,但运行“\time <cmd>”很有趣-它提供了更多细节(请原谅评论中的格式不佳):$ time ps PID TTY TIME CMD 9437 pts/19 00:00:00 bash 11459 pts/19 00:00:00 psreal 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.00user 0.01system 0:00.02elapsed 95%CPU (0avgtext+0avgdata 2160maxresident)k 0inputs+0outputs (0major+103minor)pagefaults 0swaps$ - kaiwan
2
很好。你这些细节是从哪里得到的? - Brettski
2
大部分内容都在time(1)的man页面中。其余的我是从大学的操作系统课程中学到的。有一些书籍(例如Bach或Stevens)涵盖了这些材料。 - ConcernedOfTunbridgeWells
显示剩余10条评论

367

进一步解释被接受的回答,我想提供另一个原因来说明realuser + sys

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

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

17
我一直对这个问题很好奇。由于我知道我的程序是单线程的,所以用户时间和实际时间之间的差异一定是虚拟机开销,对吗? - Quantum7
20
在Solaris机器上的Sun JVM以及在Mac OS X上的Apple JVM即使在单线程应用程序中也可以使用多个内核。如果您对Java进程进行取样,您会发现像垃圾回收这样的任务运行在单独的线程上(还有一些其他的东西,我暂时想不起来了)。但我不知道您是否真的想把这称为“VM开销”。 - lensovet
4
我猜测你现在得到了足够的赞数,以获得声望:D。那么你认为real超过usersys的总和怎么样?操作系统开销例如线程上下文切换可能是原因吗? - Muhammad Gelbana
41
另一个可能存在的问题是I/O:如果您的应用程序花费了大量时间等待接收文件或流,则显然实际时间会远远超过用户/系统时间,因为在等待获取文件或类似物品的访问权限时不使用CPU时间。 - lensovet
@MuhammadGelbana 这并不一定是额外开销。当计算机受到定时器中断时,它会决定切换到一些守护进程以保持它们的运行,X11以保持您的窗口平滑,并在短短几秒钟内返回到您的进程。大部分时间不会是额外开销,但仍然会花费时间。大约<1%的失去的时间将是来自其他进程的“sys”时间,因此这将是操作系统开销,例如上下文切换,这将计入正在运行的进程的实际但不是用户/系统时间。然而,大部分(real - (user+sys))将是其他程序的“user”时间。 - Nicholas Pipitone
显示剩余5条评论

83

real:实际运行进程从开始到结束所花费的时间,就像用秒表测量一样。

user:所有CPU在计算过程中累计的时间。

sys:所有CPU在执行系统相关任务(例如内存分配)时累计的时间。

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


5
“real”通常被描述为“挂钟时间”。 - Peter Cordes
1
在我的情况下,有时候 realuser+sys 更多,这是因为我同时运行了很多并行进程。
87.02 real 14.12 user 5.20 sys
41.30 real 7.03 user 3.20 sys
2387.46 real 750.67 user 282.80 sys
2.60 real 7.22 user 3.35 sys
- Diaa Mohamed Kasem

64

最简可运行的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)上进行了测试。

sleep系统调用

通过sleep系统调用进行的非忙碌睡眠只计算real时间,而不计算usersys时间。

例如,一个睡眠一秒钟的程序:

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

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

GitHub 上游

输出结果类似于:

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

GitHub 上游

如果你等待大约一秒钟,它会输出类似于睡眠示例的东西:

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

出于这个原因,时间可以帮助你区分CPU和IO绑定的程序:什么是"CPU绑定"和"I/O绑定"的意思? 多线程
以下示例在nthreads个线程上执行niters次无用的纯CPU绑定工作。
#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上游+绘图代码

然后,我们绘制墙壁、用户和系统作为线程数量的函数,对于我的8个超线程CPU上的固定10^10次迭代:

enter image description here

绘制数据

从图表中,我们可以看到:

  • 对于一个CPU密集型的单核应用程序,wall和user大致相同。

  • 对于2个核心,user大约是wall的2倍,这意味着用户时间在所有线程中计算。

    用户时间基本上翻了一倍,而wall保持不变。

  • 这一趋势持续到8个线程,与我的计算机的超线程数相匹配。

    在8个线程之后,wall也开始增加,因为我们没有额外的CPU来在给定的时间内处理更多的工作!

    这个比例在这一点上达到了平台期。

请注意,这个图表之所以如此清晰简单,是因为这项工作纯粹是CPU密集型的:如果它是内存密集型的,那么我们会在核心较少的情况下更早地出现性能下降,因为内存访问将成为瓶颈,如“CPU bound”和“I/O bound”这些术语是什么意思?所示。
快速检查墙上时间与用户时间的比例是确定程序是否多线程的简单方法,而且这个比例越接近核心数,并行化的效果就越好,例如:
- 多线程链接器:gcc在链接时可以使用多个核心吗? - C++并行排序:C++17并行算法已经实现了吗? 使用sendfile进行系统繁重的工作 我能想到的最重的系统工作负载是使用sendfile,它在内核空间执行文件复制操作:以一种合理、安全和高效的方式复制文件
所以我想象中,这个内核中的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

系统时间对于两个进程来说大致相同,就像对于一个单独的进程一样,但是墙上时间更长,因为进程们可能会竞争磁盘读取访问。

所以看起来它确实考虑了哪个进程启动了给定的内核工作。

Bash源代码

当你在Ubuntu上只使用time <cmd>时,它使用的是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调用
  • 否则使用timesgettimeofday
相关问题

17

Real 展示了进程的总周转时间;而 User 展示了用户定义指令的执行时间,Sys 则是系统调用的执行时间!

Real 时间还包括等待时间(如 I/O 等待时间)。


13

简单来说,我认为可以这样理解:

  • real 是运行命令实际花费的时间(就像你用秒表计时一样)

  • usersys 表示执行命令时 CPU 所需完成的工作量,这个“工作量”以时间单位表示。

一般来说:

  • user 表示 CPU 运行命令代码所需完成的工作量
  • sys 表示 CPU 处理“系统开销”类型任务(例如分配内存、文件I/O等)所需完成的工作量,以支持正在运行的命令

由于最后两个时间都是计算完成的“工作”,它们不包括线程可能等待的时间(例如等待另一个进程或磁盘I/O完成)。

然而,real 表示实际运行时间而不是“工作”时间,因此它包括任何等待时间(这就是为什么有时候 real > usr+sys)。

最后,对于多线程应用程序,有时出现反向情况(usr+sys > real)。这也是因为我们在比较“工作时间”和实际时间。例如,如果3个处理器连续运行10分钟来执行一个命令,则会得到 real = 10musr = 30m


4

我想提到一种实时时间远远大于用户+系统时间的其他场景。我创建了一个简单的服务器,响应时间很长。

real 4.784
user 0.01s
sys  0.01s

问题在于,在这种情况下,进程等待的响应既不在用户站点上,也不在系统中。

当你运行 find 命令时,类似的情况会发生。在这种情况下,大部分时间都花费在请求并从 SSD 获取响应。


-4

必须提到的是,在我的 AMD Ryzen CPU 上,多线程程序中的 user 始终大于 real(或者使用 -O3 编译的单线程程序)。

例如:

real    0m5.815s
user    0m8.213s
sys 0m0.473s

你可以编写一个多线程程序,其中线程大部分时间都在睡眠(例如用于多线程I/O),在这种情况下,用户时间的总CPU秒数可能会低于挂钟“实际时间”。但是,在具有多个核心的系统上,任何CPU密集型的多线程程序通常都会使用超过每秒1个CPU秒的实际时间。这就是重点。Ciro's answer中的图表显示了用户时间随线程缩放的情况。 - Peter Cordes
我编写了一个单线程的C程序,并使用-O3进行编译,然后real时间将小于user时间,我只有Ryzen CPU没有Intel CPU。 - neoedmund
AMD和Intel的CPU在这方面没有区别。不确定您的观点是什么或单线程测试的意义何在。是的,当然,单线程程序将具有user+sys <= real,这是有保证的。但是,如果所有线程都花费大量时间处于睡眠状态(例如等待I/O),则多线程程序也可能具有此功能。例如,一个不太繁忙的多线程Web服务器。 - Peter Cordes
哦,等一下,抱歉,我误读了你之前的评论。这不是正常情况,除非你的编译器自动并行化,而GCC默认情况下不会这样做。(只有在像这个例子中手动启用-ftree-parallelize-loops=4或使用#pragma omp parallel for ... + -fopenmp才会这样做。) - Peter Cordes
一个真正的单线程程序总是会有 user + sys <= real,我非常确定。如果更高,那就意味着它正在使用多个线程。(或者如果实际时间略低,比如一毫秒,那可能只是计时精度问题,比如没有使用完整的滴答间隔,或者如果舍入误差朝另一个方向走了,那么你实际使用的用户时间可能会被收取更多的费用。) - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接