Linux 下批量计时中的问题 | ![]() |
| ![]() | ||
![]() | |||||
![]() |
![]() |
车皓阳 在作者的 前面一篇文章中,讨论了在Linux下与计时相关的一些基本问题,以及解决这些问题的手段和方法。本文将会关注大规模测试下的计时方法问题,以及中间会遇到的一些异常情况及其应对的策略。 这里所谓的大规模,意思是指成百上千的函数或可执行文件,也就是说,不是针对某一个函数或文件来进行计时操作。大致上,可以划分为两种情形:1、使用程序设计语言如,C 或 C++ 在文件内部对函数进行批量计时;2、在 Shell 下使用脚本语言批量对文件执行时间进行计时。在计时的过程中,还会遇到系统时钟改变等一些的特殊情况,这时我们要如何处理? 因为 Linux 下的开发语言很多,每一种语言都有自己的使用风格和特色,我们不可能针对每一种语言都来探讨其中的批量计时问题,这里只选用具有典型特色的 C、C++ 和 Shell Script 语言来进行描述。 使用 C 语言对函数批量计时 为了讨论的方便,我们先假定待测函数都是无参数返回类型相同的函数。在实际操作过程中,经常遇到的也就是这一种情况。对于计时来说,函数返回类型是什么并不重要,因为我们只是测试函数调用执行时间,而并不关心返回结果。传入参数对函数调用是有影响的,而且也很可能会影响到函数运行的时间。但为了简化问题,我们还是把讨论的范围限制在无参数的情形下。如果出现极端的情形,每一个待测函数都有着不同的参数列表,那么只能针对每个函数都测量一次结果,而别无它法。下面是我们的想法。 最原始的解决办法是针对每一个函数都前后两次调用 C 函数 gettimeofday,然后做差值。以前经常也会用函数 time 来取时间,但现在它已经被 gettimeofday 替代了。 清单1. 使用 gettimeofday 在 C 语言里进行计时
最后将得到的时间值输出到控制台或文件当中。由于待测的函数总是会先编写出来,这样做,最大的一个问题就是针对每个函数都要重复拷贝上面一段代码,结果是代码冗余度太高,代码质量也随之下降,并且还不具通用性。程序员的心理都是要创造出高效、实用且美观的代码,象这种质量的代码程序员是无法接受的。 现在问题已经很清楚了,那就是如何使用尽可能少的语句来完成函数的批量计时任务,同时要保证代码具有较好的通用性。我们的想法如下。为了消除计时和输出代码的冗余,必定要用到循环,在循环体内对每一个函数执行计时操作,这样可以使冗余代码量大大降低。但调用函数的语句是肯定要存在,所以现在的目标就是如何在循环体内依次调用程序中的出现的函数,为了设定循环次数,又需要弄清楚程序中到底有多少个函数。 ANSI C中并没有如同类似现在JAVA等面向对象语言中反射或自省的功能,不可能做到在运行时动态调用方法,也无法动态得知一些运行时信息。因此在文件中编写动态调用方法的代码这条路线肯定是不通的,那么就必须预先得到所有的待测函数名称,也就是说先要静态指定函数名称。 我们采用函数指针,函数指针可以代替switch/if语句,实现晚绑定和回调功能。在这里,我们仅用函数指针来实现同类型函数的调用。 清单2. 使用函数指针实现同类型函数的调用
函数指针确实可以使代码数量大大降低,但现在的问题是数组ptFuncArr初始化列表要如何生成。几百个函数,如果全部由手工在后面添加的话,会是一件很烦琐的工作。有没有可以提取源文件中所有函数名称的工具? 一些可视化的编辑工具,如Source Insight、Source Navigator等都可以识别C语言的函数,可以将所有用到的函数名称,除main外,拷贝生成文本文件。 清单3. 文本文件的内容
要生成初始化列表,可以使用sed命令对文本文件的内容进行替换,加上前缀"&"和后缀","。 清单4. 使用sed命令把in.file中每一行都加上前缀"&"和后缀","
清单5. out.file文件的内容
如果想要五个函数一行,可以执行下面的Shell Script代码。 清单6. 将out.file文件每五行内容转换为一行的Shell脚本
记着要把new.file文件中最后一个","删除,然后用新文件的内容填充数组ptFuncArr初始化列表。 在批量计时的过程中,一些问题会凸显出来。例如,中途系统时钟被改变了,会怎么样?C函数gettimeofday将会依据新的时间来计时,计时结果将会发生错误。这时可以使用类似Windows中GetTickCount的C函数times或clock。 清单7. clock函数的用法
这样就会避免发生上述问题,排除系统时钟改变的干扰。 并且,有人曾经做过测试,连续两次使用gettimeofday时,会以一种小概率出现"时光倒流"的现象,第二次函数调用得到的时间要小于或说早于第一次调用得到的时间。gettimeofday函数并不是那么稳定,没有times或clock计时准确,但它们用法相似。clock有计时限制,据说是596.5+小时,一般情况足以应付。 使用C++对函数批量计时 清单8. timer类
需要注意的是,在C++文件中定义的函数指针指向的是用户自定义类的成员函数。除此之外,其余地方使用方法大致相同。 清单9. 指向成员函数的指针
还有一点就是,在C++里可以使用vector存储函数指针,不再使用静态数组,vector的作用相当于动态数组,支持对内存的re-allocation。 清单10. 使用vector代替数组
上面这段代码里,需要多次使用push_back将类成员函数地址置入vector,这是很麻烦的。虽然如此,但其它如设计模式中的一些方法, 或许还包括VPTR,都过于复杂了,对于这样一个问题似乎有些大材小用,因此建议还是以此出发点来考虑这个问题。 典型场景1:在某个目录下有一个可执行程序,它接受不同的输入参数。现在有要求在输入几百个不同参数的情况下,测试执行时间,并将结果写入文件中,每一个结果占用一行,以方便后续处理。 首先假设测试参数存储在一个输入文件"in.file"当中。 清单11. 文件in.file中的内容
现在的目标就是每次一行地读出文件中的参数,送给待测可执行程序,作为其执行参数,具体的代码段如下。 清单12. 测量不同参数的同一待测可执行程序在Linux下的运行时间
这里用的是Linux内置的time命令,当然也可以使用GNU的time命令(/usr/bin/time,精度要低一些)。cut命令里"-d"选项指定分隔符,分隔符默认是TAB。在待测可执行程序拥有多个参数的情况下,要使用多个变量来记录,cut命令里"-f"选项后面跟随的数字指定不同的域。再详细的说明可以参见作者的 前面一篇文章。 典型场景2:在某个目录下有好几百个可执行程序,要测试每一个可执行程序的运行时间,并将结果写入文件中,每一个可执行程序都要在文件中占用一行。 假定这些待测可执行程序都不需要指定输入参数,同时假定目录/home/developerworks/test/下的所有文件均为待测可执行文件。 清单13. 测量同一目录下所有的可执行程序在Linux下的运行时间
time命令结果有三行组成:real、user和sys。我们这里用的都是real值,它表示从程序开始到程序执行结束时所消耗的时间,包括CPU的用时。CPU用时被划分为user和sys两块。user值表示程序本身,以及它所调用的库中的子例程使用的时间。sys是由程序直接或间接调用的系统调用执行的时间。 在单处理器上,real值和整个CPU用时之差,也就是real - ( user + sys )是所有延迟程序执行的因素的总和。在SMP上,这个值近似为real * number_of_processors - ( user + sys )。这些因素包括:
结束语
|