利用Psyco提升Python运行速度


Posted in Python onDecember 24, 2014

Psyco 是严格地在 Python 运行时进行操作的。也就是说,Python 源代码是通过 python 命令编译成字节码的,所用的方式和以前完全相同(除了为调用 Psyco 而添加的几个 import 语句和函数调用)。但是当 Python 解释器运行应用程序时,Psyco 会不时地检查,看是否能用一些专门的机器代码去替换常规的 Python 字节码操作。这种专门的编译和 Java 即时编译器所进行的操作非常类似(一般地说,至少是这样),并且是特定于体系结构的。到现在为止,Psyco 只可用于 i386 CPU 体系结构。Psyco 的妙处在于可以使用您一直在编写的 Python 代码(完全一样!),却可以让它运行得更快。

Psyco 是如何工作的

要完全理解 Psyco,您可能需要很好地掌握 Python 解释器的 eval_frame() 函数和 i386 汇编语言。遗憾的是,我自己不能对其中任何一项发表专家性的意见 - 但是我想我可以大致不差地概述 Psyco。
在常规的 Python 中,eval_frame() 函数是 Python 解释器的内循环。eval_frame() 函数主要察看执行上下文中的当前字节码,并将控制向外切换到一个适合实现该字节码的函数。支持函数将做什么的具体细节通常取决于保存在内存中的各种 Python 对象的状态。简单点说,添加 Python 对象“2”和“3”和添加对象“5”和“6”会产生不同的结果,但是这两个操作都以类似的方式分派。
Psyco 用复合求值单元替代 eval_frame() 函数。Psyco 有几种方法可以用来改进 Python 所进行的操作。首先,Psyco 将操作编译成有点优化的机器码;由于机器码需要完成的工作和 Python 的分派函数所要做的事一样,所以其本身只有些许改进。而且,Psyco 编译中的“专门的”内容不仅仅是对 Python 字节码的选择,Psyco 也要对执行上下文中已知的变量值进行专门化。例如,在类似于下面的代码中,变量 x 在循环持续时间内是可知的:

x = 5

l = []

for i in range(1000):

l.append(x*i)

该段代码的优化版本不需要用“x 变量/对象的内容”乘每个 i,与之相比,简单地用 5 乘以每个 i 所用的开销较少,省略了查找/间接引用这一步。
除为小型操作创建特定于 i386 的代码之外,Psyco 还高速缓存这个已编译的机器码以备今后重用。如果 Psyco 能够识别出特定的操作和早先所执行的(“专门化的”)操作一样,那么,它就能依靠这个高速缓存的代码而不需要再次编译代码段。这样就节省了一些时间。
但是,Psyco 中真正省时的原因在于 Psyco 将操作分成三个不同的级别。对于 Psyco,有“运行时”、“编译时”和“虚拟时”变量。Psyco 根据需要提高和降低变量的级别。运行时变量只是常规 Python 解释器处理的原始字节码和对象结构。一旦 Psyco 将操作编译成机器码,那么编译时变量就会在机器寄存器和可直接访问的内存位置中表示。
最有意思的级别是虚拟时变量。在内部,一个 Python 变量就是一个有许多成员组成的完整结构 - 即使当对象只代表一个整数时也是如此。Psyco 虚拟时变量代表了需要时可能会被构建的 Python 对象,但是这些对象的详细信息在它们成为 Python 对象之前是被忽略的。例如,考虑如下赋值:
x = 15 * (14 + (13 - (12 / 11)))
标准的 Python 会构建和破坏许多对象以计算这个值。构建一个完整的整数对象以保存 (12/11) 这个值;然后从临时对象的结构中“拉”出一个值并用它计算新的临时对象 (13-PyInt)。而 Psyco 跳过这些对象,只计算这些值,因为它知道“如果需要”,可以从值创建一个对象。

使用 Psyco

解释 Psyco 相对比较困难,但是使用 Psyco 就非常容易了。基本上,其全部内容就是告诉 Psyco 模块哪个函数/方法要“专门化”。任何 Python 函数和类本身的代码都不需进行更改。
有几种方法可以指定 Psyco 应该做什么。“猎枪(shotgun)”方法使得随处都可使用 Psyco 即时操作。要做到这点,把下列行置于模块顶端:

import psyco ; psyco.jit() 

from psyco.classes import *

第一行告诉 Psyco 对所有全局函数“发挥其魔力”。第二行(在 Python 2.2 及以上版本中)告诉 Psyco 对类方法执行相同的操作。为了更精确地确定 Psyco 的行为,可以使用下列命令:
psyco.bind(somefunc) # or method, class
newname = psyco.proxy(func)
第二种形式把 func 作为标准的 Python 函数,但是优化了涉及 newname 的调用。除了测试和调试之外的几乎所有的情况下,您都将使用 psyco.bind() 形式。

