$ time foo
real 0m0.003s
user 0m0.000s
sys 0m0.004s
$
当我使用time命令时,输出结果中的real
、user
和sys
分别代表什么意思?在对我的应用程序进行基准测试时,哪一个是有意义的呢?
$ time foo
real 0m0.003s
user 0m0.000s
sys 0m0.004s
$
当我使用time命令时,输出结果中的real
、user
和sys
分别代表什么意思?在对我的应用程序进行基准测试时,哪一个是有意义的呢?
真实时间、用户时间和系统时间统计
其中有一项与其他不同。真实时间指实际经过的时间;用户时间和系统时间只指进程自身使用的CPU时间。
真实时间是墙上时钟时间——从调用开始到结束的时间。这包括其他进程使用的时间片和进程阻塞的时间(例如,如果它正在等待I/O完成)。
用户时间是进程内部在用户模式代码(内核外)中花费的CPU时间。这仅是执行进程所使用的实际CPU时间。其他进程和进程阻塞的时间不计入此数字。
系统时间是进程内核中花费的CPU时间。这意味着在内核中执行系统调用所花费的CPU时间,而不是在库代码中运行的CPU时间,后者仍在用户空间中运行。与“用户”类似,这仅是进程使用的CPU时间。下面简要描述了内核模式(也称为“监管员”模式)和系统调用机制。
User + Sys
会告诉您进程实际使用的 CPU 时间。请注意,这是跨所有 CPU 的时间,因此如果进程具有多个线程(并且该进程在具有多个处理器的计算机上运行),它可能会超过由 Real
报告的挂钟时间(通常发生)。请注意,在输出中,这些数字还包括所有子进程(及其后代)的 User
和 Sys
时间,以及它们可以被收集的时间,例如通过 wait(2)
或 waitpid(2)
,尽管底层系统调用分别返回进程及其子进程的统计信息。
time(1)
报告的统计信息来源
time
报告的统计数据来自各种系统调用。'User' 和 'Sys' 来自 wait (2)
(POSIX) 或 times (2)
(POSIX),具体取决于特定的系统。'Real' 是通过从 gettimeofday(2)
调用中获取的开始和结束时间计算出来的。根据系统版本,还可以通过 time
收集其他各种统计信息,例如上下文切换次数。
在多处理器机器上,多线程进程或分叉子进程的经过时间可能小于总 CPU 时间-因为不同的线程或进程可能并行运行。此外,所报告的时间统计信息来自不同的来源,因此对于非常短的运行任务记录的时间可能会受到舍入误差的影响,正如原帖中给出的示例所示。
内核模式与用户模式简介
在Unix或任何受保护内存的操作系统中,'内核'或'监管者'模式是指CPU可以运行的特权模式。只有当CPU处于此模式时,才能执行某些可能影响安全性或稳定性的特权操作;这些操作对应用程序代码不可用。这样一个操作的例子可能是操纵MMU以获得对另一个进程地址空间的访问权限。通常,用户模式代码无法做到这一点(理由充分),尽管它可以从内核请求共享内存,该内存可以被多个进程读取或写入。在这种情况下,共享内存通过安全机制明确地从内核请求,并且两个进程都必须明确地附加到它才能使用它。
特权模式通常被称为“内核”模式,因为内核在此模式下由CPU执行。要切换到内核模式,您必须发出特定的指令(通常称为陷阱),将CPU切换到运行在内核模式下并从跳转表中保存的特定位置运行代码。出于安全原因,您不能切换到内核模式并执行任意代码-陷阱通过地址表进行管理,除非CPU正在运行在监管者模式下,否则无法写入。您使用显式陷阱号进行陷阱,然后在跳转表中查找地址;内核具有有限数量的受控入口点。你的代码有些事情是无法在用户模式下完成的,比如分配内存或访问硬件(硬盘、网络等)。这些由内核监管,只有内核才能完成。一些操作,例如malloc
或fread
/fwrite
将调用这些内核函数,并且这将被计算为“sys”时间。不幸的是,事情并不像“每次调用malloc都会计入'sys'时间”这么简单。调用malloc
将执行一些自己的处理(仍然计入'user'时间),然后可能沿途调用内核函数(计入'sys'时间)。从内核调用返回后,还需要一些时间来计入'user',然后malloc
将返回到您的代码中。至于何时发生切换以及多少时间在内核模式下度过…您不能说。它取决于库的实现。此外,其他看似无害的函数也可能在后台使用malloc
等,这将再次消耗一些'sys'时间。
进一步解释被接受的回答,我想提供另一个原因来说明real
≠ user
+ sys
。
记住,real
代表实际经过时间,而user
和sys
值表示CPU执行时间。因此,在多核系统上,user
和/或sys
时间(以及它们的总和)实际上可能会超过实际时间。例如,在运行我课程中的Java应用程序时,我得到了这组数值:
real 1m47.363s
user 2m41.318s
sys 0m4.013s
real
超过user
和sys
的总和怎么样?操作系统开销例如线程上下文切换可能是原因吗? - Muhammad Gelbana• real:实际运行进程从开始到结束所花费的时间,就像用秒表测量一样。
• user:所有CPU在计算过程中累计的时间。
• sys:所有CPU在执行系统相关任务(例如内存分配)时累计的时间。
请注意,有时候 user + sys 的总和可能大于 real,因为多个处理器可能并行工作。
real
比 user+sys
更多,这是因为我同时运行了很多并行进程。最简可运行的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
时间,而不计算user
或sys
时间。
例如,一个睡眠一秒钟的程序:
#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
#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
#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;
}
然后,我们绘制墙壁、用户和系统作为线程数量的函数,对于我的8个超线程CPU上的固定10^10次迭代:
绘制数据。
从图表中,我们可以看到:
对于一个CPU密集型的单核应用程序,wall和user大致相同。
对于2个核心,user大约是wall的2倍,这意味着用户时间在所有线程中计算。
用户时间基本上翻了一倍,而wall保持不变。
这一趋势持续到8个线程,与我的计算机的超线程数相匹配。
在8个线程之后,wall也开始增加,因为我们没有额外的CPU来在给定的时间内处理更多的工作!
这个比例在这一点上达到了平台期。
sendfile
进行系统繁重的工作
我能想到的最重的系统工作负载是使用sendfile
,它在内核空间执行文件复制操作:以一种合理、安全和高效的方式复制文件。memcpy
将是一个占用CPU资源的操作。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源代码
当你在Ubuntu上只使用time <cmd>
时,它使用的是Bash关键字,可以从以下内容看出:
type time
time is a shell keyword
git grep '"user\b'
time_command
,它使用以下内容:
gettimeofday()
和getrusage()
times()
/usr/bin/time
wait3
调用times
和gettimeofday
Real 展示了进程的总周转时间;而 User 展示了用户定义指令的执行时间,Sys 则是系统调用的执行时间!
Real 时间还包括等待时间(如 I/O 等待时间)。
简单来说,我认为可以这样理解:
real
是运行命令实际花费的时间(就像你用秒表计时一样)
user
和 sys
表示执行命令时 CPU
所需完成的工作量,这个“工作量”以时间单位表示。
一般来说:
user
表示 CPU
运行命令代码所需完成的工作量sys
表示 CPU
处理“系统开销”类型任务(例如分配内存、文件I/O等)所需完成的工作量,以支持正在运行的命令由于最后两个时间都是计算完成的“工作”,它们不包括线程可能等待的时间(例如等待另一个进程或磁盘I/O完成)。
然而,real
表示实际运行时间而不是“工作”时间,因此它包括任何等待时间(这就是为什么有时候 real > usr+sys
)。
最后,对于多线程应用程序,有时出现反向情况(usr+sys > real
)。这也是因为我们在比较“工作时间”和实际时间。例如,如果3个处理器连续运行10分钟来执行一个命令,则会得到 real = 10m
但 usr = 30m
。
我想提到一种实时时间远远大于用户+系统时间的其他场景。我创建了一个简单的服务器,响应时间很长。
real 4.784
user 0.01s
sys 0.01s
问题在于,在这种情况下,进程等待的响应既不在用户站点上,也不在系统中。
当你运行 find
命令时,类似的情况会发生。在这种情况下,大部分时间都花费在请求并从 SSD 获取响应。
必须提到的是,在我的 AMD Ryzen CPU 上,多线程程序中的 user
始终大于 real
(或者使用 -O3
编译的单线程程序)。
例如:
real 0m5.815s
user 0m8.213s
sys 0m0.473s
real
时间将小于user
时间,我只有Ryzen CPU没有Intel CPU。 - neoedmund-ftree-parallelize-loops=4
或使用#pragma omp parallel for ...
+ -fopenmp
才会这样做。) - Peter Cordesuser + sys <= real
,我非常确定。如果更高,那就意味着它正在使用多个线程。(或者如果实际时间略低,比如一毫秒,那可能只是计时精度问题,比如没有使用完整的滴答间隔,或者如果舍入误差朝另一个方向走了,那么你实际使用的用户时间可能会被收取更多的费用。) - Peter Cordes
time
测量整个程序,请让它执行需要至少一秒钟的任务。 - Peter Cordestime
是 Bash 关键字。因此,输入man time
命令并不会为 Bash 的time
提供 man 手册页,而是提供/usr/bin/time
的手册页。这点曾经让我感到困惑。 - irritable_phd_syndromehelp time
显示了如何使用 bash 的time
命令。我最后运行了类似于/usr/bin/time --format "%e"
的命令。 - undefined