平行运算

前言:

  编写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. 内网DNS投毒技术劫持会话

    工具列表: tcpdump Ferret Hamster node closurether 拓扑环境: 攻击机:Kali 10.10.10.237 被攻击机: win7 10.10.10.232 因为 ...

  2. Python学习笔记(三)——迭代

    知识点 迭代 迭代 什么是迭代呢?和递归又有什么区别呢? 根据维基百科和网上的资料: 迭代:迭代是重复反馈过程的活动,其目的通常是为了接近并到达所需的目标或结果.每一次对过程的重复被称为一次“迭代”, ...

  3. centos7下git的使用和配置

    1.下载git,使用命令: yum install git 2.配置git: git config --global user.name "Your Name" git confi ...

  4. [jquery] jQuery 选择器>

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  5. Excel时间格式修改为文本格式

  6. Don‘t talk.Just do it.

    对于算法,自己掌握的还是不多.并且我发现对于一个算法的理解非常重要.也许你会发现你貌似会用某总算法但是,他一旦变形,自己就无从下手. 还有就是对于算法.最好每次都自己敲,这样不仅能添加对于算法的熟度. ...

  7. 测试用例和BUG描述规范

    欢迎关注我的公众号,了解更多的测试知识:[软件测试经验与教训] 一一BUG描述基础知识 Bug标题中需包含Bug的具体位置并以[]标注 举例:[模块-子模块-页面]XXXXXXXXXXXX Bug标题 ...

  8. Javascript中的感叹号和函数function

    js函数前加分号和感叹号是什么意思?有什么用?:http://www.cnblogs.com/mq0036/p/4605255.html function与感叹号:https://swordair.c ...

  9. Access数据操作-01

    1.未在本地计算机上注册“Microsoft.Jet.OLEDB.4.0”提供程序 在菜单 “项目”的最下面 工程属性  菜单,选择“生成”选项卡,将目标平台由“Amy CPU”或者“*64”改成“* ...

  10. H5 readfile 多图片预览

    /** * 多图片前端预览 * @author Tiac */ function preView(_this, offset){ let max_nums = 10;//单位 s let max_si ...