写点什么

PyParallel:Python 的一个快速并行版本

2014 年 3 月 03 日

PyParallel 是 Trent Nelson 发起的一个研究项目,其目标是以提供高性能异步支持的方式将 Windows I/O 完成端口(IOCP)的强大功能移到Python 中。

Python 的异步支持多少有点问题。它是围绕 Unix/Linux 的异步、非阻塞 I/O 理念设计的。线程会持续轮询进入的数据,然后相应进行分发。尽管 Linux 针对该模式进行了调优,但在 Windows 机器上,这种处理方式是性能的灾难。将数据从轮询线程复制到真正处理任务的线程,非常昂贵。

PyParallel 带来的就是使用了原生 IOCP 的真正的异步。在 IOCP 模型下,每个核有一个线程。每个线程负责处理完成 I/O 请求(比如,从网卡复制数据)和执行请求关联的应用层回调。

只有这一点尚不足以横向扩展 Python;还需要解决 GIL(Global Interpreter Lock,全局解释器锁)带来的问题。否则我们仍然被限制于每次执行一个线程。使用细粒度的锁替换 GIL,结果会更糟糕;像 PyPy 中的软件事务内存往往最终会导致 1 个线程继续推进,N-1 个线程持续重试的问题。所以我们需要别的解决方案。

对 PyParallel 团队来说,这个解决方案就是不允许自由创建线程。换言之,应用不能随意创建新线程。相反,并行操作被绑定到异步回调机制和并行上下文(parallel context)的概念。

在深入并行上下文之前,我们先反过来看一下。当并行上下文不运行的时候,主线程会运行;反之亦然。主线程就是你进行正常的 Python 开发所考虑的东西。主线程持有 GIL,对全局命名空间具有完全的访问权限。

相反,并行上下文对全局命名空间只能进行只读访问。这意味着,开发者需要注意某个事物是主线程对象还是并行上下文对象。处理过套间线程模型(apartment threading models)的 COM 程序员对其中的痛苦是再清楚不过了。

对于非 I/O 任务,主线程使用 async.submit_work 函数对任务进行排队,然后使用 async.run 函数切换到并行上下文。这会挂起主线程,并激活并行解释器。多个并行上下文可以同时运行,由 Windows 操作系统处理线程池的管理。

与 GIL 并行

有一点非常重要,需要注意一下,这里并没有创建多个进程。尽管多进程技术在 Python 开发中很常用,但 PyParallel 将所有东西都放在了一个进程中,以减少跨进程通信的代价。这通常是不允许的,因为 CPython 解释器不是线程安全的,这包括:

  • 全局静态数据会频繁用到
  • 引用计数不是原子的
  • 对象没有用锁保护
  • 垃圾收集不是线程安全的
  • 拘留字符串(Interned string)的创建不是线程安全的
  • bucket 内存分配器不是线程安全的
  • arena 内存分配器不是线程安全的

Greg Stein 曾尝试通过向 Python 1.4 中加入细粒度的锁来解决该问题,但是在单线程代码中,他的项目导致速度下降 40%,所以被拒绝了。因此 Trent Nelson 决定采用不同的方案。在主线程中,GIL 和原来一样运作。但是当在并行上下文中运行的时候,会使用线程安全的替换方案代替核心函数来运行。

Trent 的方案的代价是 0.01%,比 Greg 的方案好得多。至于 PyPy 的软件事务内存,对单线程模型而言,其代价大概是 200~500%。

该设计的一个有趣的地方是,在并行上下文中运行的代码,当要从全局命名空间中的对象中读取数据时,不需要获得锁。不过它只有读的能力。

PyParallel 没有垃圾收集器

为了避免处理内存分配、存取和垃圾收集相关的锁,PyParallel 使用了一种无共享模式。每个并行上下文都有自己的堆,没有垃圾收集器。就是这样,没有与并行上下文关联的垃圾收集器。因此实际上是这样:

  • 内存分配使用一个简单的块分配器完成。每次内存分配只是调整一下指针。
  • 根据需要分配 4K 或 2MB 大小的新页面,这由并行上下文的大页面设置控制。
  • 不使用引用计数。
  • 当并行上下文结束时,与它关联的所有页面同时释放。

