时间都去哪儿了
看标题有点唬人,其实我写的还是技术文章。算法中会评估时间复杂度,而实际编写c程序时,也会用到一些方法去评估时间,看一看程序运行“时间都去哪儿了”。
文章编写的c程序均在macOS
中实现验证,应该同样适用于Linux/Unix
。这里将会用到下面三种方法评估程序的运行时间:
- clock()函数
- time()函数
- gettimeofday()函数
clock()
函数
clock()
函数是 ANSI C 的标准库函数,其声明在time.h
文件中,其返回从开启这个程序进程到调用clock()函数之间的CPU 时钟计时单元(clock tick)数,在 MSDN 中称之为挂钟时间(wal-clock),这有点像嵌入式系统中的系统滴答。
函数返回一个clock_t
的类型数据,其实就是一个无符号的长整型,那么要获得时间的话,还得除以 CLOCKS_PER_SEC
的宏定义。顾名思义,CLOCKS_PER_SEC
就是每秒所经过的滴答数。
测试程序
那么编写一下程序 example1.c
尝尝鲜:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
long loopCount = 10000000L;
clock_t begin, end;
double duration;
printf( "Time to do %ld empty loops: ", loopCount) ;
begin = clock();
while( loopCount-- );
end = clock();
duration = (double)(end - begin) / CLOCKS_PER_SEC;
// 精确到ms
printf( "%.3f ms\n", duration * 1000.0 );
return 0;
}
程序验证了10000000个空循环的耗时,执行结果如下:
➜ duration ./example1
Time to do 10000000 empty loops: 18.601 ms
注意
- 函数得到的是程序所占进程在CPU上的消耗的滴答数,所以sleep这类函数的时间不会计入;
- 在 POSIX 兼容系统中,CLOCKS_PER_SEC的值为 1,000,000 的,也就是 1MHz,所以理论上精度应该是us;
网上好多资料说是精度是ms级别的,仔细想一下其实他们说的不对,就像上面我说的
CLOCKS_PER_SEC
是1000000,那么这个clock()
函数其实返回的是该进程占用CPU的us时间,那么他们说打印函数的时间占用为0ms,问题出在哪儿了呢?很简单,打印一下开始和结束的滴答数就知道了,占用滴答数不可能为0的。CLOCKS_PER_SEC
是个整型,计算的时候前后获取的clock()的值也都是整型,所以计算的时候当然会是0,只需要按浮点类型计算就可以了,比如下面example.c
的实现。这里要注意的是,有些系统中CLOCKS_PER_SEC
可能不是1000000,所以程序中计算还应让CLOCKS_PER_SEC
参与。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
double begin, end;
double duration;
begin = clock();
printf("Hello, world!\n");
end = clock();
printf("begin clock: %0.f\n", begin);
printf("end clock: %0.f\n", end);
duration = (end - begin) / CLOCKS_PER_SEC * 1000.0;
printf("print time: %.3f ms", duration);
return 0;
}
结果:
➜ duration ./example
Hello, world!
begin clock: 2056
end clock: 2084
print time: 0.028 ms
time()
函数
time()
函数来获得当前日历时间,是从一个标准时间点(一般是1970年1月1日0时0分0秒)到现在的时间,单位为秒。函数声明:
time_t time(time_t * timer);
time_t
也是一个长整型类型,函数中如果timer
为NULL
或遇到错误返回结果-1,如果不为NULL
,则会把值保存在timer
中。
试一下
编写程序example2.c
使用延时函数sleep()
进行测试:
// time()方法精度只有1s
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char *argv[]) {
time_t t1, t2;
time(&t1);
sleep(3);
time(&t2);
printf("t1: %ld, t2: %ld, sleep: %ld s", t1, t2, t2 - t1);
return 0;
}
输出结果如下:
➜ duration ./example2
t1: 1509401991, t2: 1509401994, sleep: 3 s
这里要注意的是编译的时候gcc
默认使用标准c99
,而这个标准下sleep()
无效,这里采用c89
标准进行编译即可:
gcc -std=c89 -o example2 example2.c
注意
此种方法的精度只有1s!
gettimeofday()
函数
gettimeofday()
函数在sys/time.h
中,声明如下:
int gettimeofday(struct timeval*tv, struct timezone *tz );
其中形参对应结构体如下:
struct timeval {
long tv_sec;
long tv_usec;
};
struct timezone{
int tz_minuteswest;
int tz_dsttime;
};
我们这里只关心时间差,所以只需要传入定义好的timeval
结构体就好,timezone
可以为NULL
,每次调用函数,会把时间戳记录在传入的timeval
结构体变量指针中,其中:
tv_sec
存储的是s;tv_usec
存储是us;
实例测试
那么可以编写程序example3.c
进行测试了:
#include <stdio.h>
#include <sys/time.h>
int main() {
struct timeval start;
struct timeval end;
unsigned long duration;
gettimeofday(&start, NULL);
printf("Hello,World!\n");
gettimeofday(&end, NULL);
duration = 1000000 * (end.tv_sec - start.tv_sec)+ end.tv_usec-start.tv_usec;
printf("%ld us:", duration);
return 0;
}
最终结果为:
➜ duration ./example3
Hello,World!
35 us:%
注意
精度为us;
总结
综上所述,clock()
和 gettimeofday()
方法均可以达到us的精度,但是 clock()
方法依赖于宏定义 CLOCKS_PER_SEC
,不过最终一般是us级别;而 time()
方法精度只有秒级,所以适合较大的计算过程。