- 内容提要
- 前言
- 作者简介
- 封面简介
- 第1章 理解高性能 Python
- 第2章 通过性能分析找到瓶颈
- 2.1 高效地分析性能
- 2.2 Julia 集合的介绍
- 2.3 计算完整的 Julia 集合
- 2.4 计时的简单方法——打印和修饰
- 2.5 用 UNIX 的 time 命令进行简单的计时
- 2.6 使用 cProfile 模块
- 2.7 用 runsnakerun 对 cProfile 的输出进行可视化
- 2.8 用 line_profiler 进行逐行分析
- 2.9 用 memory_profiler 诊断内存的用量
- 2.10 用 heapy 调查堆上的对象
- 2.11 用 dowser 实时画出变量的实例
- 2.12 用 dis 模块检查 CPython 字节码
- 2.13 在优化期间进行单元测试保持代码的正确性
- 2.14 确保性能分析成功的策略
- 2.15 小结
- 第3章 列表和元组
- 第4章 字典和集合
- 第5章 迭代器和生成器
- 第6章 矩阵和矢量计算
- 第7章 编译成 C
- 第8章 并发
- 第9章 multiprocessing 模块
- 第10章 集群和工作队列
- 第11章 使用更少的 RAM
- 第12章 现场教训
12.5 PyPy 促成了成功的 Web 和数据处理系统
Marko Tasic ( https://github.com/mtasic85)
PyPy是Python的一种实现。因为我早期有使用PyPy的丰富经验,所以我选择在适用的每处地方都使用它。我从速度关键的小规模玩具类型的项目一直到中等规模的项目都使用过它。我使用它的第一个项目是实现一个协议,我们所实现的协议是Modbus和DNP3。之后,我使用它来实现一个压缩算法,每一个人都惊诧于它的速度。如果我回忆准确的话,我使用的在产品中的第一个版本是PyPy 1.2,具有开箱即用的JIT。到PyPy的1.4版本之前,我们确信它就是我们所有项目的未来,因为许多错误得到了修复,而且速度增加得越来越快。我们好奇于只是通过把PyPy更新到下个版本,简单的案例就加快了2到3倍。
我将解释两个独立但是深度相关的项目,彼此共享了90%相同的代码,但为了让解释易于接受,我把它们俩统称为“项目”。
该项目就是创建一个系统来收集报纸、杂志和博客,在需要的地方应用OCR(光学字符识别),把它们进行分类、翻译,应用情感分析,分析文档结构,并为以后的搜索来做索引。用户能够以任意一种支持的语言来搜索关键词并检索出和索引文档相关的信息。搜索是跨语言的,这样用户就可以用英语来写并以法语来得到结果。此外,用户将从文档页上接收到高亮的文章和关键词,具有关于空间占用和出版价格方面的信息。更高级的使用场景将是报告生成,用户能够看到结果的制表视图,是有关于任何特定的公司在被监控的报纸、杂志和博客上所做广告花费的具体信息。除了广告之外,也能“猜测”一篇文章是付费的还是客观的,并决定它的基调。
12.5.1 先决条件
显然,PyPy是我们所偏爱的Python实现。我们使用Cassendra和Elasticsearch作为数据库。缓存服务器使用了Redis。我们使用Celery作为一个分布式的任务队列(工作者),使用RabbitMQ作为它的经纪人。结果保存在Redis的后端。以后,Celery更加专门地使用Redis来作经纪人和后端。所使用的OCR引擎是Tesseract。所用的语言翻译引擎和服务器是Moses。我们使用Scrapy来爬取网站。对于整个系统的分布式锁,我们使用了一个ZooKeeper服务器,但是一开始使用的是Redis。Web应用基于优秀的Flask Web框架以及它的许多扩展,例如Flask-Login、Flask-Principle等。Flask应用由每台Web服务器上的Gunicorn和Tornado所承载,nginx用来作为Web服务器的反向代理。代码的其余部分由我们自己来写,是运行在PyPy之上的纯粹的Python。
整个项目搭建于公司内部的OpenStack私有云上,并取决于需求,执行了100到1000个ArchLinux实例,能够在线动态调整。整个系统每6到12个月消耗200 TB的存储,取决于所提到的需求。除了OCR和翻译之外,所有的处理由我们的Python代码来完成。
12.5.2 数据库
我们为Cassandra、Elasticsearch和Redis开发了具有统一模型的类的Python包。它是一个简单的ORM(对象关系映射),在许多条记录要从数据库来获取的情况下, 把每样东西映射成一个字典或者字典的列表。
既然Cassandra 1.2不支持在索引上做复杂的查询,我们用类似join的查询来支持它们。无论如何,我们允许小规模数据集上的复杂查询(直到4GB为止),因为许多东西必须要放在内存中处理。PyPy运行在那些CPython甚至无法把数据装载进内存的场景下,多亏了它应用同构列表的策略,从而使得它们在内存中更加紧凑。PyPy的另一个好处就是它的JIT编译在发生数据操作或分析的循环中开始运转。我们以这样一种方式来写代码,那就是类型在循环内部保持静态,因为在那里JIT编译的代码尤其良好。
Elasticsearch被用来做索引以及文档的快速检索。当涉及查询复杂性时,它非常灵活,这样我们使用它就不会产生主要的问题。我们拥有的其中一个问题和文档更新有关,它不是设计用来快速修改文档的,这样我们不得不把那部分迁移到Cassendra。另一个限制和侧面(facet)以及数据库实例所需要的内存有关,但是通过产生更多更小的查询,然后手动操纵在Celery工作者中的数据,问题得到了解决。在PyPy和被用来与Elasticsearch服务器池做交互的PyES库之间没有浮现出主要的问题。
12.5.3 Web应用
就如上面所提到的那样,我们使用Flask框架以及它的第三方扩展。初始阶段,我们用Django来开始所有的工作,但是由于需求的快速改变,我们切换到了Flask。这并不意味着Flask比Django更好,它对我们来说只是用Flask比用Django更容易来跟踪代码,因为它的项目布局非常灵活。Gunicorn被用作一个WSGI(Web服务器网关接口)的HTTP服务器,它的IO循环由Tornado来执行。这允许我们让每个Web服务器达到100个并发连接。这比所期望的要低,因为许多用户查询可能花费较长的时间——用户的请求产生了许多分析,数据以用户交互的方式来返回。
初始阶段,Web应用依赖于Python映像库(PIL)来做文章和单词的高亮。我们一起使用PIL库和PyPy时发生了问题,因为那时PIL有许多内存泄漏。接着我们切换到了Pillow,它维护的频率更加高。最后,我们通过subprocess模块写出了与GraphicsMagick做交互的库。
PyPy运行良好,结果和CPython兼容。这是因为通常Web应用是IO密集型的。无论如何,随着PyPy中STM的开发,我们希望不久之后就有在多核实例层面上的可扩展的事件处理。
12.5.4 OCR和翻译
我们为Tesseract和Moses写了纯粹的Python库,因为我们在使用依赖于CPython API的扩展时发生了问题。PyPy在使用CPyExt时对CPython API具有良好的支持,但是我们想要对藏在表面下的所发生的事情具有更多的控制力。结果就是,我们制作了一个兼容PyPy的解决方案,具有比在CPython上运行稍微快一点的代码。它没有更快的原因就是大多数处理发生于Tesseract和Moses的C/C++代码中。我们只能加速输出处理以及Python结构文档的构建。在这个阶段没有主要的PyPy兼容性问题。
12.5.5 任务分发和工作者
Celery带给我们在后台运行许多任务的力量。典型的任务是OCR、翻译、分析等。所有的事情都能用Hadoop的MapReduce来做,但是我们选择了Celery,因为我们知道项目需求可能经常变更。
我们有大概20个工作者,每个工作者有10到20个函数。几乎所有的函数都有循环,或者有许多嵌套的循环。我们小心地让类型保持静态,这样JIT编译器就能显身手了。最后的结果就是以2到5倍的加速超过了CPython。我们没有得到更好的速度提升的原因是我们的循环相对较小,在2万到10万次迭代之间。在有些我们必须在单词层面上做分析的情况下,我们具有超过1百万次迭代,这就是我们得到超过10倍的速度提升的地方。
12.5.6 结论
PyPy对于每一个依赖于可读、可维护的大型源码的执行速度的纯Python项目而言是一个优秀的选择。我们发现PyPy也十分稳定。我们的所有程序都是长期运行的,使用静态和/或在数据结构内部的同构类型,这样JIT就能显身手了。当我们在CPython上测试整体项目时,结果并没有让我们吃惊:我们用PyPy比CPython大概具有2倍的速度提升。在我们的客户眼里,这意味着以相同的价格得到了2倍更好的性能。除了PyPy迄今为止带给我们的所有好处,我们希望它的软件事务内存(STM)的实现将带给我们可扩展地来并行执行Python代码。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论