平行运算

前言:

  编写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. 蔡勒(Zeller)公式

    来源好搜百科:http://baike.haosou.com/doc/1048888-1109421.html 蔡勒(Zeller)公式,是一个计算星期的公式,随便给一个日期,就能用这个公式推算出是星 ...

  2. 同一种类型的两个对象赋值,用反射。再也不用点属性了。。。。(适用于ef)

    /// <summary> /// 给对象赋值的方法(不赋地址)(同一个类型),含过滤 /// </summary> /// <typeparam name=" ...

  3. MySQL索引覆盖

    什么是“索引覆盖”? 简单来的说,就是让查询的字段(包括where子句中的字段),都是索引字段.索引覆盖的好处是什么?好处是极大的.极大的.极大的提高查询的效率!重要的说三遍! 特别说明: 1.whe ...

  4. R-CNN目标检测的selective search(SS算法)

    候选框确定算法 对于候选框的位置确定问题,简单粗暴的方法就是穷举或者说滑动窗口法,但是这必然是不科学的,因为时间和计算成本太高,直观的优化就是假设同一种物体其在图像邻域内有比较近似的特征(例如颜色.纹 ...

  5. Eclipse的调试功能的10个小窍门

    你可能已经看过一些类似“关于调试的N件事”的文章了.但我想我每天大概在调试上会花掉1个小时,这是非常多的时间了.所以非常值得我们来了解一些用得到的功能,可以帮我们节约很多时间.所以在这个主题上值得我再 ...

  6. Java精选笔记_Servlet技术

    Servlet技术 Servlet开发入门 Servlet接口 针对Servlet技术的开发,SUN公司提供了一系列接口和类,其中最重要的是javax.servlet.Servlet接口. Servl ...

  7. VS2015编译CURL7.54.0源码

    2018.8.24找到一种新途径,运行curl-master\projects\generate.bat,然后curl-master\projects\Windows\VC14\curl-all.sl ...

  8. iOS底层音频处理技术(带源代码)

    本文由论坛会员artgolff分享 前几天搜索资料时发现一个网站: iPhone Core Audio Development ,里面有iOS底层 音频 技术的几个源 代码 ,如果你要实现VoIP电话 ...

  9. iOS - UIScrollView xib添加约束的坑

    一.Storyboard中的UIScrollView使用自动布局 在使用storyboard和xib时,我们经常要用到ScrollView,还有自动布局AutoLayout,但是ScrollView和 ...

  10. 程序启动-Runloop

    0 从程序启动开始到view显示: start->(加载framework,动态静态链接库,启动图片,Info.plist,pch等)->main函数->UIApplicationM ...