Linux用户态程序计时方式详解(1)(4)
2.7 getrusage函数
getrusage函数来自BSD系统,其函数原型声明在sys/resource.h头文件中:
int getrusage(int who, struct rusage *usage); |
该函数获取当前进程或其所有已终止的子进程的资源使用信息,并将其存入指针usage所指结构体。该结构体定义为:
1 struct rusage{ 2 struct timeval ru_utime; //time spent executing in user mode 3 struct timeval ru_stime; //time spent in the system executing on behalf of the process 4 long ru_maxrss; //maximum resident set size utilized(in kilobytes) 5 long ru_ixrss; //integral value indicating the amount of memory used by the text segment shared among other processes, expressed in units of kilobytes * ticks-of-execution. Ticks refer to a statistics clock that has a frequency of sysconf(_SC_CLOCK_TCK) ticks per second. 6 long ru_idrss; //integral value of the amount of unshared memory residing in the data segment of a process(expressed in units of kilobytes * ticks-of-execution) 7 long ru_isrss; //integral value of the amount of unshared memory residing in the stack segment of a process(expressed in units of kilobytes * ticks-of-execution) 8 long ru_minflt; //number of page faults serviced without any I/O activity; here I/O activity is avoided by ''reclaiming'' a page frame from the list of pages awaiting reallocation 9 long ru_majflt; //number of page faults serviced that required I/O activity 10 long ru_nswap; //number of times a process was ''swapped'' out of main memory 11 long ru_inblock; //number of times the file system had to perform input(account only for real I/O) 12 long ru_oublock; //number of times the file system had to perform output(account only for real I/O) 13 long ru_msgsnd; //number of IPC messages sent 14 long ru_msgrcv; //number of IPC messages received 15 long ru_nsignals; //number of signals delivered 16 long ru_nvcsw; //voluntary context switches 17 long ru_nivcsw; // involuntary context switches 18 };
在rusage结构体中,Linux仅维护ru_utime/ru_stime/ru_minflt/ru_majflt/ru_nswap等字段。其中,用户时间(ru_utime)和系统时间(ru_stime)与times函数tms结构体内容相似,但由结构体timeval来保存(而不是含义模糊的clock_t)。在Linux中,getrusage使用的时钟频率由正在运行的内核决定。clock_t时间间隔可能是10ms,而getrusage获得的tick时间间隔可能是1ms(Linux 2.6内核tick频率为1000Hz,而用户频率却为100Hz)。因此,getrusage函数的计时精度将比times函数更高。
参数who的取值可为RUSAGE_SELF(获取当前进程的资源使用信息)或RUSAGE_CHILDREN(获取子进程的资源使用信息),根据该值将当前进程或其子进程的信息填入rusage结构。
若函数执行成功,则返回0;否则返回-1,并设置全局变量errno以指示相关错误。
测量某程序执行时间时,可在待计时程序段起始和结束处分别调用getrusage函数,用后一次获取的当前时间减去前一次获取的当前时间得到运行该程序所消耗的秒或微秒数。如:
1 #include <sys/resource.h> 2 void GetRusageTiming(void){ 3 struct rusage tBeginResource, tEndResource; 4 getrusage(RUSAGE_SELF, &tBeginResource); 5 TimingFunc(); 6 getrusage(RUSAGE_SELF, &tEndResource); 7 unsigned dwCostSec = (tEndResource.ru_utime.tv_sec-tBeginResource.ru_utime.tv_sec) + 8 (tEndResource.ru_stime.tv_sec-tBeginResource.ru_stime.tv_sec); 9 unsigned dwCostUsec = (tEndResource.ru_utime.tv_usec-tBeginResource.ru_utime.tv_usec) + 10 (tEndResource.ru_stime.tv_usec-tBeginResource.ru_stime.tv_usec); 11 printf("[getrusage]Cost Time = %dSec, %dUsec\n", dwCostSec, dwCostUsec); 12 }
当应用程序创建进程或使用线程时,计量出的时间会随着应用程序和计时函数的变化而不同。尤其是当应用程序创建一个子进程,而该子进程随后通过wait系统调用被收养时,父进程的运行时间数据将包含其子进程的运行时间。若进程忽略回收子进程,time将无法反映该子进程的运行时间。此时,可通过函数getrusage的参数who来控制想得到的数据。当选用RUSAGE_CHILDREN标志时,回馈的时间只包括收养后的子进程的运行时间。直到父进程调用wait为止,返回的时间将是0。然而,这对进程中的线程不成立。因为线程不是子进程,故线程消耗的时间也认为是进程所耗时间。即使未进行其他系统调用,由getrusage测量出的时间也会因为线程的运行而增大。
2.8 函数批量计时
此处简要描述如何使用C语言方便地测量一批函数的运行时间。
为方便起见,假定待测函数均不带参数且返回类型相同(其他情况稍加封装即可)。为消除计时和输出代码的冗余,使用循环和函数指针依次实现调用同类型的待测函数,代码示例如下:
1 int CalcMul(void) {int a=9999, b=135; return a*b;} 2 int CalcDiv(void) {int a=9999, b=135; return a/b;} 3 int CalcMod(void) {int a=9999, b=135; return a%b;} 4 typedef int (*FTiming)(void); 5 typedef struct{ 6 FTiming fnTimingFunc; 7 char* pszFuncName; 8 }T_FUNC_MAP; 9 #define FUNC_ENTRY(funcName) {funcName, #funcName} 10 T_FUNC_MAP TimingFuncMap[] = { 11 FUNC_ENTRY(CalcMul), 12 FUNC_ENTRY(CalcDiv), 13 FUNC_ENTRY(CalcMod) 14 }; 15 const unsigned FUNC_MAP_NUM = (unsigned)(sizeof(TimingFuncMap)/sizeof(T_FUNC_MAP)); 16 17 void BatchTiming(void){ 18 struct timeval tBeginTime, tEndTime; 19 unsigned iFuncIdx = 0; 20 for(iFuncIdx = 0; iFuncIdx < FUNC_MAP_NUM; iFuncIdx++){ 21 gettimeofday(&tBeginTime, NULL); 22 TimingFuncMap[iFuncIdx].fnTimingFunc(); 23 gettimeofday(&tEndTime, NULL); 24 float fCostTime = 1000000*(tEndTime.tv_sec-tBeginTime.tv_sec) + 25 (tEndTime.tv_usec-tBeginTime.tv_usec); 26 printf("[%s]Cost: %fSec\n", TimingFuncMap[iFuncIdx].pszFuncName, fCostTime/1000000); 27 } 28 }
示例中TimingFuncMap初始化列表仅注册三个函数(CalcMul等)。当待测函数多达数百以上时,可借助工具提取源文件中所有函数名。
批量计时应注意以下几点:
1) 多次运行待测函数取均值可减小统计误差,得出较为精确的运行时间。但要注意待测函数耗时应远大于循环指令执行时间,且需考虑清空高速缓存。
2) 批量计时过程中,若系统时钟被改变,则gettimeofday函数将依据新的时间来计时,导致计时偏差。此时可选用不受系统时间影响的函数(如clock、times等)。
三 总结
对比本文所述的各种计时方式,如下表所示:
计时方式 |
通用性 |
精度 |
计时范围 |
time命令 |
Linux |
10毫秒(ms) |
/ |
clock函数 |
ANSI C |
10毫秒(ms) |
(231-1)/1000000/60≈35分 |
times函数 |
ANSI C |
10毫秒(ms) |
(231-1)/100/3600/24≈243天 |
rdtsc指令 |
I386 |
1纳秒(ns) |
取决于CPU主频(主频为1GHz时约583年) |
time函数 |
ANSI C |
1秒(s) |
(231-1)/3600/24/365≈68年 |
gettimeofday函数 |
ANSI C |
1微秒(μs) |
((231-1)+(231-1)/1000000)/3600/24/365≈68年 |
clock_gettime函数 |
POSIX |
1纳秒(ns) |
约同gettimeofday函数 |
getrusage函数 |
POSIX |
1微秒(μs) |
同gettimeofday函数 |
1秒(second) = 1,000毫秒(millisecond) = 1,000,000微秒(microsecond) = 1,000,000,000纳秒(nanosecond) |
原文链接:http://www.cnblogs.com/clover-toeic/p/3845210.html
评论暂时关闭