时间都去哪儿了

看标题有点唬人,其实我写的还是技术文章。算法中会评估时间复杂度,而实际编写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也是一个长整型类型,函数中如果timerNULL或遇到错误返回结果-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() 方法精度只有秒级,所以适合较大的计算过程。

参考

感谢向日葵花: Linux/Unix 环境下实现精确计算程序运行的时间