首页 > *nix技术, 应用程序 > Linux下应用程序内测量时间的各种方法

Linux下应用程序内测量时间的各种方法

2012年3月17日 发表评论 阅读评论 7,685 次浏览

在linux下统计时间有很多种方法,在内核代码里,最简单方便的方法自然是利用全局变量jiffies,这个字段记录了自系统启动以来产生的节拍总数,而系统每1秒钟将产生HZ次节拍,HZ是个常数,在内核2.6版本,编译时可选择,100/250/300/1000,可以查看内核编译选项:

[root@localhost ~]# cat /usr/src/linux-`uname -r`/.config | grep CONFIG_HZ
# CONFIG_HZ_100 is not set
# CONFIG_HZ_250 is not set
# CONFIG_HZ_300 is not set
CONFIG_HZ_1000=y
CONFIG_HZ=1000
[root@localhost ~]# 

上面的介绍不是本文的重点,本文主要描述在应用程序内统计时间的方法,伟大的glibc和linux为我们提供了至少5种方法:

#include <time.h>
time_t time(time_t *t);

time()函数返回自1970年1月1号0时0分0秒到当前所经过的时间,最小时间精度为秒,粗粒度的时间测量可以考虑使用这个函数,因为它够简单。

#include <time.h>
clock_t clock(void);

