利用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中os和shutil模块实用方法集锦
May 13 Python
Python实现TCP协议下的端口映射功能的脚本程序示例
Jun 14 Python
Python中对象迭代与反迭代的技巧总结
Sep 17 Python
python高阶爬虫实战分析
Jul 29 Python
python算法题 链表反转详解
Jul 02 Python
Python异常原理及异常捕捉实现过程解析
Mar 25 Python
python高级特性简介
Aug 13 Python
如何完美的建立一个python项目
Oct 09 Python
关于Python不换行输出和不换行输出end=““不显示的问题(亲测已解决)
Oct 27 Python
用python查找统一局域网下ip对应的mac地址
Jan 13 Python
Python实现批量自动整理文件
Mar 16 Python
python神经网络 使用Keras构建RNN训练
May 04 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
Windows下部署Apache+PHP+MySQL运行环境实战
2012/08/31 PHP
php正则表达式使用的详细介绍
2013/04/27 PHP
PHP实现的常规正则验证helper公共类完整实例
2017/04/27 PHP
js 数组操作代码集锦
2009/04/28 Javascript
javascript控制swfObject应用介绍
2012/11/29 Javascript
js自动下载文件到本地的实现代码
2013/04/28 Javascript
jQuery不兼容input的change事件问题解决过程
2014/12/05 Javascript
javascript内置对象操作详解
2015/02/04 Javascript
逐一介绍Jquery data()、Jquery stop()、jquery delay()函数(详)
2015/11/04 Javascript
JS无缝滚动效果实现方法分析
2016/12/21 Javascript
jQuery轻松实现无缝轮播效果
2017/03/22 jQuery
Vue2.0 slot分发内容与props验证的方法
2017/12/12 Javascript
深入浅析Vue.js计算属性和侦听器
2018/05/05 Javascript
JavaScript 对引擎、运行时、调用堆栈的概述理解
2018/10/22 Javascript
node.js express框架实现文件上传与下载功能实例详解
2019/10/15 Javascript
[04:11]2014DOTA2国际邀请赛 CIS遗憾出局梦想不灭
2014/07/09 DOTA
[26:24]完美副总裁、DOTA2负责人蔡玮专访:电竞如人生
2014/09/11 DOTA
[26:40]DOTA2上海特级锦标赛A组资格赛#1 Secret VS MVP.Phx第一局
2016/02/25 DOTA
[01:06:54]DOTA2-DPC中国联赛 正赛 SAG vs DLG BO3 第二场 2月28日
2021/03/11 DOTA
Python linecache.getline()读取文件中特定一行的脚本
2008/09/06 Python
python中二维阵列的变换实例
2014/10/09 Python
浅谈Python的Django框架中的缓存控制
2015/07/24 Python
web.py 十分钟创建简易博客实现代码
2016/04/22 Python
django搭建项目配置环境和创建表过程详解
2019/07/22 Python
将SVG图引入到HTML页面的实现
2019/09/20 HTML / CSS
澳大利亚自然和有机的健康美容产品一站式商店:Ziani Beauty
2017/12/28 全球购物
文史专业毕业生自荐信
2013/11/17 职场文书
自动化专业大学生职业生涯规划范文:爱拚才会赢
2014/09/12 职场文书
2015元旦文艺汇演主持稿(开场白+结束语)
2014/12/14 职场文书
毕业典礼致辞
2015/07/29 职场文书
春节随笔
2015/08/15 职场文书
小学生组织委员竞选稿
2015/11/21 职场文书
简短的人生哲理(38句)
2019/08/13 职场文书
Python趣味挑战之实现简易版音乐播放器
2021/05/28 Python
人民币符号
2022/02/17 杂记
Django数据库(SQlite)基本入门使用教程
2022/07/07 Python