PyParallel

Python的快速并行版本
授权协议 Python
开发语言 Python
所属分类 开发工具、 Python开发工具
软件类型 开源软件
地区 不详
投 递 者 欧阳智志
操作系统 跨平台
开源组织
适用人群 未知
 软件概览

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)

介绍内容来自 InfoQ

  • 软件介绍 PyParallel是Trent Nelson发起的一个研究项目,其目标是以提供高性能异步支持的方式将WindowsI/O完成端口(IOCP)的强大功能移到Python中。 Python的异步支持多少有点问题。它是围绕Unix/Linux的异步、非阻塞I/O理念设计的。线程会持续轮询进入的数据,然后相应进行分 发。尽管Linux针对该模式进行了调优,但在Windows机器上,这种处理方式

 相关资料
  • B2B版本集成 B2B的用户,初始化方法,打开聊窗方法和登录方法的逻辑如下 : 在每次点击打开聊窗的时候,都要调用初始化方法初始化SDK,登录方法登录与注销和打开聊窗打开聊窗的方法,调用顺序不能变。

  • 主要内容:UnionFind2.java 文件代码:对于一组数据,并查集主要支持两个动作: union(p,q) - 将 p 和 q 两个元素连接起来。 find(p) - 查询 p 元素在哪个集合中。 isConnected(p,q) - 查看 p 和 q 两个元素是否相连接在一起。 在上一小节中,我们用 id 数组的形式表示并查集,实际操作过程中查找的时间复杂度为 O(1),但连接效率并不高。 本小节,我们将用另外一种方式实现并查集。把每一个元

  • 主要内容:UnionFind1.java 文件代码:本小节基于上一小节并查集的结构介绍基础操作,查询和合并和判断是否连接。 查询元素所在的集合编号,直接返回 id 数组值,O(1) 的时间复杂度。 ... private int find ( int p ) {     assert p >= 0 && p < count ;     return id [p ] ; } ... 合并元素 p 和元素 q 所属的集合, 合并过程需要遍历一遍所有元素

  • 我在程序(计时器类)中使用scheduleAtFixedRate方法。它每秒钟运行一次,但有时这种方法会变得非常快(每秒执行3-4次)。 然而,我在网上做了一些研究,发现了这个: 复制自android开发者页面: 对于固定速率执行,任务每次连续运行的开始时间都是计划的,而不考虑上一次运行的时间。如果延迟阻止计时器按时启动任务,则这可能会导致一系列串接运行(一个接一个地启动)。 我需要固定的计时器。

  • 本文向大家介绍Java版快速排序[不稳定]相关面试题,主要包含被问及Java版快速排序[不稳定]时的应答技巧和注意事项,需要的朋友参考一下 原理:分治+递归 复杂度:O(nlgn) - O(nlgn) - O(n^2) - O(1)[平均 - 最好 - 最坏 - 空间复杂度] 栈空间0(lgn) - O(n)

  • 问题内容: 我正在尝试使用Java并行化算法。我从合并排序开始,然后在这个问题上发表了自己的尝试。我修改后的尝试在下面的代码中,在这里我尝试并行化快速排序。 我的多线程实现或方法中是否有菜鸟错误?如果不是,我是否应该期望对决核上的顺序算法和并行算法之间的速度提高超过32%(请参阅底部的时序)? 这是多线程算法: 这是我的开始方式: 我针对Arrays.sort和类似的顺序快速排序算法进行了测试。以

  • 问题内容: [http://jsperf.com/optimized-mergesort-versus- quicksort][1] 为什么这个半缓冲区合并排序的工作速度与quicksort一样快? QuickSort是: 就地虽然会占用递归(堆栈空间) 缓存友好 这一半缓冲区合并排序: 使用Buffer进行合并。 使用递归。 进行较少的比较。 我的问题是,在这种情况下,为什么半缓冲区合并排序与Q

  • #产品2023笔面经# (1)自我介绍:挑重点介绍 重点突出对于岗位的理解 (2)快速极速版地推,如何进行地推,有哪些模式 (3)实习中有没有什么比较有代表性的项目跟导师,具体的一个项目经验,导师指导了你什么,结果怎么样? (4)为什么没有考虑做一个程序员,而是做一个产品? (5)反问