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

real、user和sys在时间输出中意味着什么?在对我的应用进行基准测试时,哪一项是有意义的?


当前回答

简单地说,我喜欢这样想:

real是运行命令所需的实际时间(就像用秒表计时一样)user和sys是CPU执行命令所需的“工作量”。这项“工作”以时间单位表示。

一般来说:

user是CPU运行命令代码所做的工作量sys是CPU为了支持运行命令而处理“系统开销”类型任务(如分配内存、文件I/O等)所需的工作量

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

然而,real是实际运行时间的度量,而不是“工作”,因此它确实包括了等待所花费的时间。

其他回答

•真实:从开始到结束运行过程所花费的实际时间,就像是由人用秒表测量的一样

•用户:计算过程中所有CPU花费的累计时间

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

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

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

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

为了进一步解释公认的答案,我只想提供另一个原因,为什么real≠user+sys。

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

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

必须提到的是,至少在我的AMD Ryzen CPU上,在多线程程序(或使用-O3编译的单线程程序)中,用户总是比实际用户大。

eg.

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

最少可运行的POSIX C示例

为了使事情更具体,我想用一些最小的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 CPU(4核/8线程)、2x三星M471A2K43B1-CRC RAM(2x 16GiB)中测试。

休眠系统调用

睡眠系统调用所做的非繁忙睡眠仅在实际情况下有效,而对用户或系统无效。

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

#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可用时被阻止的程序也是如此。

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

#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绑定”是什么意思?

多个线程

下面的示例在n个线程上执行无数次无用的纯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上游+绘图代码。

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

绘图数据。

从图中可以看出:

对于CPU密集型单核应用程序,墙和用户几乎相同对于2个内核,用户大约是2xwall,这意味着所有线程都会计算用户时间。用户基本上增加了一倍,而墙壁保持不变。这将持续到8个线程,这与我的计算机中的超线程数相匹配。8岁之后,墙壁也开始增加,因为我们没有任何额外的CPU来在给定的时间内投入更多的工作!该比率在这一点上趋于稳定。

请注意,此图之所以如此清晰和简单,是因为工作完全受CPU限制:如果它受内存限制,那么我们将在更少内核的情况下更早地降低性能,因为内存访问将成为瓶颈,如“CPU限制”和“I/O限制”所示?

快速检查wall<user是确定程序是多线程的一种简单方法,并且该比率与内核数量越接近,并行化就越有效,例如:

多线程链接器:gcc在链接时可以使用多个内核吗?C++并行排序:C++17并行算法已经实现了吗?

使用sendfile进行系统繁重的工作

我能想到的最繁重的系统工作负载是使用sendfile,它在内核空间上执行文件复制操作:以理智、安全和高效的方式复制文件

所以我设想内核内存将是一个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上游。

这基本上给出了预期的大部分系统时间:

real    0m2.175s
user    0m0.001s
sys     0m1.476s

我也很好奇时间是否能区分不同进程的系统调用,所以我尝试了:

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源代码中为输出字符串grep源代码:

git grep '"user\b'

这导致我们执行_cmd.c函数time_command,它使用:

gettimeofday()和getrusage()(如果两者都可用)time()否则

所有这些都是Linux系统调用和POSIX函数。

GNU Coreutils源代码

如果我们称之为:

/usr/bin/time

然后它使用GNU Coreutils实现。

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

非POSIX BSD wait3调用(如果可用)时间和到达时间,否则1: https://i.stack.imgur.com/qAfEe.png**最少可运行的POSIX C示例**

为了使事情更具体,我想用一些最小的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 CPU(4核/8线程)、2x三星M471A2K43B1-CRC RAM(2x 16GiB)中测试。

睡觉

非繁忙睡眠在用户或系统中都不算,只算真实睡眠。

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

#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可用时被阻止的程序也是如此。

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

#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绑定”是什么意思?

多线程链接器:gcc在链接时可以使用多个内核吗?C++并行排序:C++17并行算法已经实现了吗?