$ time foo
real 0m0.003s
user 0m0.000s
sys 0m0.004s
$
real、user和sys在时间输出中意味着什么?在对我的应用进行基准测试时,哪一项是有意义的?
$ time foo
real 0m0.003s
user 0m0.000s
sys 0m0.004s
$
real、user和sys在时间输出中意味着什么?在对我的应用进行基准测试时,哪一项是有意义的?
当前回答
•真实:从开始到结束运行过程所花费的实际时间,就像是由人用秒表测量的一样
•用户:计算过程中所有CPU花费的累计时间
•sys:所有CPU在系统相关任务(如内存分配)期间所花费的累积时间。
注意,有时user+sys可能大于real,因为多个处理器可以并行工作。
其他回答
实时、用户和系统处理时间统计
其中一件事与另一件不同。Real指实际经过的时间;用户和系统指的是仅由进程使用的CPU时间。
真正的是从通话开始到结束的挂钟时间。这是所有经过的时间,包括其他进程使用的时间片和进程阻塞的时间(例如,如果它正在等待I/O完成)。User是进程内用户模式代码(内核外)所花费的CPU时间。这是执行过程中使用的唯一实际CPU时间。其他流程和流程被阻塞的时间不计入该数字。Sys是进程中内核所花费的CPU时间。这意味着在内核内执行系统调用所花费的CPU时间,而不是仍然在用户空间中运行的库代码。与“用户”一样,这只是进程使用的CPU时间。有关内核模式(也称为“监督”模式)和系统调用机制的简要描述,请参见下文。
User+Sys将告诉您进程实际使用了多少CPU时间。请注意,这是跨所有CPU的,因此如果进程有多个线程(并且此进程在具有多个处理器的计算机上运行),则可能会超过Real报告的时钟时间(通常会发生)。注意,在输出中,这些数字包括所有子进程(及其子进程)的用户和系统时间,以及它们可能被收集的时间,例如,通过wait(2)或waitpid(2),尽管底层系统调用分别返回进程及其子进程的统计信息。
按时间报告的统计数据来源(1)
按时间报告的统计数据是从各种系统调用中收集的“User”和“Sys”来自wait(2)(POSIX)或times(2),具体取决于特定系统。”“Real”是从gettimeofday(2)调用收集的开始和结束时间计算得出的。根据系统的版本,还可以按时间收集各种其他统计信息,例如上下文开关的数量。
在多处理器机器上,多线程进程或分叉子进程的运行时间可能小于总CPU时间,因为不同的线程或进程可能并行运行。此外,报告的时间统计数据来自不同的来源,因此非常短的运行任务所记录的时间可能会有舍入误差,如原始海报所示。
内核与用户模式的简单入门
在Unix或任何受保护的内存操作系统上,“内核”或“管理器”模式是指CPU可以在其中操作的特权模式。某些可能影响安全性或稳定性的特权操作只能在CPU在这种模式下操作时进行;这些操作对应用程序代码不可用。这种动作的一个例子可能是操纵MMU以获得对另一个进程的地址空间的访问。通常,用户模式代码无法做到这一点(有充分的理由),尽管它可以从内核请求共享内存,而这些内存可以由多个进程读取或写入。在这种情况下,共享内存是通过安全机制从内核显式请求的,两个进程都必须显式连接到共享内存才能使用它。
特权模式通常被称为“内核”模式,因为内核是由在该模式下运行的CPU执行的。为了切换到内核模式,您必须发出特定的指令(通常称为陷阱),将CPU切换到以内核模式运行,并从跳转表中的特定位置运行代码。出于安全原因,您不能切换到内核模式并执行任意代码-陷阱是通过一个地址表来管理的,除非CPU以监控模式运行,否则无法写入地址表。你用一个明确的陷阱号码进行陷阱,地址在跳转表中查找;内核具有有限数量的受控入口点。
C库中的“系统”调用(特别是手册页第2节中描述的那些)有一个用户模式组件,这是您从C程序实际调用的组件。在幕后,他们可能会向内核发出一个或多个系统调用,以执行特定的服务(如I/O),但他们仍然有以用户模式运行的代码。如果需要,也可以从任何用户空间代码直接向内核模式发出陷阱,尽管您可能需要编写一段汇编语言来正确设置调用的寄存器。
有关“sys”的详细信息
有些事情是你的代码无法在用户模式下完成的,比如分配内存或访问硬件(HDD、网络等)。这些都在内核的监督下,只有内核才能完成。像malloc或fread/fwrite这样的一些操作将调用这些内核函数,然后将其计算为“sys”时间。不幸的是,这并不像“对malloc的每次调用都将计入'sys'时间”那么简单。对malloc的调用将自己进行一些处理(仍计入“用户”时间),然后在某个地方调用内核中的函数(计入“sys”时间)。从内核调用返回后,“user”中会有更多的时间,然后malloc将返回到代码中。至于何时发生切换,以及在内核模式下花费了多少时间。。。你不能说。这取决于库的实现。此外,其他看似无辜的函数也可能在后台使用malloc等,这在“sys”中也会有一段时间。
•真实:从开始到结束运行过程所花费的实际时间,就像是由人用秒表测量的一样
•用户:计算过程中所有CPU花费的累计时间
•sys:所有CPU在系统相关任务(如内存分配)期间所花费的累积时间。
注意,有时user+sys可能大于real,因为多个处理器可以并行工作。
简单地说,我喜欢这样想:
real是运行命令所需的实际时间(就像用秒表计时一样)user和sys是CPU执行命令所需的“工作量”。这项“工作”以时间单位表示。
一般来说:
user是CPU运行命令代码所做的工作量sys是CPU为了支持运行命令而处理“系统开销”类型任务(如分配内存、文件I/O等)所需的工作量
由于这最后两次是计算已完成的“工作”,因此它们不包括线程可能花费的等待时间(例如等待另一个进程或磁盘I/O完成)。
然而,real是实际运行时间的度量,而不是“工作”,因此它确实包括了等待所花费的时间。
为了进一步解释公认的答案,我只想提供另一个原因,为什么real≠user+sys。
请记住,real表示实际运行时间,而user和sys值表示CPU执行时间。因此,在多核系统上,用户和/或系统时间(以及它们的总和)实际上可能超过实时。例如,在我为类运行的Java应用程序上,我得到了一组值:
real 1m47.363s
user 2m41.318s
sys 0m4.013s
最少可运行的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并行算法已经实现了吗?