这种设计避免了线程安全的垃圾收集器或线程安全的引用计数的代价。另外,它支持前面提到的块分配器,这可能是最快的内存分配方式了。

PyParallel 团队认为这种设计可以成功,因为并行上下文意在支持生命周期较短、范围较为有限的应用。一个很好的例子是并行排序算法或 Web 页面请求处理程序。

为使这种设计正常工作,在并行上下文中创建的对象不能逃逸到主线程中。这是通过只读访问全局命名空间这一限制来保证的。

引用计数与主线程对象

这时我们有两类对象:主线程对象和并行上下文对象。主线程对象会使用引用计数进行管理,因为它们在某一时刻需要回收。但并行上下文对象没有使用引用计数。但如果两类对象有相互作用,那该如何处理呢?

因为并行上下文对象不能修改主线程对象,所以它就不能改变主线程对象的引用计数。但是又因为,当并行上下文运行时,主线程的垃圾收集器无法运行,所以这就不是问题了。当主线程的垃圾收集启动时,所有的并行上下文对象都已经销毁,所以没有从它们指回到主线程对象的东西。

这一切的最终结果是,在并行上下文中执行的代码通常比在主线程中执行的代码快。

并行上下文与异步 I/O

当考虑异步 I/O 调用时,上面讨论的内存模型就有问题了。这些调用会使并行上下文存活的时间比系统设计的存活时间长得多。像 Web 页面请求处理程序这样的情况,调用的数目是没有限制的。

为处理这一问题,Trent 加入了快照(snapshot)的概念。当一个异步回调开始时,就为并行上下文的内存保存一个快照。在回调的最后,所有改变都会被恢复,新分配的内存也会释放。这比较适合无状态应用,比如 Web 页面请求处理程序,但是对需要保持数据的应用就不合适了。

快照最多可以嵌套 64 层深,但是 Trent 没有详细描述其处理细节。

平衡同步与异步 I/O

异步 I/O 不是免费的午餐。如果想获得最大的吞吐量,同时保持最低的延迟,同步 I/O 实际上更快。但只有并发请求数比可用的核数少时,这才成立。

因为开发者不一定会随时了解负载情况,所以指望他决策可能是不合理的。因此 PyParallel 提供了一个套接字(socket)库,可以在运行时根据活动的客户端数做出决策。只要活动客户端的数目比核数少,就执行同步代码。如果客户端数超过了核数,该库会自动切换到异步模式。不管哪种方式,这里的修改对应用都是透明的。

异步 HTTP 服务器

作为概念验证的一部分,PyParallel 还提供了一个异步 HTTP 服务器,它基于 stdlib 中的 SimpleHttpServer 。它的一个主要特性是支持 Win32 函数 TransmitFile ,允许数据直接从文件缓存发送到套接字。

未来计划

未来,Trent 希望继续改进内存模型,准备通过引入一组新的互锁(interlocked)数据类型和使用上下文管理器控制内存分配协议来实现。

Numba 的集成也正在进行之中。想法是异步启动 Numba,当 Numba 完成时,将 CPython 交换出去,换为本机生成的代码。

另一个计划中的变化是支持可插拔的 PxSocket_IOLoop 端点。这就允许不同的协议以流水线方式链到一起。在可能的情况下,他想使用管道代替套接字,因为这可以减少在多个步骤之间所要复制的必要数据的量。

更多信息,可以查看 Trent Nelson 的演讲: PyParallel - How We Removed the GIL and Exploited All Cores (Without Needing to Remove the GIL at all)

关于作者

Jonathan Allen从 2006 年开始就为 InfoQ 编写新闻报道了, 目前他是.NET 板块的主编。如果你有兴趣为 InfoQ 编写新闻和技术文章,请通过jonathan@infoq.com 联系他。

查看英文原文: PyParallel: A Fast Parallel Version of Python

