Board logo

标题: Linux 下批量计时中的问题(1) [打印本页]

作者: look_w    时间: 2018-5-7 19:40     标题: Linux 下批量计时中的问题(1)

这里所谓的大规模,意思是指成百上千的函数或可执行文件,也就是说,不是针对某一个函数或文件来进行计时操作。大致上,可以划分为两种情形:1、使用程序设计语言如,C 或 C++ 在文件内部对函数进行批量计时;2、在 Shell 下使用脚本语言批量对文件执行时间进行计时。在计时的过程中,还会遇到系统时钟改变等一些的特殊情况,这时我们要如何处理?
因为 Linux 下的开发语言很多,每一种语言都有自己的使用风格和特色,我们不可能针对每一种语言都来探讨其中的批量计时问题,这里只选用具有典型特色的 C、C++ 和 Shell Script 语言来进行描述。
使用 C 语言对函数批量计时问题的典型场景:某程序员因工作需要编写了数量众多的C函数,之后有要求记录所有这些函数的运行时间,以完成性能评测。
为了讨论的方便,我们先假定待测函数都是无参数返回类型相同的函数。在实际操作过程中,经常遇到的也就是这一种情况。对于计时来说,函数返回类型是什么并不重要,因为我们只是测试函数调用执行时间,而并不关心返回结果。传入参数对函数调用是有影响的,而且也很可能会影响到函数运行的时间。但为了简化问题,我们还是把讨论的范围限制在无参数的情形下。如果出现极端的情形,每一个待测函数都有着不同的参数列表,那么只能针对每个函数都测量一次结果,而别无它法。下面是我们的想法。
最原始的解决办法是针对每一个函数都前后两次调用 C 函数 gettimeofday,然后做差值。以前经常也会用函数 time 来取时间,但现在它已经被 gettimeofday 替代了。
清单1. 使用 gettimeofday 在 C 语言里进行计时
1
2
3
4
5
gettimeofday( &start, NULL );
foobar();
gettimeofday( &end, NULL );
timeuse = 1000000 * ( end.tv_sec - start.tv_sec ) + end.tv_usec - start.tv_usec;
timeuse /= 1000000;




最后将得到的时间值输出到控制台或文件当中。由于待测的函数总是会先编写出来,这样做,最大的一个问题就是针对每个函数都要重复拷贝上面一段代码,结果是代码冗余度太高,代码质量也随之下降,并且还不具通用性。程序员的心理都是要创造出高效、实用且美观的代码,象这种质量的代码程序员是无法接受的。
现在问题已经很清楚了,那就是如何使用尽可能少的语句来完成函数的批量计时任务,同时要保证代码具有较好的通用性。我们的想法如下。为了消除计时和输出代码的冗余,必定要用到循环,在循环体内对每一个函数执行计时操作,这样可以使冗余代码量大大降低。但调用函数的语句是肯定要存在,所以现在的目标就是如何在循环体内依次调用程序中的出现的函数,为了设定循环次数,又需要弄清楚程序中到底有多少个函数。
ANSI C中并没有如同类似现在JAVA等面向对象语言中反射或自省的功能,不可能做到在运行时动态调用方法,也无法动态得知一些运行时信息。因此在文件中编写动态调用方法的代码这条路线肯定是不通的,那么就必须预先得到所有的待测函数名称,也就是说先要静态指定函数名称。
我们采用函数指针,函数指针可以代替switch/if语句,实现晚绑定和回调功能。在这里,我们仅用函数指针来实现同类型函数的调用。
清单2. 使用函数指针实现同类型函数的调用
1
2
3
4
5
6
7
8
9
10
11
12
13
#define numFuncs 2
int foobar1() { …… }
int foobar2() { …… }
……
typedef int ( *ptFuncDef )();
ptFuncDef ptFuncArr[] = { &foobar1, &foobar2, …… };
……
for ( i = 0; i < numFuncs; i++ ) {
    gettimeofday( &start, NULL );
    ptFuncArr();
    gettimeofday( &end, NULL );
    ……
}




函数指针确实可以使代码数量大大降低,但现在的问题是数组ptFuncArr初始化列表要如何生成。几百个函数,如果全部由手工在后面添加的话,会是一件很烦琐的工作。有没有可以提取源文件中所有函数名称的工具?
一些可视化的编辑工具,如Source Insight、Source Navigator等都可以识别C语言的函数,可以将所有用到的函数名称,除main外,拷贝生成文本文件。
清单3. 文本文件的内容
1
2
3
foobar1
foobar2
……




要生成初始化列表,可以使用sed命令对文本文件的内容进行替换,加上前缀"&"和后缀","。
清单4. 使用sed命令把in.file中每一行都加上前缀"&"和后缀","
1
sed -e 's/^/\&/' -e 's/$/,/' in.file > out.file




清单5. out.file文件的内容
1
2
3
&foobar1,
&foobar2,
……




如果想要五个函数一行,可以执行下面的Shell Script代码。
清单6. 将out.file文件每五行内容转换为一行的Shell脚本
1
2
3
4
5
6
7
8
9
10
11
12
#!bin/bash
i=0
while read line
do
i=`expr $i + 1`
if [ $i -lt 5 ] ; then
   echo $line | tr -d "\n" >> "new.file"
else
   echo $line >> "new.file"
   i=0
fi
done < "out.file"




记着要把new.file文件中最后一个","删除,然后用新文件的内容填充数组ptFuncArr初始化列表。
在批量计时的过程中,一些问题会凸显出来。例如,中途系统时钟被改变了,会怎么样?C函数gettimeofday将会依据新的时间来计时,计时结果将会发生错误。这时可以使用类似Windows中GetTickCount的C函数times或clock。
清单7. clock函数的用法
1
2
3
4
5
6
#include<time.h>
……
begin = clock();
……
end = clock();
fprintf( fh, "%8.2g\n", (begin - end) / CLOCKS_PER_SEC );




这样就会避免发生上述问题,排除系统时钟改变的干扰。
        并且,有人曾经做过测试,连续两次使用gettimeofday时,会以一种小概率出现"时光倒流"的现象,第二次函数调用得到的时间要小于或说早于第一次调用得到的时间。gettimeofday函数并不是那么稳定,没有times或clock计时准确,但它们用法相似。clock有计时限制,据说是596.5+小时,一般情况足以应付。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0