zoukankan      html  css  js  c++  java
  • 探讨性能测试中的计时问题

    下载源代码

    摘要:本文结合作者在代码性能测试工作中的经验,介绍一组自己封装的的计时函数。使用该组函数可以简化测试工作,从而把更多的精力放在主要工作上,不需要过多地维护计时代码,仅仅使用两个宏就可以方便、精确地实现多个模块、多方式的时间性能测试,并且计时结果以一个文本文件独立保存,清晰直观。

    关键字:精确计时;性能测试;

    在系统测试时,尤其在需要测试算法或者某些模块的运行时间时,往往需要调用一些时间函数库(如VC中的timeGetTime等可以获取毫秒级的时间),在待测试的模块前后分别测试时间,然后,计算前后两个时间的差值,就得到模块的运行时间,如图 1。



    图 1 一个典型的模块计时方法


    但是,使用原始的计时函数直接进行时间测试在很多复杂情况下不方便,如图 1,当在一个模块中有多个子模块需要分别计时,所编写的计时代码甚至比原有的代码还多,这增加了程序维护和阅读的难度,容易出错。作者结合自己在相关工作中的经验,封装了一组计时函数,共享给大家。该组函数有如下几个优点:

    • 计时精确:封装的是高精度的计时API函数QueryPerformanceCounter(),该函数根据硬件定时器的频率,理论上可以得到微秒(us)级精度的计时结果;
    • 使用简单:只用在待测试的模块前后加上两个宏BM_START和BM_END,不需要对结果进行计算,也不需要考虑对各个模块测试结果数据的维护,这些操作已经被封装。
    • 结果输出独立:在系统运行结果时,只需要调用一个函数就可以把计时结果保存在一个文本文件里,如图 5和图 8所示。

    1. 高精度计时函数
    在Windows系统下,程序员通常可以使用多种方式来进行时间控制:如使用前文提到的timeGetTime()函数,或者使用GetTickCount()函数,又或实现WM_TIMER消息的映射等等。但是这些方法得到的时间精度都有一定的局限性,为了增加下文将到介绍的计时函数库的适用性,本文采用高精度的时控API函数QueryPerformanceCounter()。

    计时之前,调用QueryPerformanceFrequency()函数获得机器内部定时器的时钟频率,然后在需要计时的模块前后分别调用QueryPerformanceCounter()函数,利用两次获得的计数之差获得时钟频率,计算出模块的运行时间。代码如图 2:

    图 2 精确计时代码段

    2. 封装计时函数
    2.1. 数据结构
    为了维护计时结果,我们定义如下几个数据:

    #define BENCHMARK_MAX_COUNT 20
    
    double gStarts[BENCHMARK_MAX_COUNT];
    
    double gEnds[BENCHMARK_MAX_COUNT];
    
    double gCounters[BENCHMARK_MAX_COUNT];
    
    double dfFreq = 1;


    其中,BENCHMARK_MAX_COUNT定义了需要计时的模块总数,20表示最多可以定时20个模块,该值可以根据具体应用而定。gStarts和gEnds分别用于保存开始计时和终止计时的计数器的值,gCounters用来保存计时结果。全局变量dfFreq用来保存上文介绍的时钟频率,如图 2所示。

    2.2. 初始化InitBenchmark()
    初始化函数InitBenchmark()包括两部分内容:

    • 对数组gStarts, gEnds, gCounter清零;
    • 获得机器内部定时器时钟频率。
    InitBenchmark()代码如下所示:

    void InitBenchmark()
    {
             ResetBenchmarkCounters();
             GetClockFrequent();
    }

    该函数一般在程序运行最初调用。

    2.3. 开始计时BMTimerStart()
    开始计时函数BMTimerStart()放在计时模块的开始,函数定义如下:

    void BMTimerStart(int iModel)
    {
             LARGE_INTEGER litmp;
             QueryPerformanceCounter(&litmp);
             gStarts[iModel] = litmp.QuadPart;
    }
    其中参数iModel表示当前计时的模块序号,0<=iModel<=BENCHMARK_MAX_COUNT;为了简化调用代码,我们给出一个宏定义如下:
    #define BM_START(t)                  BMTimerStart(t);
    2.4. 终止计时BMTimerEnd()
    终止计时函数BMTimerEnd()放在计时模块的结束,函数定义如下:
    void BMTimerEnd(int iModel)
    {       
             LARGE_INTEGER litmp;
             QueryPerformanceCounter(&litmp);
             gEnds[iModel] = litmp.QuadPart;
             gCounters[iModel] += (((gEnds[iModel] - gStarts[iModel]) / dfFreq) * 1000000);
    }

    参数iModel同BMTimerStart()。本函数首先获取当前的时钟数,然后除以dfFreq得到运行时间。对于最后一条语句:

    gCounters[iModel] += (((gEnds[iModel] - gStarts[iModel]) / dfFreq) * 1000000);

    要注意两点:

    • 用“+=”而不是“=”,这个看似简单的代替,可以实现对同一个模块的重复计时,后文3.3节列举的情况;
    • 乘以1000000,表示计时单位为微秒(us)。

    类似BMTimerStart(),同样为BMTimerEnd()定义一个宏:

    #define BM_END(t)             BMTimerEnd(t);

    2.5. 结果输出WriteData()
    以一个文本文件(见图 5和图 8)把全局变量gCounters中的所有值输出,该函数一般在程序结束处调用,如图 4中最后一行代码所示。由于篇幅限制,具体实现代码请参考源程序。

    3. 计时测试实例
    3.1. 多个模块计时
    图 3展示了嵌套计时以及对一个函数中多个模块进行计时的代码,图中可以看到,利用输入参数我们对计时模块进行统一编号,测试代码相对图 1更清晰、直观。



    图 3 用我们的函数实现嵌套计时

    3.2. 循环内部计时
    图 4中的代码展示了我们对循环体内每次执行运算的计时,只需简单地给出参数 就可以得到从1到20的阶乘的每次计算计算时间,计时结果输出为文件“D:\log.txt”,如图 5。



    图 4 对循环中每次运算的计时



    图 5 计时结果1

    3.3. 循环累加计时
    从图 5可以看到,由于现代计算机处理速度越来越快,一些简单运算的模块,微秒的计时单位几乎都不够精确,因此,一种常用的测试方法就是对同一模块进行N (N 取1000,10000等)次重复执行。使用本文介绍的计时函数,我们可以采用两种方式对这种情况进行测试,代码分别如图 6和图 7,请注意二者的区别,并请读者分析为何图 7中的方法也是可行的。 N次运算计时结果如图 8。


    图 6 累加计时1

    图 7 累加计时2



    图 8 计时结果2

    4. 结束语

    本文实现了一组计时函数的封装,并给出几种特殊情况下的测试实例,实验表明该组函数可以满足各种复杂情况下的计时,能够很方便地应用的实际的测试工作中。当然,还可以进一步封装成一个计时类,留给读者们自己去做。

    原为转自

    作者:BuildNewApp
    出处:http://syxchina.cnblogs.comBuildNewApp.com
    本文版权归作者、博客园和百度空间共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则作者会诅咒你的。
    如果您阅读了我的文章并觉得有价值请点击此处,谢谢您的肯定1。
  • 相关阅读:
    leetcode_138复制带随机指针的链表
    minSTL
    LLVM
    STL基础_迭代器
    mysql数据库表清空后id如何从1开始自增
    explain用法和结果分析
    MySQL多表查询与子查询
    数据结构与算法笔记
    MySQL数据库的SQL语言与视图
    mysql忘记密码解决方案
  • 原文地址:https://www.cnblogs.com/syxchina/p/2197581.html
Copyright © 2011-2022 走看看