平行运算

前言:

  编写Python程序时,我们可能会遭遇性能问题,即使优化了代码,程序也依然有可能运行的很慢,从而无法满足我们对执行速度的要求,目前的计算机,其cpu核心数越来越多,于是,我们可以考虑通过平行计算来提升性能,能不能把代码的总计算量分配到多个独立的任务之中,并在多个CPU核心上面同时运行这些任务呢?

  很遗憾,Python的全局解释器锁(GIL)使得我们没有办法用线程实现真正的平行计算,因此,上面那个想法行不通。另外一种常见的建议,是用C语言把程序中对性能要求较高的那部分代码,改为扩展模块,由于C语言更贴近硬件,所以运行的比Python快,一旦运行速度达到要求,我们自然就不用再考虑平行计算了,C语言扩展也可以启动并并行地运行多个原声线程,从而充分利用CPU的多个内核。Python中的C语言扩展API,有完备的文档可供查阅,这使得它成为解决性能问题的一个好办法。

  但是,用C语言重写代码,是有很大代价的,短小而易读的Python代码,会变成冗长而费解的C代码,在进行这样的移植时,必须进行大量的测试,确保移植过程中没有引入bug。然而问题在于:只把程序中的一小部分迁移到C,通常是不够的。一般来说,Python程序之所以执行得比较慢,并不是某个主要因素单独造成的,而是多个因素联合导致的,所以,要想充分利用C语言的硬件和线程优势,就必须把程序中的大量代码移植到C,而这样做,有大幅增加了测试量和风险。于是,我们应该思考一下:有没有一种更好的方式,只需要使用较少的Python代码,即可有效提升执行速度,并迅速解决复杂的计算问题。

  我们可以试着通过内置的concurrent.futures模块,来利用另外一个名叫multiprocessing的内置模块,从而实现这种需求,该做法会以子进程的形式,平行地运行多个解释器,从而令Python程序能够利用多核心CPU提升执行速度,由于子进程与主解释器相分离,所以它们的全局解释器锁也是相互独立的,每个紫禁城都可以完整地利用一个CPU内核,而且这些自经常,都与主进程之间有着联系,通过这条联系渠道,紫禁城可以接收主进程发过来的指令,并把计算结果返回给主进程

程序运算:

  编写运算量很大的Python程序,查找两数最大公约数,用三种不同的方式进行对比

① 单线程

代码:

# 单线程
import time def gcd(pair):
a,b = pair
low = min(a,b)
for i in range(low,0,-1):
if a % i == 0 and b % i == 0:
return i numbers = [(89937224,53452411),(97432894,43939284),(95938272,94910833),
(7398473,47382942),(85938272,90493759)]
start = time.time()
results = list(map(gcd,numbers))
end = time.time()
print('Took %.3f secondes'%(end-start))

执行结果:

Took 22.083 secondes

多线程

代码:

# 多线程
import time
from concurrent.futures import ThreadPoolExecutor def gcd(pair):
a,b = pair
low = min(a,b)
for i in range(low,0,-1):
if a % i == 0 and b % i == 0:
return i numbers = [(89937224,53452411),(97432894,43939284),(95938272,94910833),
(7398473,47382942),(85938272,90493759)]
start = time.time()
pool = ThreadPoolExecutor(max_workers=2)
results = list(pool.map(gcd,numbers))
end = time.time()
print('Took %.3f secondes'%(end-start))

执行结果:

Took 25.338 secondes

注:用多条Python现场来改善上述程序,是没有效果的,因为全局解释器锁(GIL)使得Python无法在多个CPU核心上面平行地运行这些线程。线程启动的时候,是有一定开销的,与线程池进行通信,也会有开销,所以上面这个程序运行的比单线程版本还要满

多进程(只能linux下运行)

我们只需要改动一行代码,就可以提升整个程序的速度,把ThreadPoolExecutor换成concurrent.futures模块里的ProcessPoolExecutor,程序的速度就上去了

代码:

import time
from concurrent.futures import ProcessPoolExecutor
from multiprocessing import cpu_count def gcd(pair):
a,b = pair
low = min(a,b)
for i in range(low,0,-1):
if a % i == 0 and b % i == 0:
return i numbers = [(89937224,53452411),(97432894,43939284),(95938272,94910833),
(7398473,47382942),(85938272,90493759)]
start = time.time()
pool = ProcessPoolExecutor(max_workers=cpu_count()) # 四核
results = list(pool.map(gcd,numbers))
end = time.time()
print('Took %.3f secondes'%(end-start))

执行结果:

Took 6.816 secondes

注:果然比前面两个版本的程序执行速度快了很多

总结:

ProcessPoolExecutor类利用由multiprocessing模块所提供的底层机制,来逐步完成下列操作:

  • 把numbers列表中的每一项输入数据都传给map。
  • 用pickle模块对数据进行序列化,将其变成二进制形式。
  • 通过本地套接字(local socket),将序列化之后的数据从主解释器所在的进程,发到子解释器所在的进程。
  • 接下来,在子进程中,用pickle对二进制数据进行反序列化操作,将其还原为Python对象。
  • 引入包含gcd函数的那个Python模块。
  • 各条子进程平行地针对各自的输入数据,来运行gcd函数。
  • 对运行结果进行序列化操作,将其转变为字节。
  • 将这些字节通过socket负责到主进程之中。
  • 主进程对这些字节执行反序列话操作,将其还原为Python对象。
  • 最后,把每条子进程所求出的计算结果合并到一份列表之中,并返回给调用者

  从编程者的角度看,上面的这些步骤,似乎是比较简单的,但实际上,为了实现平行计算,mutiprocessing模块和ProcessPoolExecutor类在幕后做了大量的工作,如果改用其他编程语言来写,那么开发者只需要用一把同步锁或一项原子操作,就可以把线程之间的同学过程协调好,而在Python语言中,我们却必须使用开销较高的multiprocessing模块,mutiproocessing的开销之所以比较大,原因在于:主进程和子进程之间,必须进行序列化和反序列化操作,而程序中的大量开销,正式由这些操作所引发的。

  对于某些较为孤立,且数据利用率较高的任务来说,这套方案非常合适。所谓孤立,是指待运行的函数不需要与程序中的其他部分共享状态。所谓利用率高,是指只需要在主进程和紫禁城之间传递一部分数据,就能完成大量的运算。本例中的最大公约数算法,满足这两个条件,其他的一些类似数学算法,也可以通过这套方案实现平行计算。

  

