优化的意外惊喜 (二)

 

Sunday, February 06, 2005

Optimization Surprises

“然而,这仅是我的第一“惊”。下一步我将把新的基类和替代闭包的类引入Pyrex中,因为他们不再有不支持的结构。然后便运行性能测试程序,结果是其性能比我去掉闭包后产生的损失一样十分的糟糕!”

现在,我已经知道了用Pyrex简单编译一个Python函数或类将不会使它更快。Pyrex不能真正的生成十分高效的代码,由于其重点放在与C代码的接口兼容上面而非成为一个有优化功能的Python编译器。Pyrex产生很多额外的incref/decref代码,实际执行时它在查找globalsbuiltins方面要比Python解释器效率差一些。由于它也对non-C变量的类型一无所知,故使用一般的API调用,在这里的一些特殊调用可能会更加高效。例如,如果你在编写规范的C代码并有一个tuple,,你可以用一些特定的tuple宏和整数偏移量来快速的访问各项。(在这里你可以使用PySequence_GetItem,这样就无须先将整数转化成一个Python对象了。)

然而Pyrex对此一无所知,所以它仅使用最安全的(也是最慢的)API调用,比如当你要使用数字时,就需要频繁创建新的整型对象。同样,Python解释器的eval loop也有很多内嵌的类型检查优化机制。例如,当你执行两个数相加时,解释器并非是简单的调用PyNumber_Add。它先要检查相加的两个数是否都是整数,能否使用内联来加快执行速度。当Pyrex试图通过类型检查(typechecking)执行这些操作时将生成大量的代码,因此它无法对这种情况进行优化。

在过去使用Pyrex过程中遭遇了这些问题后,时间的增加也就不足为奇了。但我期望它能集中解决一下代码生成方面一些相对明显的缺陷,通过用特定的Python/C API调用能轻松取代泛型Pythongeneric Python)。我注意到的第一件事就是,在关键路径上执行的类型检查是对全局变量的查找。我因此对其进行了优化,使其占负载的比重大一些。然而收效甚微。

所以我开始复查所生成的C代码,看问题是否出在别的地方。Pyrex使用PyObject_Cmp进行了数字上的比较。我便声明所有变量为整型,但并没有什么显著的改观。经过与生成代码最后一番折腾后,我放弃了。我明白了这些代码用C写会执行得更快些,但以我的能力还不能使Pyrex做到这一点。

当然,这些技术上的问题基本等同于编写C代码,但Python的语法和一连串其他的机制将会让你不堪重负。这些机制通常是有用的,因为它起到为你正确地执行引用计数,解决对垃圾回收的支持等作用。然而,当你试图调用Python/C API函数以使用更多高效的特定类型的API时,它却成了你的障碍。所以,我若打算解决该优化问题的话,也许不得不要放弃Pyrex的安全性而直接用C进行编码了。

尽管让人觉得可惜,因为若Pyrex成为一个Python编译器,它将真的cool极了,你可以声明类似listtuplePython类型,且当你使用len()aList[offset]时它将把它们转换为正确的API调用。而要是应用Guido’s类型声明的语法的话那就就更cool了,该语法对我来说要比Pyrex丑陋的cdef的声明和创建好用的多。

当然,Pyrex作为很有价值的一项开发工作实现了通过C代码能够很容易的访问Pythonic wrappers。但在这一点上,我所期望的是能够生成特殊化类型(type-specializing)代码一个实际的Python编译器。我以为Psyco也许会帮很大忙,它将提高系统其他很多部件的速度。然而,出于比较目的依靠它似乎显得不太公平。如果我在泛型函数上运行Psyco,我还得以手动调整(hand-tuned)版本来运行它,所以,其相对速度还不是一样吗?实际上,Psyco对手动调整版本会有多的处理,而比较(comparison)的效率会变得更差。换个角度说,降低负载就是降低负载(decreased overhead is decreased overhead)。

从长远来看,我希望写一个Python编译器,完美地包含CJavabackends,包含包括debugger calls的选项(这样就可以用Python调试器对生成的代码进行步进(step)了),以及包含类似特殊化类型C API对外部类型的使用和支持一样对闭包和生成器(generators)的完全支持的功能。我已经做了不少有关这样一个大家伙的架构方面的设想,并得出了结论,即它的实现将主要包含对冗长(tedious)事务前台和后台的实现。这听来有些显而易见,但我的意思是这不仅仅是个架构问题。你需要能理解C模块定义和类定义的结构以及函数等方面的后台对象。而前台需要泛型函数来匹配AST节点模式并告知后台所生成的内容。这一冗长的部分定义了所有后台代码生成模式和所有前台匹配模式。

真实地“架构”部分只有类型系统。虽然为了达到目的只要能够让例程执行更快我就会向一个例程连续地声明类型,然而类型系统应能懂得表达式类型、管理导入类型并进行一些有限的类型推理。我不想按部就班地仅对Python代码、关键性能和与其它语言接口部分的代码进行编译。

但现在在今年PyCon以前这个冗长部分和框架部分我都还没有时间做,所以估计在这方面我还要吃点老本,姑且满足于9.8微秒了。更确切的说,负载仅为9秒左右,因为手动调整版从不足.8微妙到.9微妙的执行时间对于满足一个实验性泛型函数来说其执行负载并不是很大,况且泛型函数写起来要比其对应的手动调整版简单三倍。

泛型函数主要好处是使你不用去想几十或几百个规则是如何交互的,并能从分布在你代码库中的规则中模块化构造函数。作为一个处于优化中的编译器,也许速度不及于你所能够用手编写的代码,但可能比你将会实际手写的要快。换句话说,其速度快的足以让你能把注意力放在其他地方。

P.S. 我还有没有更多在Python关键路径上有关性能方面要写的东西了?实际上,应该是有的。但比起我为速度改进所必须要做的工作也许显得微不足道。我可以在例程内部复制例程的内容,这样就删掉了一个使用程序计数器的状态变量。这将减少两个局部变量赋值语句和一个快速路径(fast path)上的try/finally块。我只是不确定其收益是否值得在一个已有31行之长的函数中重复14行代码。从另一方面来说,Pyrex版本应更加具有重复性(duplication),因此也许我无须太在意了。

更新:我试过了,重复不是那么糟糕,它在快速路径上又用了.4微秒。

教训:你在Python中几乎总可以做点事情来实现加速!)(完)

 

(原文链接网址:http://dirtsimple.org/2005/02/optimization-surprises.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值