多线程的代码编写好了,如何统计运行时间以证明多线程的优势呢?
参考:http://www.cnblogs.com/clover-toeic/p/3845210.html(clover_toeic 《Linux用户态程序计时方式详解》)
先给出一些关于时间的概念:
进程时间也称CPU时间,用以度量进程使用的中央处理器资源。进程时间以时钟滴嗒计算,通常使用三个进程时间值,即实际时间(real)、用户CPU时间(user)和系统CPU时间(sys),这一点与 time 指令一致。
1)real是从进程开始执行到完成所经历的挂钟(wall clock)时间,包括其他进程使用的时间片(time slice)和本进程耗费在阻塞(如等待I/O操作完成)上的时间。该时间对应秒表(stopwatch)直接测量。
2)user是进程执行用户态代码(内核外)耗费的CPU时间,仅统计该进程执行时实际使用的CPU时间,而不计入其他进程使用的时间片和本进程阻塞的时间。
3)sys是该进程在内核态运行所耗费的CPU时间,即内核执行系统调用所使用的CPU时间。
CPU总时间(user + sys)是CPU执行用户进程操作和内核(代表用户进程执行)系统调用所耗时间的总和,即该进程(包括其线程和子进程)所使用的实际CPU时间。若程序循环遍历数组,则增加用户CPU时间;若程序执行exec或fork等系统调用,则增加系统CPU时间。
简单来说:
依据进程的三种状态为阻塞、就绪、运行,可以得到下面的关系式:
1)挂钟时间 = 阻塞时间 + 就绪时间 +运行时间
2)用户CPU时间 + 系统CPU时间 = 运行时间
3)用户CPU时间 = 运行状态下的用户空间时间
4)系统CPU时间 = 运行状态下的系统空间时间
在多核处理器机器上,若进程含有多个线程或通过fork调用创建子进程,则实际时间可能小于CPU总时间——因为不同线程或进程可并行执行,但其时间会计入主进程的CPU总时间。若程序在某段时间处于等待状态而并未执行,则实际时间可能大于CPU总时间。其数值关系总结如下:
1)Real < CPU,表明进程为计算密集型(CPU bound),利用多核处理器的并行执行优势;
2)Real ≈ CPU,表明进程为计算密集型(CPU bound),未并行执行;
3)Real > CPU,表明进程为I/O密集型(I/O bound),多核并行执行优势并不明显。
之前一直使用 clock() 函数统计时间,后来发现该函数在多线程下无法使用,google 后知道了另一个函数 clock_gettime(),现对两种方法在进一步研究如下:
1、clock() 函数
clock() 是ANSI C标准库函数,其函数原型声明在time.h头文件中。
clock_t clock(void);
该函数返回自程序进程开始运行起,到程序中调用 clock() 函数时的处理器时钟计时单元数(俗称clock tick,即硬件时钟滴答次数)。若无法得到处理器时间,则返回-1。时钟计时单元的长短由CPU控制,但clock tick并非CPU时钟周期,而是一个C/C++基本计时单位。返回类型 clock_t 通常定义为有符号长整型(long)。
使用clock函数时应注意以下几点:
1) 该函数返回处理器耗费在某程序上的时间(CPU时间片数量)。若程序中存在sleep函数,则sleep所消耗的时间将不计算在内,因为此时CPU资源被释放。
2) 返回值若以秒计需除以CLOCKS_PER_SEC宏,该宏表示一秒钟有多少个时钟计时单元(硬件滴答数),取值因系统而异。在POSIX兼容系统中,CLOCKS_PER_SEC值为1,000,000,即1MHz(此时返回值单位为微秒)。通过(231-1)/1000000/60≈35.8可估算出clock函数超过半小时后将会溢出。
3) 该函数仅能返回毫秒级的计时精度(大致与操作系统的线程切换时间相当),低于精度的程序计为0毫秒。因此,该函数适用于测量一些耗时较长(大于10ms)的大型程序或循环程序。
4) 当程序单线程或单核心机器运行时,该函数计时准确;但多线程环境下并发执行时不可使用,因为结束时间与起始时间之差是多个核心总共执行的时钟滴答数,会造成计时偏大。
5) 该函数未考虑CPU被子进程使用的情况,也不能区分用户模式和内核模式。该函数计量进程占用的CPU时间,大约是用户时间和系统时间的总和。
测量某程序执行时间时,可在待计时程序段起始和结束处分别调用 clock() 函数,用后一次的返回值减去前一次的返回值得到运行该程序所消耗的处理器时钟计时单元数,再除以 CLOCKS_PER_SEC 转换为秒。
2、clock_gettime() 函数
clock_gettime() 是 POSIX1003.1 实时函数,其函数原型声明在time.h头文件中。
int clock_gettime(clockid_t clk_id, struct timespec *tp);
该函数获取关于指定时钟的当前 timespec 值,并将其存入指针 tp 所指结构体。其结构体定义为:
struct timespec{ time_t tv_sec; //tv_sec represents seconds since the Epoch long tv_nsec; //自上一秒开始经过的纳秒数(nanoseconds) }可见,该函数计时精度达到纳秒级。若函数执行成功,则返回0;否则返回一个错误码。
clockid_t值用于指定计时器的类型:
1)CLOCK_REALTIME:系统范围内的实时时钟,反映挂钟时间(wall clock time),即绝对时间。若系统时钟源被改变或系统时间被重置,该时钟会相应地调整。若指定该时钟类型,clock_gettime() 函数等效于gettimeofday() 函数,尽管精度有所不同;
2)CLOCK_MONOTONIC:单调时间,不可设置。该时间通过jiffies值计算,其值为当前时间减去起始时间之差,即从系统启动至今所经过的时间。单调时间在运行期间会一直稳定增加,而不受系统时钟的影响。若指定该时钟类型,则tv_sec值与“cat /proc/uptime”第一个输出值(秒)相同;
3)CLOCK_PROCESS_CPUTIME_ID:每个进程的CPU高精度硬件计时器;
4)CLOCK_THREAD_CPUTIME_ID:每个线程的CPU高精度硬件计时器。
因为CLOCK_MONOTONIC计时器更加稳定,故推荐以此获得系统的运行时间。我当前就是用此计时器统计多线程下的时间。
使用方法如下:
struct timespec start, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &start); /* ... */ clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - start.tv_sec); elapsed += (finish.tv_nsec - start.tv_nsec) / 1000000000.0;