Psyco 的性能

尽管 Psyco 如此神奇,使用它仍然需要一点思考和测试。主要是要明白 Psyco 对于处理多次循环的块是很有用的,而且它知道如何优化涉及整数和浮点数的操作。对于非循环函数和其它类型对象的操作,Psyco 多半只会增加其分析和内部编译的开销。而且,对于含有大量函数和类的应用程序来说,在整个应用程序范围启用 Psyco,会在机器码编译和用于这一高速缓存的内存使用方面增加大量的负担。有选择性地绑定那些可以从 Psyco 的优化中获得最大收益的函数,这样会好得多。
我以十分幼稚的方式开始了我的测试过程。我仅仅考虑了我近来运行的、但还未考虑加速的应用程序。想到的第一个示例是用来将我即将出版的书稿(Text Processing in Python)转换成 LaTeX 格式的文本操作程序。该应用程序使用了一些字符串方法、一些正则表达式和一些主要由正则表达式和字符串匹配所驱动的程序逻辑。实际上将它用作 Psyco 的测试候选是很糟的选择,但是我还是使用了,就这么开始了。
第一遍测试中,我所做的就是将 psyco.jit() 添加到脚本顶端。这做起来一点都不费力。遗憾的是,结果(意料当中)很令人失望。原先脚本运行要花费 8.5 秒,经过 Psyco 的“加速”后它大概要运行 12 秒。真差劲!我猜测大概是即时编译所需的启动开销拖累了运行时间。因此接下来我试着处理一个更大的输入文件(由原来那个输入文件的多个副本组成)。这次获得了小小的成功,将运行时间从 120 秒左右减到了 110 秒。几次运行中的加速效果比较一致,但是效果都不显著。
本处理候选项的第二遍测试中。我只添加了 psyco.bind(main) 这一行,而不是添加一个总的 psyco.jit() 调用,因为 main() 函数确实要循环多次(但是仅利用了最少的整数运算)。这里的结果名义上要比前面好。这种方法将正常的运行时间削减了十分之几秒,在较大的输入版本的情况下削减了数秒钟。但是仍然没有引入瞩目的结果发生(但也没产生什么害处)。

为进行更恰当的 Psyco 测试,我搜寻出我在以前的文章里编写的一些神经网络代码(请参阅“参考资料”)。这个“代码识别器(code_recognizer)”应用程序可以经“训练”用于识别不同编程语言编写的不同 ASCII 值的可能分布情况。类似于这样的东西可能在猜测文件类型方面(比方说丢失的网络信息包)将很有用;但是,关于“训练”些什么,代码实际上完全是通用的 - 它能很容易地学会识别面孔、声音或潮汐模式。任何情况下,“代码识别器”都基于 Python 库 bpnn,Psyco 4.0 分发版也包含(以修正的形式)了该库作为测试用例。在本文中,对“代码识别器”要重点了解它做了许多浮点运算循环并花费了很长的运行时间。这里我们已经有了一个能用于 Psyco 测试的好的候选用例。
使用了一段时间后,我建立了有关 Psyco 用法的一些详细信息。对于这种只有少量类和函数的应用程序,使用即时绑定还是目标绑定没有太大区别。但最佳的结果是,通过有选择性地绑定最优化类,仍可得到几个百分点的改进。然而,更值得注意的是要理解 Psyco 绑定的作用域,这一点很重要。
code_recognizer.py 脚本包括类似于下面的这些行:

从 bpnn 导入 NN
class NN2(NN):
# customized output methods, math core inherited
也就是说,从 Psyco 的观点来看,有趣的事情在类 bpnn.NN 之中。把 psyco.jit() 或 psyco.bind(NN2) 添加到 code_recognizer.py 脚本中起不了什么作用。要使 Psyco 进行期望的优化,需要将 psyco.bind(NN) 添加到 code_recognizer.py 或者将 psyco.jit() 添加到 bpnn.py。与您可能假设的情况相反,即时优化不在创建实例时或方法运行时发生,而是在定义类的作用域内发生。另外,绑定派生类不会专门化其从其它地方继承的方法。
一旦找到适当的 Psyco 绑定的细微的详细信息,那么加速效果是相当明显的。使用参考文章中提供的相同测试用例和训练方法(500 个训练模式,1000 个训练迭代),神经网络训练时间从 2000 秒左右减到了 600 秒左右 - 提速了 3 倍多。将迭代次数降到 10,加速的倍数也成比例降低(但对神经网络的识别能力无效),迭代的中间数值也会如此变化。
我发现使用两行新代码就能将运行时间从超过半小时减到 10 分钟左右,效果非常显著。这种加速仍可能比 C 编写的类似应用程序的速度慢,而且它肯定比几个独立的 Psyco 测试用例所反映出的 100 倍加速要慢。但是这种应用程序是相当“真实的”,而且在许多环境中这些改进已经是够显著的了。

