android手机cpu耗电量,cpu时间片jiffies怎么转换成时间s?

Android性能测试工具APT使用指南
腾讯的平台高效的性能测试工具APT(Android Performance Testing Tools),适用于开发自测和定位性能瓶颈,帮助测试人员完成性能基准测试、竞品测试。
APT提供了CPU利用率实时曲线图、多维度内存实时曲线图,方便竞品对比测试和定位内存泄露问题;支持进程内存构成分析,支持手工或超过阈值时自动转储(Dump)详细堆内存对象信息,支持多进程,支持生成和导出常用格式的的日志图表。
APT相比同类工具,具有下面2大特性:
使用Java语言开发,基于Eclipse,跨平台,支持windows、Linux和MAC;
支持同时监控多个进程。
那如何高效利用APT工具呢?现在,我们为大家准备了从安装部署到开启APT各重要功能的介绍资料,方便大家参考。
APT源码地址:https://code.csdn.net/Tencent/apt
欢迎大家进行建立分支和提交更改。
一、安装部署
我们只需要将下载的APT_Eclipse_Plugin.jar文件(&前往CSDN CODE下载)放到Eclipse安装目录下的plugins文件夹下,然后重启Eclipse即可。选择&Eclipse工具栏Window - Open Perspective - Other&,选择APT,即可启动APT透视图。
提示:我们需要提前安装ADT插件。如果是升级安装,可能需要在启动Eclipse的快捷方式中添加&-clean&参数,清除插件的缓存信息。
二、CPU与内存检测
1. &设置&视图简介
首先,我们对&设置&视图的各标签页进行简单介绍:
首选页:控制当前的测试内容;
CPU:可设置采样间隔、CPU占有率的采集方法;提供top和dumpsys cpuinfo两种方式;jiffies统计开关,可以精确量化CPU时间片消耗,适用于待机功耗测试;
内存:可设置采集间隔、自动转储(Dump)内存快照开关、自动转储(Dump)内存快照阈值;可设置内存类型曲线显示开关,此项在测试过程中可动态调整。
2. 启动CPU与内存检测
在&设置&视图的&首选项&选项卡中选择监测项:CPU或内存;
点击&进程列表&视图的刷新按钮,获取手机上的正在运行进程列表;
在进程列表中双击或者右键添加要测试的进程(支持多选);
点击&开始&按钮,即可启动监测。
3. 生成统计表格
在CPU、内存视图右边的统计表格中会实时统计CPU、内存和jiffies等三个统计项的最大值、最小值、平均值和增量等统计数据。如下图所示:
三、获取内存快照
获取内存快照的方式有2种,一种是手动方式,一种是自动获取。
1. 手动获取内存快照:在进程列表中右键点击被测应用进程,选择&Dump Hprof&。
2. 自动获取内存快照:在&设置&视图的&内存&选项卡中选择开启Dump Hprof功能。设定内存阈值后,启动内存监测。
当被监控的进程内存超过设定的阈值后,会自动保存当前进程的内存快照hprof文件。内存快照.hprof文件保存在用户根目录\APT\log\hprof&目录下,用Memory Analyzer(MAT)打开分析。
四、进程列表视图
1. 开启进程列表视图
本视图包括的选项有:
开始/暂停按钮:控制测试的开始和暂停。
刷新按钮:更新手机的状态以及手机上的进程列表。
打开log:打开APT生成的log,重新生成曲线图。
打开log存放的文件夹。
2. 被测进程列表
支持手动输入进程名添加到被测进程列表的方式,当测试某个进程的启动过程时会很有用,因为开始的时候该进程并不存在。
双击或者右键删除被测的进程。
被测进程左侧的复选框,用于控制测试过程中要显示的进程曲线。
3. 运行进程列表
展示手机上运行的进程列表,双击或者添加到被测进程列表。
右键的上下文菜单:支持手动转储(DUMP)内存快照、GC、获取PMAP文件等功能。
4. 进程内存构成分析
获取快照:在进程列表中右键点击被测应用进程,选择&PMAP&,内存快照保存在:&用户根目录\APT\log\目录下的&包名_pid_pmap_时间戳.txt&。
查看快照:点击Eclipse工具栏Windows-》ShowView-》other,选择APT视图PMAP, 在PMAP视图中点击打开快照文件。
可以同时选择2次内存快照打开,会计算增量内存的占比,如下图。可以发现两次快照间被测应用释放了21M的匿名内存映射。
五、注意事项
下面是使用APT工具过程中需要主要的几点内容,为您提示下:
APT中的DumpHprof和GC功能由于用到了DDMLIB的功能,所以有两个限制;
确保手机系统或者被测应用是可调试的;
DDMLIB不允许同时有多个工具获取被调试手机上的进程信息,所以如果想用APT的这两个功能,最好把Eclipse的透视图切换到APT,然后重启Eclipse;
获取PMAP文件需要root权限,并且需要默认root。如果进入adb shell默认不是root权限,可以优先执行adb root即可;
最好将adb路径加入到path中;
Eclipse需要安装ADT插件。
APT源码地址:&https://code.csdn.net/Tencent/apt
> 本站内容系网友提交或本网编辑转载,其目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请及时与本网联系,我们将在第一时间删除内容!
[腾讯开源]Android性能测试工具APT使用指南
09:58 CSDN CODE 作者 CSDN CODE
7833 腾讯 apt 安卓 性能测试 开源 我们近日对腾讯的安卓平台高效的性能测试工具APT团队进行了专访.APT,适用于开发自测和定位性能瓶颈,帮助完成性能基准测试.竞品测试.我们为大家准备了从安装部署到开启AP ...
Android性能测试工具(一) 之Emmagee
Emmagee是监控指定被测应用在使用过程中占用机器的CPU.内存.流量资源的性能测试小工具.支持SDK:Android2.2以及以上版本 Emmagee功能介绍1.检测当前时间被测应用占用的CPU使用率以及总体CPU使用量2.检测当前时间被测应用占用的内存量,以及占用的总体内存百分比,剩余内存量3.检测 ...
Android 性能测试工具- Emmagee Emmagee是监控指定被测应用在使用过程中占用机器的CPU.内存.流量资源的性能测试小工具. 支持SDK:Android2.2以及以上版本 Emmagee功能介绍 1.检测当前时间被测应用占用的CPU使用率以及总体CPU使用量 2.检测当前时间被测应用占用的内存量,以及占用的总体内存百分比,剩余内
Iperf是一个网络性能测试工具.可以测试TCP和UDP带宽质量,可以测量最大TCP带宽,具有多种参数和UDP特性,可以报告带宽,延迟抖动和数据包丢失.Iperf在linux和windows平台均有二进制版本供自由使用. Iperf was developed by NLANRDAST as a modern alternative for measurin ...
1.APT工具简介:
APT是一个eclipse插件,可以实时监控Android手机上多个应用的CPU.内存数据曲线,并保存数据:另外还支持自动获取内存快照.PMAP文件分析等,方便开发人员自测或者测试人员完成性能测试,快速发现产品问题.项目地址:https://code.csdn.net/Tencent/apt
2.APT的功能 支持多进程 ...
1.APT工具简介: APT是一个eclipse插件,可以实时监控Android手机上多个应用的CPU.内存数据曲线,并保存数据:另外还支持自动获取内存快照.PMAP文件分析等,方便开发人员自测或者测试人员完成性能测试,快速发现产品问题.项目地址:https://code.csdn.net/Tencent/apt 2.APT的功能 支持多进程的CPU测试,并 ...
Emmagee介绍 Emmagee是监控指定被测应用在使用过程中占用机器的CPU.内存.流量资源的性能测试小工具.该工具的优势在于如同windows系统性能监视器类似,它提供的是数据采集的功能,而行为则基于用户真实的应用操作开源地址:/NetEase/Emmagee/releases支持SDK:Android2.2以及以上 ...
补记: MAT(memory analyzer tool )是google 推荐的进行内存使用量分析的工具. 功能全面而强大!!!首先看一下dumpsys有哪些功能:dumpsys 用来给出手机中所有应用程序的信息,并且也会给出现在手机的状态.dumpsys [Option]
meminfo 显示内存信息android耗电,cpu时间片jiffies怎么转换成时间s?
一般来说 不需要转化。如果非要一个jiffes乘以20毫秒。但是这样更不准确了。
已有帐号?
无法登录?
社交帐号登录& & &良好的计时器可帮助程序开发人员确定程序的性能瓶颈,或对不同算法进行性能比较。但要精确测量程序的运行时间并不容易,因为进程切换、中断、共享的多用户、网络流量、高速缓存访问及转移预测等因素都会对程序计时产生影响。
& & &本文将不考虑这些影响因素(相关资料可参考《深入理解计算机系统》一书),而仅仅关注Linux系统中用户态程序执行时间的计算方式。除本文所述计时方式外,还可借助外部工具统计耗时,如《》一文中介绍的strace。
& & &本文示例代码的运行环境如下:
一 &基本概念
1.1 日历时间
& & &Coordinated Universal Time(UTC):世界协调时间(又称世界标准时间),旧称格林威治标准时间(Greenwich Mean Time, GMT)。
& & &Calendar Time:日历时间,即从一个标准时间点到此时的时间所经过的秒数。该标准时间点因编译器而异,但对编译系统而言标准时间点不变。该编译系统中的时间对应的日历时间都通过该标准时间点衡量,故日历时间是“相对时间”。UNIX/Linux的时间系统由“新纪元时间(Epoch)”开始算起,该起点指定为日凌晨0时0分0秒(格林威治时间)。Microsoft
C/C++ 7.0中标准时间点指定为日0时0分0秒,而其它版本的Microsoft C/C++和所有不同版本的Visual C++中标准时间点指定为日0时0分0秒。日历时间与时区无关。
& & &Epoch:时间点。时间点在标准C/C++中是一个整数(time_t),它用此刻的时间和标准时间点相差的秒数(即日历时间)来表示。目前大部分UNIX系统采用32位记录时间,正值表示为1970年以后,负值则表示1970年以前。可简单地估算出所能表达的时间范围:1970±((231-1)/)≈[]年。为表示更久远的时间,某些编译器厂商引入64位甚至更长的整型数来保存日历时间。
1.2 进程时间
& & &进程时间也称CPU时间,用以度量进程使用的中央处理器资源。进程时间以时钟滴嗒计算,通常使用三个进程时间值,即实际时间(Real)、用户CPU时间(User)和系统CPU时间(Sys)。
& & &实际时间指实际流逝的时间;用户时间和系统时间指特定进程使用的CPU时间。具体区别如下:
Real是从进程开始执行到完成所经历的挂钟(wall clock)时间,包括其他进程使用的时间片(time slice)和本进程耗费在阻塞(如等待I/O操作完成)上的时间。该时间对应秒表(stopwatch)直接测量。User是进程执行用户态代码(内核外)耗费的CPU时间,仅统计该进程执行时实际使用的CPU时间,而不计入其他进程使用的时间片和本进程阻塞的时间。Sys是该进程在内核态运行所耗费的CPU时间,即内核执行系统调用所使用的CPU时间。
& & &CPU总时间(User+Sys)是CPU执行用户进程操作和内核(代表用户进程执行)系统调用所耗时间的总和,即该进程(包括其线程和子进程)所使用的实际CPU时间。若程序循环遍历数组,则增加用户CPU时间;若程序执行exec或fork等系统调用,则增加系统CPU时间。
& & &在多核处理器机器上,若进程含有多个线程或通过fork调用创建子进程,则实际时间可能小于CPU总时间——因为不同线程或进程可并行执行,但其时间会计入主进程的CPU总时间。若程序在某段时间处于等待状态而并未执行,则实际时间可能大于CPU总时间。其数值关系总结如下:
Real & CPU,表明进程为计算密集型(CPU bound),利用多核处理器的并行执行优势;Real ≈ CPU,表明进程为计算密集型(CPU bound),未并行执行;Real & CPU,表明进程为I/O密集型(I/O bound),多核并行执行优势并不明显。
& & &在单核处理器上,Real时间和CPU时间之差,即Real- (User + Sys)是所有延迟程序执行的因素的总和。可估算程序运行期间的CPU利用率为CpuUsage = (User + Sys)/ Real * 100(%)。
& & &在SMP(对称多处理系统)上,该差值近似为Real* ProcessorNum - (User + Sys)。这些因素包括:
调入程序文本和数据的I/O操作;获取程序实际使用内存的I/O操作;由其它程序消耗的CPU用时;由操作系统消耗的CPU用时。
二 &计时方式
& & &本节将基于下面的函数来讨论和对比各种计时方式:
1 #include &math.h&
2 #define TIME_LOOP_NUM
3 void TimingFunc(void){
unsigned int i = 0;
double y = 0.0;
for(; i & TIME_LOOP_NUM; i++)
y = sin((double)i);
2.1 间隔计数
& & &操作系统用计时器(timer)来记录每个进程使用的累计时间,该时间只是程序执行时间的粗略测量值。
& & &操作系统维护着每个进程使用的用户时间量和系统时间量的计数值。当计时器中断发生时,操作系统会在当前进程列表中寻找活动的进程,并对该进程的计数值增加计时器时间间隔(通常10毫秒)。若该进程在内核模式中运行,则增加系统时间,否则增加用户时间。
& & &这种间隔计数(“记账”)方法原理虽然简单但并不精确。若某进程运行时间很短(与系统计时器间隔相同数量级),且计时中断发生时发现进程正在运行,则不论进程已运行一段时间还是中断前1毫秒才开始运行,都会对计数器增加计时器时间间隔;中断发生时进程已切换的情况与之类似。因此,间隔计数时头尾都有误差。不过,若程序运行时间足够长(至少数秒),间隔计数的不准确性可能相互弥补(高估和低估的测量值平均后误差接近0)。理论上很难分析该误差值,故通常只有程序运行时间达到秒级时,采用间隔计数方法才有意义。此外,该方法的主要优点是其准确性不是非常依赖于系统负载。
& & &Linux系统time命令和times库函数采用间隔计数方法测量命令或程序执行时间。
2.1.1 time命令
& & &time命令可测量命令或脚本执行所耗时间及系统资源使用等信息,统计结果包含以下时间(以秒计):
实际执行时间(real time):从命令行执行到运行结束所消耗的时间;用户CPU时间(user CPU time):命令在用户态中执行所消耗的CPU时间,即程序本身及其调用的库函数所使用的时间;系统CPU时间(system CPU time):命令在内核态中执行所消耗的CPU时间,即由程序直接或间接调用的系统调用执行的时间。
& & &Linux系统中,可使用Shell内置命令time,或GNU一般命令time(/usr/bin/time)来测试程序运行的时间。前者只负责计时,精度可达10毫秒;后者精度略低,但可访问getrusage系统调用的信息,并提供丰富的参数选项,包括指定输出文件等功能。
& & &time命令不能用于测量程序内某个函数或某段代码的执行时间。
2.1.1.1 Shell命令
& & &Shell内置命令time的使用格式为
time &command& [&arguments...&]
& & &命令行执行完成后,会在标准输出中打印执行该命令行的时间统计结果。例如:
& & &可见Real&(User+Sys),说明处理器可能同时在执行其他进程,或本进程被阻塞或睡眠(sleep)。睡眠时间不计入用户时间和系统时间。阻塞可能是因为系统调用的错误使用,也可能是系统中的慢设备引起的。
& & &又如统计在当前目录下查找文件hello.c所消耗的时间:
& & &可见Real远大于(User+Sys),因为find命令遍历各个目录时进行大量磁盘I/O操作,这些操作比较耗时,因此大部分时间find进程都在等待磁盘I/O完成。此外,与文件相关的系统调用也会消耗系统时间。
& & &再次运行find命令时,real时间将显著减小:
& & &这得益于系统文件缓存,磁盘I/O操作次数显著减少。
& & &以下两种方法可将time命令输出的时间信息重定向到文件里,如下所示:
{ time find . -name &hello.c&; } 2&hello.txt
//代码块(花括号内侧空格符不可少)
(time find . -name &hello.c&) 2&hello.txt
//子Shell(多占些资源)
& & &注意上面示例中的花括号和小括号不可缺少,否则Shell会把time关键字后面的命令行作为一个整体进行处理,time命令本身的输出不会被重定向。内置命令time输出到标准错误,文件描述符2表示标准错误stderr。若还要包括find命令执行的结果,则可用:
(time find . -name &hello.c&) 2&hello.txt 2&&1
2.1.1.2 GNU命令
& & &GNU命令time的简单使用格式为
/usr/bin/time [options] &command& [&arguments...&] 或
\time [options] &command& [&arguments...&]
& & &命令执行完成后,输出与Shell内置命令time相似,但更详细。例如:
& & &还可加上-v选项得到时间、内存和I/O等更具体的输出:
& & &以下几种方法可将GNU工具time的输出信息重定向到文件里,如下所示:
1 /usr/bin/time --output=hello.txt find . -name &hello.c&
2 /usr/bin/time find . -name &hello.c& 2& hello.txt
3 \time --output=hello.txt find . -name &hello.c&
4 \time find . -name &hello.c& 2& hello.txt
& & &若还要包括find命令执行的结果,则可用:
\time --output=hello.txt --append find . -name &hello.c& & hello.txt
& & &若要控制输出时间的格式,可使用-f选项进行格式化(格式控制符用法见相关手册):
\time -f &\\t%E real,\\t%U user,\\t%S sys& find . -name &hello.c&
& & &输出结果如下所示:
& & &time命令的输出时间值中,用户时间和系统时间来自wait(2)或times(2)系统调用(依赖特定系统),实际时间由gettimeofday(2)中结束时间和起始时间相减得到。因为时间来源不同,故time命令对运行时间较短的任务计时时,会产生舍入错误(Rounding Errors),导致输出的时间精度仅为毫秒级(10毫秒)。
2.1.2 times函数
& & &times是个GNU标准库函数,函数原型声明在sys/times.h头文件中:
clock_t times(struct tms *buf);
& & &该函数读取进程计时器,返回自系统启动以来(Linux 2.4及以前)或启动前(232/HZ)-300秒以来(Linux 2.6)经过的时钟滴嗒数(即挂钟时间)。Linux系统中,若参数buf为NULL指针,则时间值也通过返回值获取(POSIX未指定该行为,其他Unix系统实现多要求非空指针)。若执行失败,则函数返回(clock_t)-1。返回类型clock_t通常定义为长整型(long int)。tms结构体定义为:
1 struct tms{
clock_t tms_
//user time
clock_t tms_
//system time
clock_t tms_
//user time of reaped children
clock_t tms_
//system time of reaped children
& & &该结构体成员utime/stime含义与time命令输出相同,而cutime(用户CPU时间+子进程用户CPU时间)和cstime给出已经终止且被回收的子进程使用的累计时间。因此,times函数不能用于监视任何正在进行的子进程所使用的时间。此外,times函数返回相对时间,故其差值才有实用意义。
& & &测量某程序执行时间时,可在待计时程序段起始和结束处分别调用times函数,用后一次返回值减去前一次返回值得到运行该程序所消耗的时钟滴嗒数,再除以sysconf(_SC_CLK_TCK)转换为秒。如:
1 #include &sys/times.h&
2 void TimesTiming(void){
clock_t tBeginTime = times(NULL);
TimingFunc();
clock_t tEndTime = times(NULL);
double fCostTime = (double)(tEndTime - tBeginTime)/sysconf(_SC_CLK_TCK);
printf(&[times]Cost Time = %fSec\n&, fCostTime);
& & &注意,库函数times与clock均获取CPU时间片数量,但计时单位不同,即sysconf(_SC_CLK_TCK)的值不一定等于CLOCKS_PER_SEC(通常前者为100,后者为1,000,000)——这可降低溢出的可能性。&
& & &sysconf(_SC_CLK_TCK)单位是次数每秒(或Hz),即每秒时钟滴嗒数。
2.2 周期计数rdtsc
& & &从Intel Pentium开始,很多80x86微处理器都引入一个运行在时钟周期级别的时间戳计数寄存器TSC(Time Stamp Counter)。该寄存器以64位无符号整型数的格式,记录CPU上电以来所经过的时钟周期数,并在每个时钟信号(CLK,即处理器中用于接收外部振荡器的时钟信号输入引线)到来时加一。目前的处理器主频非常高,因此该寄存器可达到纳秒级的计时精度(在1GHz处理器上每个时钟周期为1纳秒)。
& & &关于周期计时的最大长度,可用下列公式简单估算:
自CPU上电以来的秒数 = RDTSC读出的周期数 / CPU主频速率(Hz)
& & &若处理器主频为1GHz,则大约需要583~584年,才会从2的64次方(64位无符号整数所能表达的最大数字+1)绕回到0,所以大可不必考虑溢出问题。
& & &通过机器指令RDTSC(Read Time Stamp Counter)可读取TSC时间戳值,并将其高32位存入EDX寄存器,低32位存入EAX寄存器。现有的C/C++编译器多数不直接支持使用RDTSC指令,需用内嵌汇编的方式访问。以下给出常见的几个RDTSC宏定义和封装函数:
1 #define RDTSC(low, high)
asm volatile(&rdtsc& : &=a& (low), &=d& (high))
2 #define RDTSC_L(low)
asm volatile(&rdtsc& : &=a& (low) : : &edx&)
3 #define RDTSC_LL(val)
asm volatile(&rdtsc& : &=A& (val))
5 /* Set *hi and *lo to the high and low order bits of the cycle counter.
* Implementation requires assembly code to use the rdtsc instruction. */
7 void AccessCounter(unsigned *hi, unsigned *lo){
asm volatile(& movl %%edx,%0; movl %%eax, %1&
: &=r& (*hi), &=r& (*lo)
: /* No input */
: &%edx&, &%eax&);
14 typedef unsigned long long cycle_t;
15 /* Record the current value of the cycle counter. */
16 inline cycle_t CurrentCycle(void){
cycle_t tRdtscR
asm volatile(&rdtsc& : &=A& (tRdtscRes));
return tRdtscR
21 inline cycle_t CurrentCycle2(void){
unsigned hi,
asm volatile (&rdtsc& : &=a&(lo), &=d&(hi));
return ((cycle_t)lo) | (((cycle_t)hi)&&32);
& & &其中,asm/volatile是GCC扩展的__asm__/__volatile__内嵌汇编关键字宏定义,若不考虑兼容性可直接采用不加下划线的格式。
& & &通过TSC寄存器值可计算处理器主频,或测试处理器其他处理单元的运算速度。例如,一个周期计数相当于1/(处理器主频Hz数)秒,若处理器主频为1MHZ,则TSC值会在1秒内增加。在时间间隔1秒的前后分别记录TSC值,然后求差并除以,即可计算出以MHZ为单位的主频。代码如下:
1 #include &unistd.h&
//alarm, pause
2 #include &sys/types.h&
3 #include &signal.h&
//signal, kill
5 cycle_t tStart = 0, tEnd = 0;
6 void TimingHandler(int signo){
tEnd = CurrentCycle();
printf(&CPU Frequency: %lldMHz\n&, (tEnd-tStart)/1000000);
kill(getpid(), SIGINT);
12 void CalcCpuFreq(void){
signal(SIGALRM, TimingHandler);
tStart = CurrentCycle();
& & &考虑到sleep调用基于alarm和pause实现,可将上面的代码改造为更简单的方式:
1 unsigned gCpuFreqInHz = 0; //Record Cpu Frequency for later use
2 void CalcCpuFreq2(void){
cycle_t tStart = CurrentCycle();
sleep(1); //调用sleep时,进程挂起直到1秒睡眠时间到达。这期间经过的周期是被其他进程执行的。
cycle_t tEnd = CurrentCycle();
gCpuFreqInHz = tEnd - tS
printf(&CPU Frequency: %dMHz\n&, gCpuFreqInHz/1000000);
& & &执行输出CPU Frequency: 2696MHz(随每次执行可能稍有变化)。对比/proc文件系统中CPU信息(双核):&
& & &可见两者非常接近。
& & 测量某程序执行时间时,可在待计时程序段起始和结束处分别调用CurrentCycle函数(读取TSC值),用后一次的返回值减去前一次的返回值得到运行该程序所消耗的处理器时钟周期数,再除以处理器主频(Hz)转换为秒。如:
1 void RdtscTiming(void){
cycle_t tStartCyc = CurrentCycle();
TimingFunc();
cycle_t tEndCyc = CurrentCycle();
double fCostTime = (double)(tEndCyc-tStartCyc) /gCpuFreqInHz;
printf(&[rdtsc]Cost Time = %fSec\n&, fCostTime);
& & &周期计数方式的优点是:&
& & &1) 高精度。在目前处理器上可获得纳秒级的计时精度。
& & &2) 成本低。Pentium以上的i386处理器均支持RDTSC指令(其他平台也有类似指令),且访问开销极小。
& & &其缺点是:
& & &1) 周期计数指令因处理器平台和实现机制而异,没有与平台无关的统一访问接口,需借助内嵌汇编。
& & &2) 因精度较高,故数据抖动比较厉害。RDTSC指令每次结果都不一样,经常有几百甚至上千的差距。
& & &此外,周期计数方式只测量经过的时间,不关心哪个进程使用这些周期。机器负载、进程上下文切换、高速缓存命中率以及转移预测等都会影响计数值,导致过高估计程序的真实运行时间。《深入理解计算机系统》一书第9章中,深入讨论了这些因素对计时的影响以及尽可能获取精确计时的方法。
2.3 gettimeofday函数
& & &gettimeofday是个库函数,函数原型声明在sys/time.h头文件中:
int gettimeofday(struct timeval *tv,struct timezone *tz);
& & &该函数查询系统时钟,并将当前时间存入tv所指结构体,当地时区信息存入tz所指结构体。其结构体定义为:
1 struct timeval{
time_t tv_
//当前时间距UNIX时间基准的秒数
suseconds_t tv_ //一秒之内的微秒数,且1000000&tv_usec&=0
5 struct timezone{
int tz_ //和Greenwich时间相差多少分钟
//日光节约时间的状态
& & &tv或tz均可为空,为空时不返回对应的结构体。通常只会获取当前时间,故置时区指针tz为空。
& & &相对于间隔计数的小适用范围和周期计数的麻烦性,gettimeofday是一个可移植性更好相对较准确的方法。在Linux系统中,该函数计时精度可达到微秒级。
& & 测量某程序执行时间时,可在待计时程序段起始和结束处分别调用gettimeofday函数,用后一次获取的当前时间减去前一次获取的当前时间得到运行该程序所消耗的秒或微秒数。如:
1 #include &sys/time.h&
2 #define TIME_ELAPSED(codeToTime) do{ \
struct timeval beginTime, endT \
gettimeofday(&beginTime, NULL); \
{codeToT} \
gettimeofday(&endTime, NULL); \
long secTime
= endTime.tv_sec - beginTime.tv_ \
long usecTime = endTime.tv_usec - beginTime.tv_ \
printf(&[%s(%d)]Elapsed Time: SecTime = %lds, UsecTime = %ldus!\n&, __FUNCTION__, __LINE__, secTime, usecTime); \
10 }while(0)
12 void GetTimeofDayTiming(void){
struct timeval tBeginTime, tEndT
gettimeofday(&tBeginTime, NULL);
TimingFunc();
gettimeofday(&tEndTime, NULL);
float fCostTime = 1000000*(tEndTime.tv_sec-tBeginTime.tv_sec)+ //先减后加避免溢出!
(tEndTime.tv_usec-tBeginTime.tv_usec);
fCostTime /= 1000000;
printf(&[gettimeofday]Cost Time = %fSec\n&, fCostTime);
& & &使用gettimeofday函数计时时应注意:
& & &1) 该函数的实现因系统和平台而异,故计时精度也随之而异。Linux系统直接提取硬件时钟来实现该函数,故精度接近周期计数精度;而Windows NT系统使用间隔计数实现,故精度较低。i386平台下采用内核sys_gettimeofday系统调用实现,调用时会向内核发送软中断,然后陷入内核态,内核进行软中断等处理并将执行结果复制到用户态,这些成本超过1微秒;而x86_64平台下采用vsyscall虚拟系统调用实现,创建一个用户态有权限访问的内核态共享内存页面,不通过中断即可获取系统时间,调用成本不到1微秒。
& & &2) 该函数依赖于系统时间,若系统时间被人为改变则获取的时间随之改变。
& & &3) 若计时过程中系统正在运行其他后台程序,可能会影响到最终的计时结果。
& & &可用gettimeofday函数和usleep调用精确地计算处理器主频,如下:
1 void CalcCpuFreq3(void){
struct timeval tStartTime, tEndT
cycle_t tStart = CurrentCycle();
gettimeofday(&tStartTime, NULL);
usleep(1000000); //精度不高,由gettimeofday加以补偿
cycle_t tEnd = CurrentCycle();
gettimeofday(&tEndTime, NULL);
int dwUsecDelay
= 1000000 * (tEndTime.tv_sec - tStartTime.tv_sec) +
(tEndTime.tv_usec - tStartTime.tv_usec);
printf(&CPU Frequency: %lldMHz\n&, (tEnd-tStart)/dwUsecDelay);
2.4 clock函数
& & &clock是ANSI C标准库函数,其函数原型声明在time.h头文件中:
clock_t clock(void);
& & &该函数返回自待测试程序进程开始运行起,到程序中调用clock函数时的处理器时钟计时单元数(俗称clock tick,即硬件时钟滴答次数)。若无法得到处理器时间,则返回-1。时钟计时单元的长短由CPU控制,但clock tick并非CPU时钟周期,而是一个C/C++基本计时单位。返回类型clock_t通常定义为有符号长整型(long int)。
& & &使用clock函数时应注意以下几点:
& & &1) 该函数返回处理器耗费在某程序上的时间(CPU时间片数量)。若程序中存在sleep函数,则sleep所消耗的时间将不计算在内,因为此时CPU资源被释放。
& & &2) 返回值若以秒计需除以CLOCKS_PER_SEC宏,该宏表示一秒钟有多少个时钟计时单元(硬件滴答数),取值因系统而异。在POSIX兼容系统中,CLOCKS_PER_SEC值为1,000,000,即1MHz(此时返回值单位为微秒)。通过(231-1)/≈35.8可估算出clock函数超过半小时后将会溢出。
& & &3) 该函数仅能返回毫秒级的计时精度(大致与操作系统的线程切换时间相当),低于精度的程序计为0毫秒。因此,该函数适用于测量一些耗时较长(大于10ms)的大型程序或循环程序。
& & &4) 当程序单线程或单核心机器运行时,该函数计时准确;但多线程环境下并发执行时不可使用,因为结束时间与起始时间之差是多个核心总共执行的时钟滴答数,会造成计时偏大。
& & &5) 该函数未考虑CPU被子进程使用的情况,也不能区分用户模式和内核模式。该函数计量进程占用的CPU时间,大约是用户时间和系统时间的总和。
& & 测量某程序执行时间时,可在待计时程序段起始和结束处分别调用clock函数,用后一次的返回值减去前一次的返回值得到运行该程序所消耗的处理器时钟计时单元数,再除以CLOCKS_PER_SEC转换为秒。如:
1 #include &time.h&
2 void ClockTiming(void){ //可尝试在计时间隔内调用sleep(5),观察计时结果是否增加5秒
clock_t tBeginTime = clock(); //记录起始时间
TimingFunc(); //待计时函数
clock_t tEndTime = clock(); //记录结束时间
double fCostTime = (double)(tEndTime - tBeginTime)/CLOCKS_PER_SEC; //注意类型强制转换
printf(&[clock]Cost Time = %fSec\n&, fCostTime);
2.5 time函数
& & &time是ANSI C标准库函数,其函数原型声明在time.h头文件中:
time_t time(time_t * timer);
& & &该函数返回当前的日历时间(以秒计)。若参数timer为非NULL指针,则时间值也通过该指针存储。若机器无法提供当前时间,或时间值过大而无法用time_t表示,则函数返回(time_t)-1。返回类型time_t通常定义为有符号长整型(long)。
& & &测量某程序执行时间时,可在待计时程序段起始和结束处分别调用time函数,用后一次的返回值减去前一次的返回值即可得到运行该程序所消耗的秒数。如:
1 #include &time.h&
2 void TimeTiming(void){
time_t tBeginTime = time(NULL);
TimingFunc();
time_t tEndTime = time(NULL);
double fCostTime = difftime(tEndTime, tBeginTime);
printf(&[time]Cost Time = %fSec\n&, fCostTime);
& & &注意,时间类型time_t是个“可表示时间的算术类型(arithmetic type capable of representing times)”别名。但C标准并未规定time函数中该算术类型的时间编码方式。POSIX规定time函数必须返回一个时间整数,表示自Epoch(00:00 hours, Jan 1, 1970 UTC)以来的秒数;但库函数可能采用不同的时间表示方式。因此不应使用字面值常量,因其含义可能因编译器而异。
& & &遵循POSIX规范的程序可直接对time_t对象进行算术运算;可移植程序则应调用相关标准库函数(如localtime、gmtime或difftime),将time_t对象转换为可移植类型。TimeTiming函数即使用difftime函数将先后调用time所获得的时间差值转换为秒。
& & &Linux下time返回值为秒数,故difftime调用处等效于double fCostTime = (double)(tEndTime-tBeginTime)。注意,虽然difftime函数返回类型为double类型,但其值为以秒计的时间间隔,故只能精确到秒。
& & &以下代码分别给出两种版本,以实现在至少dwWorkSec(秒)时间内多次执行TimingFunc:
1 #include &time.h&
2 int NoncompliantWork(int dwWorkSec){
time_t tStart = time(NULL);
if(tStart == (time_t)(-1))
return -1;
while(time(NULL) & tStart + dwWorkSec){ //时间编码方式未定义,故加法运算不能保证增加dwWorkSec秒
TimingFunc(); //Do some work
12 int CompliantWork(int dwWorkSec){
time_t tStart = time(NULL);
time_t tCurrent = tS
if(tStart == (time_t)(-1))
return -1;
while(difftime(tCurrent, tStart) & dwWorkSec){ //因time_t表示范围所限,可能造成死循环(infinite loop)
TimingFunc(); //Do some work
tCurrent = time(NULL);
if(tCurrent == (time_t)(-1))
return -1;
2.6 clock_gettime函数
& & &clock_gettime是POSIX1003.1实时函数,其函数原型声明在time.h头文件中:
int clock_gettime(clockid_t clk_id, struct timespec *tp);
& & &该函数获取tp关于指定时钟的当前timespec值,并将其存入指针tp所指结构体。其结构体定义为:
1 struct timespec{
time_t tv_
//自日以来经过的秒数
//自上一秒开始经过的纳秒数(nanoseconds)
& & &可见,该函数计时精度达到纳秒级。若函数执行成功,则返回0;否则返回一个错误码。
& & &clockid_t值用于指定计时器的类型,POSIX.1b所支持的标准计时器如下:
CLOCK_REALTIME:系统范围内的实时时钟,反映挂钟时间(wall clock time),即绝对时间。若系统时钟源被改变或系统时间被重置,该时钟会相应地调整。若指定该时钟类型,clock_gettime函数等效于gettimeofday函数,尽管精度有所不同。CLOCK_MONOTONIC:单调时间,不可设置。该时间通过jiffies值计算,其值为当前时间减去起始时间之差,即从系统启动至今所经过的时间。单调时间在运行期间会一直稳定增加,而不受系统时钟的影响。若指定该时钟类型,则tv_sec值与“cat
/proc/uptime”第一个输出值(秒)相同。CLOCK_PROCESS_CPUTIME_ID:每个进程的CPU高精度硬件计时器。CLOCK_THREAD_CPUTIME_ID:每个线程的CPU高精度硬件计时器。
& & 因为CLOCK_MONOTONIC计时器更加稳定,故推荐以此获得系统的运行时间。结合/proc/uptime文件,可通过以下几种方式获得系统自举以来的秒数:
1 #include &fcntl.h&
2 #include &unistd.h&
3 //通过文件接口读取/proc/uptime中的值进行字符串的转换
4 int GetSysTime(int *pSec, int *pMsec){
if(NULL == pSec && NULL == pMsec)
return -1;
int dwFd = open(&/proc/uptime&, O_RDONLY);
if(dwFd &= 0)
return -2;
char acReadBuf[128] = {0};
if(read(dwFd, acReadBuf, sizeof(acReadBuf)) &= 0)
return -3;
int dwSecond = 0, dwMsecond = 0;
sscanf(acReadBuf, &%d.%d[^ ]&, &dwSecond, &dwMsecond);
if(pSec != NULL)
*pSec = dwS
if(pMsec != NULL)
*pMsec = dwM
close(dwFd);
27 #include &sys/syscall.h&
28 //利用__NR_clock_gettime系统调用直接获取(编译链接时无需-lrt选项)
29 int GetSysTime2(int *pSec, int *pMsec){
if(NULL == pSec && NULL == pMsec)
return -1;
struct timespec tS
memset(&tSpec, 0, sizeof(tSpec));
syscall(__NR_clock_gettime, CLOCK_MONOTONIC, &tSpec);
if(pSec != NULL)
*pSec = tSpec.tv_
if(pMsec != NULL)
*pMsec = tSpec.tv_nsec/1000;
45 int GetSysTime3(int *pSec, int *pMsec){
if(NULL == pSec && NULL == pMsec)
return -1;
struct timespec tS
memset(&tSpec, 0, sizeof(tSpec));
clock_gettime(CLOCK_MONOTONIC, &tSpec);
if(pSec != NULL)
*pSec = tSpec.tv_
if(pMsec != NULL)
*pMsec = tSpec.tv_nsec/1000;
& & &注意,/proc/uptime文件第二列输出为系统空闲的时间(以秒为单位),该时间计算时会计入SMP系统中所有逻辑CPU。
& & 测量某程序执行时间时,可在待计时程序段起始和结束处分别调用clock_gettime函数,用后一次获取的当前时间减去前一次获取的当前时间得到运行该程序所消耗的秒或微秒数。如:
1 #include &time.h&
2 void ClockGetTimeTiming(void){
struct timespec tBeginTime, tEndT
clock_gettime(CLOCK_MONOTONIC, &tBeginTime);
TimingFunc();
clock_gettime(CLOCK_MONOTONIC, &tEndTime);
double fCostTime = (tEndTime.tv_sec-tBeginTime.tv_sec) +
(double)(tEndTime.tv_nsec-tBeginTime.tv_nsec)/;
printf(&[clock_gettime]Cost Time = %fSec\n&, fCostTime);
& & &注意,编译链接时需加上-lrt选项,因为clock_gettime函数在librt库中实现。
& & &以下代码通过settimeofday函数将当前系统时间往回设置10秒,对比gettimeofday和clock_gettime所受的影响。注意,只有root权限才能调用settimeofday函数修改当前时间。
1 #include &time.h&
2 #include &unistd.h&
3 #include &sys/time.h&
5 void ChangeSysTime(void){
struct timeval tv1, tv2;
struct timespec ts1, ts2;
gettimeofday(&tv1, NULL);
clock_gettime(CLOCK_MONOTONIC, &ts1);
struct timeval temp = tv1;
temp.tv_sec -= 10;
settimeofday(&temp, NULL); //将当前系统时间往回设置10秒
gettimeofday(&tv2, NULL);
clock_gettime(CLOCK_MONOTONIC, &ts2);
printf(&gettimeofday: [%ld.%6ld ~ %ld.%6ld] =& diff = %f\n&, tv1.tv_sec, tv1.tv_usec, tv2.tv_sec, tv2.tv_usec,
((tv2.tv_sec*1000000+tv2.tv_usec)-(tv1.tv_sec*1000000+tv1.tv_usec))/);
printf(&clock_gettime: [%ld.%9ld ~ %ld.%9ld] =& diff = %f\n&, ts1.tv_sec, ts1.tv_nsec, ts2.tv_sec, ts2.tv_nsec,
((ts2.tv_sec*+ts2.tv_nsec)-(ts1.tv_sec*+ts1.tv_nsec))/.0);
tv2.tv_sec += 10;
settimeofday(&tv2, NULL); //恢复系统时间
gettimeofday(&tv2, NULL);
printf(&gettimeofday2: [%ld.%6ld]\n&, tv2.tv_sec, tv2.tv_usec);
& & &执行结果输出如下:
& & &可见,当系统时间被人为改动时,gettimeofday函数计算的时间差存在偏差;clock_getime函数计时则不受影响,仅与实际所经历的时间相关。
2.7 getrusage函数
& & &getrusage函数来自BSD系统,其函数原型声明在sys/resource.h头文件中:
int getrusage(int who, struct rusage *usage);
& & &该函数获取当前进程或其所有已终止的子进程的资源使用信息,并将其存入指针usage所指结构体。该结构体定义为:
1 struct rusage{
struct timeval ru_
//time spent executing in user mode
struct timeval ru_
//time spent in the system executing on behalf of the process
//maximum resident set size utilized(in kilobytes)
ru_ //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.
ru_ //integral value of the amount of unshared memory residing in the data segment of a process(expressed in units of kilobytes * ticks-of-execution)
ru_ //integral value of the amount of unshared memory residing in the stack segment of a process(expressed in units of kilobytes * ticks-of-execution)
ru_ //number of page faults serviced without any I/O here I/O activity is avoided by ''reclaiming'' a page frame from the list of pages awaiting reallocation
//number of page faults serviced that required I/O activity
//number of times a process was ''swapped'' out of main memory
//number of times the file system had to perform input(account only for real I/O)
//number of times the file system had to perform output(account only for real I/O)
//number of IPC messages sent
//number of IPC messages received
//number of signals delivered
//voluntary context switches
// involuntary context switches
& & &在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){
struct rusage tBeginResource, tEndR
getrusage(RUSAGE_SELF, &tBeginResource);
TimingFunc();
getrusage(RUSAGE_SELF, &tEndResource);
unsigned dwCostSec = (tEndResource.ru_utime.tv_sec-tBeginResource.ru_utime.tv_sec) +
(tEndResource.ru_stime.tv_sec-tBeginResource.ru_stime.tv_sec);
unsigned dwCostUsec = (tEndResource.ru_utime.tv_usec-tBeginResource.ru_utime.tv_usec) +
(tEndResource.ru_stime.tv_usec-tBeginResource.ru_stime.tv_usec);
printf(&[getrusage]Cost Time = %dSec, %dUsec\n&, dwCostSec, dwCostUsec);
& & &当应用程序创建进程或使用线程时,计量出的时间会随着应用程序和计时函数的变化而不同。尤其是当应用程序创建一个子进程,而该子进程随后通过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{
FTiming fnTimingF
8 }T_FUNC_MAP;
9 #define FUNC_ENTRY(funcName)
{funcName, #funcName}
10 T_FUNC_MAP TimingFuncMap[] = {
FUNC_ENTRY(CalcMul),
FUNC_ENTRY(CalcDiv),
FUNC_ENTRY(CalcMod)
15 const unsigned FUNC_MAP_NUM = (unsigned)(sizeof(TimingFuncMap)/sizeof(T_FUNC_MAP));
17 void BatchTiming(void){
struct timeval tBeginTime, tEndT
unsigned iFuncIdx = 0;
for(iFuncIdx = 0; iFuncIdx & FUNC_MAP_NUM; iFuncIdx++){
gettimeofday(&tBeginTime, NULL);
TimingFuncMap[iFuncIdx].fnTimingFunc();
gettimeofday(&tEndTime, NULL);
float fCostTime = 1000000*(tEndTime.tv_sec-tBeginTime.tv_sec) +
(tEndTime.tv_usec-tBeginTime.tv_usec);
printf(&[%s]Cost: %fSec\n&, TimingFuncMap[iFuncIdx].pszFuncName, fCostTime/1000000);
& & &示例中TimingFuncMap初始化列表仅注册三个函数(CalcMul等)。当待测函数多达数百以上时,可借助工具提取源文件中所有函数名。
& & &批量计时应注意以下几点:
& & &1) 多次运行待测函数取均值可减小统计误差,得出较为精确的运行时间。但要注意待测函数耗时应远大于循环指令执行时间,且需考虑清空高速缓存。
& & &2) 批量计时过程中,若系统时钟被改变,则gettimeofday函数将依据新的时间来计时,导致计时偏差。此时可选用不受系统时间影响的函数(如clock、times等)。
& & &对比本文所述的各种计时方式,如下表所示:
& & & & & 计时方式
& & & & &精度
& & & & & & & & & & & & & & & & & & 计时范围
10毫秒(ms)
10毫秒(ms)
(231-1)/≈35分
10毫秒(ms)
(231-1)/100/3天
取决于CPU主频(主频为1GHz时约583年)
(231-1)/≈68年
gettimeofday函数
1微秒(μs)
((231-1)+(231-1)/00/24/365≈68年
clock_gettime函数
约同gettimeofday函数
getrusage函数
1微秒(μs)
同gettimeofday函数
1秒(second) = 1,000毫秒(millisecond) = 1,000,000微秒(microsecond) = 1,000,000,000纳秒(nanosecond)
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:9546次
排名:千里之外
转载:16篇
(3)(2)(2)(2)(3)(1)(1)(1)(2)

我要回帖

更多关于 android jiffies 的文章

 

随机推荐