Python开发【笔记】:concurrent.futures 平行运算的更多相关文章

  1. python 全栈开发,Day42(Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures)

    昨日内容回顾 线程什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线程是什么关系? 线程是在进程中的 一个执行单位 多进程 本质上开启的这个进程里就有一个线程 多线程 单纯的在当 ...

  2. python全栈开发,Day42(Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures)

    昨日内容回顾 线程 什么是线程? 线程是cpu调度的最小单位 进程是资源分配的最小单位 进程和线程是什么关系? 线程是在进程中的一个执行单位 多进程 本质上开启的这个进程里就有一个线程 多线程 单纯的 ...

  3. Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures

    参考博客: https://www.cnblogs.com/xiao987334176/p/9046028.html 线程简述 什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线 ...

  4. python开发笔记-通过xml快捷获取数据

    今天在做下python开发笔记之如何通过xml快捷获取数据,下面以调取nltk语料库为例: import nltk nltk.download() showing info https://raw.g ...

  5. Python标准模块--concurrent.futures

    1 模块简介 concurrent.futures模块是在Python3.2中添加的.根据Python的官方文档,concurrent.futures模块提供给开发者一个执行异步调用的高级接口.con ...

  6. 在python中使用concurrent.futures实现进程池和线程池

    #!/usr/bin/env python # -*- coding: utf-8 -*- import concurrent.futures import time number_list = [1 ...

  7. Python标准模块--concurrent.futures(进程池,线程池)

    python为我们提供的标准模块concurrent.futures里面有ThreadPoolExecutor(线程池)和ProcessPoolExecutor(进程池)两个模块. 在这个模块里他们俩 ...

  8. python并发之concurrent.futures

    concurrent:并发 Python标准库为我们提供了threading和multiprocessing模块编写相应的多线程/多进程代码.从Python3.2开始,标准库为我们提供了concurr ...

  9. Python标准模块--concurrent.futures 进程池线程池终极用法

    concurrent.futures 这个模块是异步调用的机制concurrent.futures 提交任务都是用submitfor + submit 多个任务的提交shutdown 是等效于Pool ...

随机推荐

  1. Tomcat 配置 项目 到tomcat目录外面 和 域名绑定访问(api接口、前端网站、后台管理网站)

    先停止tomcat服务 1.进入apache-tomcat-7.0.68/conf/Catalina/localhost(如果之前还都没有启动过tomcat,是不会有此目录的,先启动一次再关闭,会自动 ...

  2. Ubuntu 12.04/13.04 安装 Oracle11gR2:该笔记已经陈旧!请参考后续的笔记

    注意点: 在 ubuntu的 /bin 下建立以下几个基本命令的链接: basename awk sh->bash | sh -> ksh 安装以下几个必须的包: binutils bui ...

  3. DOTween中的Time.Scale

    因为在做游戏暂停的时候通常会使用Time.Scale = 0 ,可是暂停的时候UI如果需要继续有动画怎么办呢?在DoTween中只需要设置         tweener.SetUpdate(true ...

  4. WPF图片模糊的解决之路

    设计稿转为xaml后,设计师开始review UI了,发现图片都模糊了. 这一张很神奇,三个图片都是同一张,中间的那个最清楚,上面的这个左右两边清楚,下面的那个四个边都不清楚. 这一张,右边是原图,左 ...

  5. 详解如何将MathType嵌入word中

    将MathType嵌入word中的过程就是word插入对象的过程,插入对象是word软件中最常见的操作,MathType公式编辑器与所有的Office程序(OLE技术)都有很好的兼容性,本教程将详解如 ...

  6. 高级选项更改MathType数学公式样式

    MathType中系统的样式有很多种,我们将通过示例来演示如何更改样式定义达到修改等式的目的.使用样式将允许你迅速且方便的获得一种格式,这种格式将使你创建的等式具有统一的风格. 以下步骤中,我们将创建 ...

  7. mysqldump: command not found

    原因:这是由于系统默认会查找/usr/bin下的命令,如果这个命令不在这个目录下,当然会找不到命令,我们需要做的就是映射一个链接到/usr/bin目录下,相当于建立一个链接文件.首先得知道mysql命 ...

  8. linux下mysql 启动命令

    1,使用service 启动.关闭MySQL服务 service mysql start service mysql stop service mysql restart 运行上面命令,其实是serv ...

  9. linux--GCC简单用法

    gcc是linux下最常用的一款c编译器,对应于CPP 有相应的g++工具,debug有gdb,只是还不会用. 个人感觉gcc确实是个好东西,完全可以直接在gedit下编程然后写个shell脚本用gc ...

  10. Keil(MDK-ARM)在线调试(Ⅰ)(转)

    Ⅰ.写在前面 Keil在线调试的内容有很多,本文带来在线调试常用的内容:Debug Toolbar调试工具栏(复位.全速运行.停止运行.单步调试.逐行调试.跳出调试.运行到光标行.跳转到暂停行.调试窗 ...