clock()函数对时间的测量精度可达微妙,但是它返回的值仅表示CPU实际‘工作’消耗的时间,而一个进程消耗的时间大体可分为三类:CPU时间、I/O时间、等待时间,所以在单cpu机器上,通过clock函数测量的值会比实际过去的墙上时钟时间(wall clock time,http://en.wikipedia.org/wiki/Wall_clock_time)要小,但在多cpu机器上,由于可以多个cpu并行工作,即clock时间可能会成倍计算,所以clock又可能反而比消耗的墙上时钟时间要大。看一个实例:

[root@localhost ~]# cd /home/lenky/time/
[root@localhost time]# cat clock_demo.c 
/**
 * FileName: clock_demo.c
 * sh# gcc clock_demo.c -o clock_demo
 */

#include <stdint.h> 
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

void clock_calc_time() 
{
	clock_t c1 = clock();
	sleep(2);
	clock_t c2 = clock();
	printf("clock() : %.2f s\n", (double) (c2 - c1) / (double) CLOCKS_PER_SEC);
}
 
void gettimeofday_calc_time() 
{
	struct timeval t1, t2;
	gettimeofday(&t1, NULL);
	sleep(2);
	gettimeofday(&t2, NULL);

	printf("gettimeofday() : %.2f s\n", (double) (t2.tv_sec - t1.tv_sec) + 
                                        (t2.tv_usec - t1.tv_usec) / 1000000.0);
}

int main(int argc, char *argv[])
{
	clock_calc_time();
	gettimeofday_calc_time();
 
	return (0);
}

[root@localhost time]# gcc clock_demo.c -o clock_demo
[root@localhost time]# ./clock_demo 
clock() : 0.00 s
gettimeofday() : 2.00 s
[root@localhost time]# 

clock()函数测量时间结果为0,为何?因为这2秒内,cpu什么也没做,它一直在睡眠,所以cpu工作时间为0,与此对比的gettimeofday()函数测量时间的结果为2秒。那么什么时候该使用clock()函数,也许你已经知道了。

#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);
int settimeofday(const struct timeval *tv , const struct timezone *tz);

gettimeofday()函数的时间测量精度同样为微妙,而且它测量的是墙上时钟时间,也就说如果前后两次gettimeofday()函数调用的差值为2,那么我们房间里那挂在墙上的钟表(貌似现在很少看到这种钟表了,想起重庆大学有个钟塔广场,那个时钟也是挂着的,也能称之为墙上时钟吧)的秒钟也顺时钟方向移动了两下(人生就这样又少了两秒,囧)。微妙级粒度的时间测量可以考虑使用这个函数。

tsc(http://en.wikipedia.org/wiki/Time_Stamp_Counter)寄存器是从奔腾就开始存在的一个时钟64位计时器,它记录从机器启动开始那一刻起到当前的周期数(clock cycles),而一个周期是多少时间呢?这和具体机器的cpu频率有关,如果cpu频率为2G,那么一个周期的时间就是(1/2G)秒 = 0.5纳秒,也就是在这种机器上,rdtsc指令测量时间的精度可达0.5纳秒,但由于rdtsc指令依赖cpu频率,所以如果在时间测量的过程中,cpu频率发生了变化,那么结果就不准确了。另外,同gettimeofday()一样,rdtsc测量的是墙上时钟时间。cpu的频率是可能发生变化的,比如当cpu空闲了很长一段时间,为了节省功耗,cpu频率可能会自动降低,另外对于多核的情况,各个核心的tsc可能会发生漂移(即先读cpu0的tsc获得时间time1,接着读cpu1的tsc获得时间time2,可能会出现time2比time1还早的情况),在文章http://lwn.net/Articles/209101/有这些内容的介绍,不过总之可以看到rdtsc性能相当的高(获取‘当前时间’只需读一个cpu寄存器而已),对于细粒度的时间测量,如果能够避免上面说的两种的情况,比如我的程序会一直忙(也就是说cpu别想空闲太久,这种情况下才有必要追求更高精度的时间控制吧?)、程序对tsc的操作都是per-cpu(也就是各个cpu的tsc的比较都只针对自身)的,那么还是可以用rdtsc指令的,毕竟没有比rdtsc更细粒度的时间测量了(对于机器而言,clock cycle就是cpu工作的基本时间单元)。

long sys_clock_gettime (clockid_t which_clock, struct timespec *tp);

rdtsc指令太麻烦,所以POSIX兼容系统上提供了一个clock_gettime()函数来纳秒级精确计算时间,而事实上clock_gettime()函数的内部也是通过rdtsc指令来实现纳秒级精确计算的,如果不支持rdtsc指令,则通过调用gettimeofday()来计算时间,此时时间精度只有微妙级。clock_gettime()函数依赖rt库,所以编译时要链接上该库。

完整的代码实例:

[root@localhost time]# cat time_demo.c 
/**
 * FileName: time_demo.c
 * sh# gcc time_demo.c -o time_demo -lrt
 */

#include <stdint.h> 
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

uint64_t max_count;
uint64_t local_cpu_hz;

uint64_t consume_time() 
{
	uint64_t i = 0;
	uint64_t c = 0xABCDEFFEDCBAAF;
	for (; i < max_count * 1000000; i ++)
		c |= i;
	for (; i < max_count * 1500000; i ++)
		c |= i;
	for (; i < max_count * 3000000; i ++)
		c |= i;
	return i;
}

uint64_t get_cpu_hz(uint64_t *local_cpu_hz)
{
	FILE *fp;
	char cpuinfo[128];
	memset(cpuinfo, 0, sizeof(cpuinfo));
	
	fp = popen("cat /proc/cpuinfo|grep \"cpu MHz\"|sed -e 's/.*:[^0-9]//'","r");
	if (fp == NULL) {
		printf("Open /proc/cpuinfo failed.\n");
		return -1;
	}
	if (fgets(cpuinfo, 128, fp) == NULL) {
		printf("Read cpu frequency failed.\n");
		fclose(fp);
		return -1;
	}
	*local_cpu_hz = (uint64_t)(atof(cpuinfo) * 1000000);
	pclose(fp);
	return 0;
}

 
uint64_t get_rdtsc() 
{
	uint64_t a, d;
 
	__asm__ volatile("rdtsc" : "=a" (a), "=d" (d));
 
	return a | (d << 32);
}
 
void time_calc_time() 
{
	time_t t1, t2;
 
	time(&t1);
	consume_time();
	time(&t2);
 
	printf("time() : %.2f s\n", (double)(t2 - t1));
}
 
void clock_calc_time() 
{
	clock_t c1 = clock();
	consume_time();
	clock_t c2 = clock();
	printf("clock() : %.2f s\n", (double) (c2 - c1) / (double) CLOCKS_PER_SEC);
}
 
void gettimeofday_calc_time() 
{
	struct timeval t1, t2;
	gettimeofday(&t1, NULL);
	consume_time();
	gettimeofday(&t2, NULL);

	printf("gettimeofday() : %.2f s\n", (double) (t2.tv_sec - t1.tv_sec) + 
                                        (t2.tv_usec - t1.tv_usec) / 1000000.0);
}
 
void rdtsc_calc_time() 
{
	uint64_t t1, t2;
 	t1 = get_rdtsc();
	consume_time();
	t2 = get_rdtsc();
 
	printf("rdtsc() : %.2f s\n", (double) (t2 - t1) / local_cpu_hz);
}
 
void clock_gettime_calc_time() {
	struct timespec t1, t2;
	clock_gettime(CLOCK_REALTIME, &t1);
	consume_time();
	clock_gettime(CLOCK_REALTIME, &t2);
 
	printf("clock_gettime() : %.2f s\n", (double) (t2.tv_sec - t1.tv_sec) + 
							(double) (t2.tv_nsec - t1.tv_nsec) / 1000000000.0);
}
 
int main(int argc, char *argv[])
{
	if (argc > 1)
		sscanf(argv[1], "%li", &max_count);

	if (max_count <= 0)
		max_count = 100;
	
	if (get_cpu_hz(&local_cpu_hz) == -1)
		return (-1);
 
	time_calc_time();
	clock_calc_time();
	gettimeofday_calc_time();
 	rdtsc_calc_time();
	clock_gettime_calc_time();
 
	return (0);
}

[root@localhost time]# gcc time_demo.c -o time_demo -lrt
[root@localhost time]# ./time_demo 
time() : 1.00 s
clock() : 0.92 s
gettimeofday() : 0.93 s
rdtsc() : 0.93 s
clock_gettime() : 0.93 s
[root@localhost time]# ./time_demo 564
time() : 5.00 s
clock() : 5.22 s
gettimeofday() : 5.23 s
rdtsc() : 5.24 s
clock_gettime() : 5.23 s
[root@localhost time]# 

转载请保留地址:http://www.lenky.info/archives/2012/03/1286http://lenky.info/?p=1286


备注:如无特殊说明,文章内容均出自Lenky个人的真实理解而并非存心妄自揣测来故意愚人耳目。由于个人水平有限,虽力求内容正确无误,但仍然难免出错,请勿见怪,如果可以则请留言告之,并欢迎来讨论。另外值得说明的是,Lenky的部分文章以及部分内容参考借鉴了网络上各位网友的热心分享,特别是一些带有完全参考的文章,其后附带的链接内容也许更直接、更丰富,而我只是做了一下归纳&转述,在此也一并表示感谢。关于本站的所有技术文章,欢迎转载,但请遵从CC创作共享协议,而一些私人性质较强的心情随笔,建议不要转载。

法律:根据最新颁布的《信息网络传播权保护条例》,如果您认为本文章的任何内容侵犯了您的权利,请以Email或书面等方式告知,本站将及时删除相关内容或链接。

  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.