2014 年 3 月 03 日 08:084093
用户头像
臧秀涛 极客邦科技技术会议负责人

发布了 300 篇内容, 共 114.2 次阅读, 收获喜欢 21 次。

关注

评论

发布
暂无评论
发现更多内容

年轻人不讲武德不仅白piao接口测试知识还白piao接口测试工具会员

测试人生路

接口测试

云原生2.0时代下,DevOps实践如何才能更加高效敏捷?

华为云开发者社区

云计算 数字化 华为云

小学妹问我:如何利用可视化工具排查问题?

田维常

可视化

高性能利器!华为云MRS ClickHouse重磅推出!

华为云开发者社区

数据库 Clickhouse MRS

《垃圾回收的算法与实现》.pdf

田维常

垃圾回收

太赞了!腾讯T3-3架构师整理了5000页的Java学习手册免费开放下载

Java架构之路

Java 程序员 架构 面试 编程语言

开个交易所需要多少费用?数字货币交易所搭建

13530558032

什么是低代码(Low-Code)?

应用研发平台EMAS

工具 研发效能 低代码 开发 代码

IoT企业物联网平台,从设备端到云端业务系统全链路开发实战

IoT物联网技术

阿里云 最佳实践 物联网 IoT

#不吐不快# 三观很正的Boss,你遇到过么?

flyer0126

职场成长 奇葩的经历 不吐不快

圆通快递回应内鬼泄露用户信息:严打数据倒卖灰色产业

石头IT视角

SpringBoot:整合Swagger3.0与RESTful接口整合返回值(2020最新最易懂)

比伯

Java 编程 架构 面试 计算机

胡继晔:中国应建区块链行业准入制度

CECBC区块链专委会

区块链 金融 数字经济

synchronized 到底该不该用

古时的风筝

Java synchronized

【涂鸦物联网足迹】涂鸦云平台消息服务—顺带Pulsar简单介绍

IoT云工坊

人工智能 物联网 云服务 Apache Pulsar 云平台

区块链数字货币钱包源码价格,区块链多币种钱包

13530558032

交易所做市机器人,自动跑K线机器人,市值管理

WX13823153201

DàYé的CTO姗姗学步路

曲水流觞TechRill

管理 CTO

科普干货|漫谈鸿蒙LiteOS-M与HUAWEI LiteOS内核的几大不同

华为云开发者社区

华为 鸿蒙 IoT

区块链,音乐,流媒体和版税

CECBC区块链专委会

区块链 艺术

一瞬间让我秒变“快男”!腾讯内部强推Java性能优化手册,快了不止一点点。

Java架构追梦

Java 架构 jdk 面试 性能优化

一次 Java 进程 OOM 的排查分析(glibc 篇)

996小迁

Java 编程 架构 面试 计算机

区块链在债券市场如何应用

CECBC区块链专委会

区块链 债券

基于SpringBoot、SpringCloud、Docker微服务架构实战,资源分享

Java架构之路

Java 程序员 架构 面试 编程语言

大四女学霸社招竟成功签约字节跳动,拿下30万年薪?

Java架构师迁哥

分布式事务太繁琐?官方推荐Atomikos,5分钟帮你搞定

互联网应用架构

分布式事务 springboot

云算力矿机源码价格,区块链挖矿平台开发

13530558032

Forrester 最新报告:阿里云稳居领导者地位,引领云原生开发浪潮

阿里巴巴云原生

阿里云 Serverless Kubernetes 容器 云原生

#不吐不快# CV千千条,修改最重要。代码不规范,伙伴两行泪!

程序员小航

奇葩的经历 不吐不快

收藏!数据建模最全知识体系解读

华为云开发者社区

数据仓库 数据 数据建模

天啊!怎么会有人把Spring Cloud微服务架构讲得这么透彻?

Java架构之路

Java 程序员 架构 面试 编程语言

NLP领域的2020年大事记及2021展望

NLP领域的2020年大事记及2021展望

PyParallel:Python的一个快速并行版本-InfoQ