事情的起因是我在对 SAT Solver
进行优化测试时,发现了我的求解器测不准时间,具体表现为,我在代码中测试的时间与 gprof
得到的时间不相符,后者的时间要比前者少将近 ,实在是让人匪夷所思。
背景知识
一个简单的 SAT Solver
,我们以 DPLL
算法为例,其框架如下所示:
我们假设这个过程为函数 solve
,于是,我们测时间的代码如下:
于是,dur
就是求解的时间
gprof
简介
Gprof 是 GNU binutils 工具之一。可以分析出代码中每个函数的调用次数、每个函数消耗的处理器时间等,我们通过在编译选项中加上 -pg
即可开启。
例如:
随后,运行 ./main
后,会产生一个名为 gmon.out
的文件,我们通过如下命令来解析:
即可查看 profiling
的结果。
其工作原理十分简单,主要是利用了插桩,他首先会激活一个名为 mcount
的函数,随后,他会在运行每段函数之前都去调用这个 mcount
来记录此函数的调用次数以及时间,例如,我们在 propagate
函数中:
粗略了解了这些,我们来进行问题的介绍。
测试结果
然而,我们得到的结果如下所示:
然而,gprof
的结果如下所示:
可以发现一个
原因分析
查询了诸多资料,发现有之前有人提出过类似的 问题,但时间却已经是 2010 年了,其解决方法都是换一个 profiling
工具,并没有给出这个问题的原因。
由于我们代码的运行时间几乎 都在运行
propagate
函数,因此下面我们只考虑这一部分(测不准的也在这部分)
这里,我给出一个猜测的原因,由于我们的 propagate
代码的写法如下:
然而,其结果如下所示:
我们统计了 propagate
被调用的次数:
于是,我们有一个合理的怀疑:
由于函数调用会有上下文切换的额外开销,在如此之大的函数调用数下,是否大多数的时间都在运行调用栈的开辟与恢复?而由于
gprof
的插桩也只能让我们测试出每个函数的运行时间,反而在切换时的时间会忽略,这是否也是造成gprof
无法测准时间的一大重要原因?
接着,我们直接手动内联(指人力内联)传播的两个函数,合并为一整个 propagate
函数后,我们发现 gprof
能够测准时间了,并且时间与程序内时间函数测试的一致(并没有减少),但相较于频繁的函数调用,时间还是减少了不少。
结论
在函数调用次数过多,大于 ,我们推荐不要再使用函数,直接手动内联,否则在函数调用的开销会十分巨大,导致程序速度变慢,且 gprof
无法测准函数运行时间。