$ time foo
real 0m0.003s
user 0m0.000s
sys 0m0.004s
$
时间输出中的“真实”,“用户”和“ sys”是什么意思?
对我的应用进行基准测试时,哪一个有意义?
time
,请执行至少一秒钟的操作。
time
是bash关键字。所以打字man time
是不是给你一个手册页的庆典time
,而是它给的手册页/usr/bin/time
。这使我绊倒了。
$ time foo
real 0m0.003s
user 0m0.000s
sys 0m0.004s
$
时间输出中的“真实”,“用户”和“ sys”是什么意思?
对我的应用进行基准测试时,哪一个有意义?
time
,请执行至少一秒钟的操作。
time
是bash关键字。所以打字man time
是不是给你一个手册页的庆典time
,而是它给的手册页/usr/bin/time
。这使我绊倒了。
Answers:
实时,用户和系统处理时间统计
这些事情之一与另一件事不一样。实际是指实际经过的时间;用户和系统指仅由进程使用的CPU时间。
实际时间是挂钟时间-从通话开始到结束的时间。这是所有经过的时间,包括其他进程使用的时间片以及该进程花费的时间被阻塞(例如,如果它正在等待I / O完成)。
用户是在用户模式代码花费(内核之外)CPU时间量内的过程。这只是执行过程中使用的实际CPU时间。该进程花费的其他进程和时间不计入该数字。
Sys是进程中内核花费的CPU时间量。这意味着与内核代码仍在用户空间中运行的库代码相比,执行内核中的系统调用所花费的CPU时间。像“用户”一样,这只是进程使用的CPU时间。有关内核模式(也称为“管理程序”模式)和系统调用机制的简要说明,请参见下文。
User+Sys
会告诉您您的进程使用了多少实际CPU时间。请注意,这是所有CPU上的情况,因此,如果进程具有多个线程(并且此进程在具有多个处理器的计算机上运行),则可能会超过报告的挂钟时间Real
(通常发生)。请注意,在输出中,这些数字包括所有子进程(及其子代)的收集时间User
和Sys
时间(例如,通过wait(2)
或收集的时间)waitpid(2)
,尽管底层系统调用分别返回该进程及其子进程的统计信息。
统计员报告的起源 time (1)
报告的统计信息time
是从各种系统调用中收集的。“用户”和“系统”来自wait (2)
(POSIX)或times (2)
(POSIX),具体取决于特定的系统。“真实”是根据gettimeofday (2)
通话收集的开始时间和结束时间计算得出的。根据系统的版本,还可以通过收集其他各种统计信息,例如上下文切换的数量time
。
在多处理器机器上,由于不同的线程或进程可以并行运行,因此多线程进程或派生子进程的时间可能比CPU总时间短。而且,报告的时间统计信息来自不同的来源,因此,对于非常短的运行任务记录的时间可能会舍入误差,如原始海报所示的示例所示。
内核与用户模式简介
在Unix或任何受保护的内存操作系统上,“内核”或“超级用户”模式是指CPU可以在其中运行的特权模式。某些可能影响安全性或稳定性的特权操作只能在CPU在以下情况下运行:此模式;这些操作不适用于应用程序代码。此类操作的一个示例可能是对MMU的操纵,以获得对另一个进程的地址空间的访问。通常,用户模式代码不能这样做(有充分的理由),尽管它可以从内核请求共享内存,这可以可以通过多个过程进行读写。在这种情况下,共享内存是通过安全机制从内核显式请求的,并且两个进程都必须显式附加到该共享内存才能使用它。
特权模式通常称为“内核”模式,因为内核是由以该模式运行的CPU执行的。为了切换到内核模式,您必须发出一条特定的指令(通常称为trap),该指令将CPU切换为以内核模式运行,并从跳转表中的特定位置运行代码。 出于安全原因,您不能切换到内核模式并执行任意代码-陷阱通过地址表管理,除非CPU在超级用户模式下运行,否则该地址表无法写入。使用一个明确的陷阱号进行陷阱,并在跳转表中查找该地址;内核具有有限数量的受控入口点。
C库中的“系统”调用(尤其是手册页的第2节中描述的调用)具有用户模式组件,这是您实际上从C程序中调用的组件。在后台,他们可能会向内核发出一个或多个系统调用以执行特定服务,例如I / O,但它们仍然具有在用户模式下运行的代码。如果需要,也很有可能直接从任何用户空间代码向内核模式发出陷阱,尽管您可能需要编写一段汇编语言来为调用正确设置寄存器。
有关“ sys”的更多信息
有些代码是用户模式无法完成的,例如分配内存或访问硬件(HDD,网络等)。这些都是在内核的监督下完成的,仅靠内核即可做到。诸如malloc
或fread
/之类的某些操作fwrite
将调用这些内核函数,然后将其计为“ sys”时间。不幸的是,这并不像“每次对malloc的调用都以'sys'时间计入”那样简单。调用to malloc
将自己进行一些处理(仍以“用户”时间计),然后沿其调用内核中的函数的某处(以“ sys”时间计)。从内核调用返回后,“用户”中将有更多时间,然后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
总计?诸如线程上下文切换之类的OS开销可能是?
• 真实的:从开始到结束运行过程所花费的实际时间,就好像是人类用秒表测量的一样
• 用户:计算期间所有CPU花费的累积时间
• sys:所有CPU在与系统有关的任务(例如内存分配)期间花费的累积时间。
请注意,有时用户+ sys可能大于实际值,因为多个处理器可能并行工作。
sys
在系统调用(和页面错误处理程序)中花费的CPU时间是多少?
real
通常被称为“挂钟”时间。
最少的可运行POSIX C示例
为了使事情更具体,我想time
用一些最小的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线程),2个Samsung M471A2K43BB1-CRC RAM(2个16GiB)中进行了测试。
睡觉
非繁忙睡眠不计任何user
或sys
只real
。
例如,一个睡眠一秒钟的程序:
#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
对于在IO上可用而被阻止的程序也是如此。
例如,以下程序等待用户输入字符并按Enter:
#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
因此,time
可以帮助您区分CPU和IO绑定程序:“ CPU绑定”和“ I / O绑定”是什么意思?
多线程
下面的示例niters
对nthreads
线程进行无用的,纯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;
}
然后,在我的8个超线程CPU上,针对固定的10 ^ 10迭代,将wall,user和sys绘制为线程数的函数:
绘制数据。
从图中可以看到:
对于CPU密集型单核应用程序,wall和user大致相同
对于2核,用户大约是2倍墙,这意味着用户时间是在所有线程中计算的。
用户基本上翻了一番,而墙保持不变。
这最多可以持续8个线程,这与我计算机中超线程的数量相匹配。
8点之后,墙壁也开始增加,因为我们没有任何额外的CPU在给定的时间内进行更多的工作!
此时的比率平稳。
请注意,此图非常清晰和简单,因为工作纯粹是受CPU限制的:如果它是受内存限制的,那么我们将在内核更少的情况下更早地降低性能,因为内存访问将成为瓶颈,如What所示。术语“ CPU绑定”和“ I / O绑定”是什么意思?
系统工作繁重 sendfile
我想承担的最重的sys工作负载是使用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;
}
基本上可以提供预期的系统时间:
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源代码
当您仅time <cmd>
在Ubuntu上执行操作时,它可以使用Bash关键字,如下所示:
type time
输出:
time is a shell keyword
因此我们在Bash 4.19源代码中为输出字符串使用grep源代码:
git grep '"user\b'
这导致我们执行execute_cmd.c函数time_command
,该函数使用:
gettimeofday()
而getrusage()
如果两者都可用times()
除此以外GNU Coreutils源代码
如果我们称其为:
/usr/bin/time
然后使用GNU Coreutils实现。
这个有点复杂,但是相关的资源似乎在resuse.c上,它确实:
wait3
呼叫(如果有)times
和gettimeofday
否则