参考书籍《深入理解计算机系统》
文章目录
计时选择
- 大于1.0s:采用间隔计数就足够了,并且这种计数多处理器的负载不敏感。
- 0.01~1.s:在负载很轻的系统上,采用gettimeofday库函数测量。
- 小于0.01s:采用基于周期的计时。可以采用gettimofday或者直接访问机器的周期计数器。
1 计算机记时机制
- 计算机采用两种基本机制来记录时间:低频率计时器(timer)和计数器(counter)。低频率计时器采用周期性中断处理器计时,计数器则是每个时钟周期(约1ns)加1。
基于这两种机制,我们可以获得非常短(小于10ms)或者非常长(大于1s)的时间段的准确测量值,但是10ms~1s之间的准确测量则需要特殊处理。 - 宏观上处理器响应外部事件大多是以ms(毫秒)计时,比如计算机图形显示器约33ms刷新一次,键盘敲击速率最快也要50ms,磁盘启动一次磁盘传送约10ms,处理器一次分配给每个任务的时间大约是5~20ms。
微观上,指令的执行速度是以时钟周期计时,每个时钟周期能执行一条或多条指令,每个时钟周期约1ns。 - 进程调度和计时器中断:计算机有一个外部计时器,它周期性的向处理器发送中断信号(间隔时间),当计时器中断发生,操作系统调度程序可以选择继续执行当前进程或者切换到其他进程。这个间隔时间必须设置得足够短使得处理器在不同任务间切换得足够频繁,看上去就像是在同时执行多个任务,但是进程切换需要几千个时钟周期进行上下文切换,故间隔时间太短又会导致性能很差,典型得时间间隔范围是1~10ms。
2 通过间隔计数来测量时间
2.1 操作系统维护的时间量
- 操作系统用计时器(timer)记录每个进程使用得累计时间,这个信息不是很准确。
- 操作系统维护每个进程使用得用户时间量和系统时间量得计数值,当计时器(timer)中断发生时,操作系统会确定哪个进程是活动得,并对活动进程得计数值增加计时器间隔时间;如果操作系统在内核模式中执行,则增加系统时间,反之增加用户时间。
2.2 读取进程的计时器的方法
- 可以在执行程序之前加上
time
来测量运行的总时间,如下:
$ time cp -r ../conf ./
real 0m0.015s
user 0m0.000s
sys 0m0.010s
- 在程序中可以调用
sys/times.h
的times
接口来读取进程的计时器。
头文件:#include <sys/times.h>
原型:clock_t times(struct tms *buf);
tms结构定义:
struct tms {
clock_t tms_utime; /* user time */
clock_t tms_stime; /* system time */
clock_t tms_cutime; /* user time of children */
clock_t tms_cstime; /* system time of children */
};
3 周期计数器(高精度测量)
- 概述:为了给计时测量提供更高的精确度,许多处理器还包含一个运行在时钟周期级的计时器,这个计时器是一个特殊的寄存器,每个时钟周期它会加1。这样的计数器在不同处理器上的实现细节各不相同,故没有统一的、与平台无关的接口读这个计数器的值。
- IA32的读取方法:IA32计数器是用
rdtsc
指令访问的,这条指令没有参数,它将寄存器%edx设置为计数器的高32位,%eax设置位低32位,可以进行如下封装:
void access_counter(unsigned *hi, unsigned *lo)
{
asm("rdtsc;movl %%edx,%0;movl %%eax,%1"
: "=r" (*hi), "=r" (*lo)
:
: "%edx", "%eax");
}
- 计时:周期计数器提供了非常精确的工具,可以测量一个程序中两个不同点之间的时间,进而测量某个过程P的运行时间。
- 缺陷:1.如果两次调用计数器之间,有另外某个进程执行了(即存在上下文切换),测量误差会极大;2.高速缓存和分支预测也会对计时变换有影响,这个影响比上下文切换的影响要小一些。一般采用k次最优测量法消除误差。
4 基于gettimeofday函数的测量(一般测量看这个)
- gettimeofday的功能:查询系统时钟。
- 缺陷:这种计时机制依赖于gettimeofday的实现机制,其测量值并不总是准确,误差可能为正也可能为负。在linux系统上测量值类似于直接使用周期计数器时得到的结果。
- 基于周期的:需要用K次最优计时方法。
- 基于间隔的:需要找到一些使用机器的周期计数器的方法,可能会需要汇编语言编码。