对泛型函数性能的拷机测试

Tuesday, January 25, 2005

Torture-testing Generic Function Performance

 

    由于我一直宣称我的PyCon表达在Python上面十分高效,我觉得在这个周末有必要对目前PyProtocols的泛型函数(generic function)执行的性能作一下实际测试。

基于这个想法,我进行了一场拷机测试。我并没有以泛型函数最擅长的那方面为基准(有太多复杂的规则),而决定以一个容易使用假设规则的场景作为基准。我还特别写了一个十分普通的泛型函数,仅有七条规则,为一个人的年龄分配一个参数。之后,我写了同样的函数,作为假设语句的手动调整(hand-tuned)好的一个二叉树。

当然,写这个手动调整的版本花费的是写泛型函数版本三倍长的时间,而且如果本来就有很多规则或者包含比简单范围检查(simple range checking)更复杂的条件,乘法器(multiplier)会变得更加的糟糕。而关键是使测试变得极其不公平,这就类似于将一个未经优化的C++编译器的输出与一个用手动调整的汇编程序作比较。

然而,我的第一次计时运行就显示出泛型函数所用的时间大约是手动调整版本的50倍,这让我吃惊不已。手动调整版本的运行时间大约为十分之八微秒,而泛型函数用了大约40微秒。

可是,我马上意识到第一个问题便是:我这里测试的是纯粹的参数分配,即这些函数除了返回一个值什么都没做:它们要做的事只是决定做什么!所以,理解这个结果的正确方法应是泛型函数不断地作了大约40微秒的加法,这样听起来就合理多了。

但是,如果你想到处都可以使用泛型函数的话,40微秒的开销在我看来就太大了。例如,我想将它们作为peak.web的遍历机制的一部分来使用,它本身只是值转换(value rendering),而这需要能够以1毫秒的零头来运行。并且一次调用只用40微秒,这比你想的快得多。

有趣的是,对于特定输入值,泛型函数真的是非常、非常的快,所用时间缩短为不到20微秒。这是因为这些值是用来做区域测试(range test)的,如果你输入一个值,与其中的一个规则的常数相匹配,它将避免二分查找而是在哈希表中寻找结果。所以,这种情况下的分配操作将与手写的Python函数一样快,但仍有额外的大约18微秒的开销。

用其他输入值进行的这一点实验告诉我们:如果你一直使用一个数值,是处于经过规则测试的区域的最末位置,那就会发生另外一个函数调用,MinMax对象的__cmp__ method。啊哈!这是第一个直接的可翻译成C的标志。我使用Pyrex来构造相关的类和函数,所有输入值的时间开销下降到仅为21微秒。Yay!

我也通过分配算法(dispatch algorithm)观察了流的情况,了解到很大一部分开销来自于参数检索。泛型函数可让你利用规则来测试任意表达式,我为一般情况编了代码,在这类情况下你使用的任何表达式都可能包含随机计算。因而,出于测试目的,我按同样的惯例编了一个快速通道版本,并对其进行了计时测试,虽然得到了一定改进,但仍不能确定如何在一般情况下来实现它们。

所以,我今晚又观察了一下并意识到了我需要做的是重新制定分配逻辑,这就使得如果你的规则只是查看下参数(而非包含参数的随机表达式)就完事了,泛型函数便从输入参数的元组中得到数值,而不用通过“查找表达式”这段代码来完成。这也省却了为每一个invocation分配表达式缓存,除非要实际使用不带参数的表达式。最终结果是:开销从21微秒降到13微秒。考虑到我只是重新整理了一些Python代码而未加入新的C,这已经很不错了。

那些剩下的13微秒大概将需要更多进一步的工作才能消去,并且越来越多的迹象表明只有转到C才能解决这个问题。简单的计时测试和过去的经验告诉我在剩下的代码中每个语句的执行时间是多少,并且这些时间都用在了属性访问或一些不可避免的函数调用上面了(一次调用需要一微秒中的一部分)。消去这些时间开销的唯一方法是将泛函转移到C上,这样属性查找就可被指针解引用(pointer dereferences)所代替。(遗憾的是,额外的调用还会存在,一旦规则被选定,抽象泛型函数仍要对其进行调用,所以总会有这样一个额外函数调用的开销。)

如果至少我可以使分配算法完全的锁无关(lock-free),还是有几个我可以调整的属性访问的(虽说仍旧在Python上)。此后对于每一次分配我将不再需要获得和释放一个线程安全的锁。但那加起来大约有1.5微秒,仅占剩余开销的10%。而且那还要花费点心思来构造一个可证明正确性的锁无关算法。

说到底还是会有一些开销:我将没有能力为像这样的一个拷机测试的场景消除开销了。泛型函数真正擅长的地方在最佳规则中太复杂了,这使我们这些可怜的人们很难正确地编码,或者说我们懒得去手动调整。对于这样的环境,即使是20微秒的开销也将会在噪声中正常地消失掉,可实际把它看成是规则的任何事。(记住,这些拷机测试是使用仅返回一个值的规则,在分配后没有进行实际计算。)

然而,在有很多复杂规则的情形下留意这些开销的大小是很有意思的,而这些规则是人们都宁愿不用手动调整来处理。另一方面,实际上做这些对比会很不爽,因为就其定义而言它将是你不愿手写的东西。:)所以,或许有的人有一些他已经写好的包含复杂规则的东西,然后我们就可以作为泛型函数进行重新编码来看看它运行得如何。

 

(原文链接网址:http://dirtsimple.org/2005/01/torture-testing-generic-function.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值