Python 相关文章推荐
Python列表推导式的使用方法
Nov 21 Python
Python环境下搭建属于自己的pip源的教程
May 05 Python
python将ansible配置转为json格式实例代码
May 15 Python
python出现"IndentationError: unexpected indent"错误解决办法
Oct 15 Python
tornado 多进程模式解析
Jan 15 Python
Python爬虫之pandas基本安装与使用方法示例
Aug 08 Python
python实现flappy bird小游戏
Dec 24 Python
Python面向对象实现一个对象调用另一个对象操作示例
Apr 08 Python
详解python中的time和datetime的常用方法
Jul 08 Python
python实现LRU热点缓存及原理
Oct 29 Python
python Matplotlib基础--如何添加文本和标注
Jan 26 Python
详解Python自动化之文件自动化处理
Jun 21 Python
Python解决鸡兔同笼问题的方法
Dec 20 #Python
Python列表计数及插入实例
Dec 17 #Python
Python二维码生成库qrcode安装和使用示例
Dec 16 #Python
Mac下Supervisor进程监控管理工具的安装与配置
Dec 16 #Python
Python 正则表达式(转义问题)
Dec 15 #Python
python正则表达式中的括号匹配问题
Dec 14 #Python
python的类方法和静态方法
Dec 13 #Python
You might like
生成php程序的php代码
2008/04/07 PHP
php实现批量压缩图片文件大小的脚本
2014/07/04 PHP
详解php中serialize()和unserialize()函数
2017/07/08 PHP
PHP+Apache环境中如何隐藏Apache版本
2017/11/24 PHP
微信公众平台开发教程②微信端分享功能图文详解
2019/04/10 PHP
window.showModalDialog使用手册
2007/01/11 Javascript
javascript 简单高效判断数据类型 系列函数 By shawl.qiu
2007/03/06 Javascript
让iframe子窗体取父窗体地址栏参数(querystring)
2009/10/13 Javascript
jQuery $.each的用法说明
2010/03/22 Javascript
JQuery里面的几种选择器 查找满足条件的元素$("#控件ID")
2011/08/23 Javascript
EasyUI中的tree用法介绍
2011/11/01 Javascript
JQuery触发radio或checkbox的change事件
2012/12/18 Javascript
js中符号转意问题示例探讨
2013/08/19 Javascript
javascript回车完美实现tab切换功能
2014/03/13 Javascript
将form表单中的元素转换成对象的方法适用表单提交
2014/05/02 Javascript
JavaScript数据库TaffyDB用法实例分析
2015/07/27 Javascript
JS实现网站菜单拖拽移位效果的方法
2015/09/24 Javascript
详解JavaScript时间格式化
2015/12/23 Javascript
jQuery延迟执行的实现方法
2016/12/21 Javascript
vue2.0全局组件之pdf详解
2017/06/26 Javascript
js实现鼠标跟随运动效果
2020/08/02 Javascript
vue 每次渲染完页面后div的滚动条保持在最底部的方法
2018/03/17 Javascript
vue swipe自定义组件实现轮播效果
2019/07/03 Javascript
编写Python爬虫抓取暴走漫画上gif图片的实例分享
2016/04/20 Python
python 含子图的gif生成时内存溢出的方法
2019/07/07 Python
Python多线程threading join和守护线程setDeamon原理详解
2020/03/18 Python
Django调用支付宝接口代码实例详解
2020/04/04 Python
python识别验证码的思路及解决方案
2020/09/13 Python
Russell Stover巧克力官方网站:美国领先的精美巧克力制造商
2016/11/27 全球购物
Everlast官网:拳击、综合格斗和健身相关的体育用品
2020/08/03 全球购物
大学毕业生自我鉴定
2013/11/05 职场文书
学校招生宣传广告词
2014/03/19 职场文书
开工仪式策划方案
2014/05/23 职场文书
代理人委托书
2014/08/01 职场文书
对外汉语专业大学生职业生涯规划范文
2014/09/13 职场文书
2015年体育教学工作总结
2015/05/20 职场文书