python多核并行计算_Python 基本功: 14. 多核并行计算

现在的笔记本电脑,台式机都流行多核心,低频率的架构,原因是低频率低耗电,而多核心又可以在并行计算中表现出色。在上一篇教程中:多多教Python:Python 基本功: 13. 多线程运算提速​zhuanlan.zhihu.comv2-37ccd3da7a54f49501f9545b50d3ff6f_180x120.jpg

描述了如何用 Python 的多线程模块来实现计算提速,但是受限于在一个进程,也就是单核心上的运算。而这篇教程,将告诉大家如何利用 Python 在多核心上做并行计算。

教程需求:Mac OS (Windows, Linux 会略有不同)

安装了 Python 3.0 版本以上, PyCharm

阅读了多多教Python:Python 基本功: 6. 第一个完整的程序​zhuanlan.zhihu.comv2-6c4fc56914acd34bf7f675ce5e094691_180x120.jpg多多教Python:Python 基本功: 10. 面对对象-类 Class​zhuanlan.zhihu.comv2-191925a0884aa24c6982e86056f08c95_180x120.jpg

多进程 vs. 多线程

多进程 (Multi-Process) 和多线程 (Multi-Thread) 最大的区别是,多进程是在各自单独的进程内存管理下运行代码,而多线程是共享一个进程内存。在各自单独的进程管理下,多进程的明显优势是可以最大的利用计算机多核心的处理能力。但是多进程也有其劣势,比如说在进程之间通信需要 IPC (Inter Process Communication) 工具,而不像多线程那样可以共享内存数据。

如果你拥有的是2015年之后的电脑,那么CPU基本上都是多核心的。从 多多教Python:Python 基本功: 0. 选择环境 开始跟随的小伙伴应该知道作者是在苹果电脑上开发 Python的,而现在你可以在苹果官网买到 8核心的笔记本:最高可配置 8核 的2019款 Mac 笔记本电脑

或者一台最高配置达到18核心的 Xeon 架构苹果台式机:iMac Pro 2018款, Xeon 架构的处理器

苹果电脑作为消费者机型,已经带来了最高 18核心的 Xeon 服务器架构的电脑,说明多核心计算能力已经普及到了大众。一旦你学习了如何利用 Python 来调度如此强大的计算能力,你就可以比别人更快的一步的获得重要的信息资源。

多进程库 Multiprocessing

通过调用 Python 自带的多进程库 Multiprocessing, 你就可以轻松的在本地电脑上进行多核并行计算,现在我们来看一些代码了解一下这个库:

import math

import datetime

import multiprocessing as mp

def train_on_parameter(name, param):

result = 0

for num in param:

result += math.sqrt(num * math.tanh(num) / math.log2(num) / math.log10(num))

return {name: result}

if __name__ == '__main__':

start_t = datetime.datetime.now()

num_cores = int(mp.cpu_count())

print("本地计算机有: " + str(num_cores) + " 核心")

pool = mp.Pool(num_cores)

param_dict = {'task1': list(range(10, 30000000)),

'task2': list(range(30000000, 60000000)),

'task3': list(range(60000000, 90000000)),

'task4': list(range(90000000, 120000000)),

'task5': list(range(120000000, 150000000)),

'task6': list(range(150000000, 180000000)),

'task7': list(range(180000000, 210000000)),

'task8': list(range(210000000, 240000000))}

results = [pool.apply_async(train_on_parameter, args=(name, param)) for name, param in param_dict.items()]

results = [p.get() for p in results]

end_t = datetime.datetime.now()

elapsed_sec = (end_t - start_t).total_seconds()

print("多进程计算 共消耗: " + "{:.2f}".format(elapsed_sec) + " 秒")核心数量: cpu_count() 函数可以获得你的本地运行计算机的核心数量。如果你购买的是 Intel i7或者以上版本的芯片,你会得到一个乘以2的数字,得益于超线程 (Hyper-Threading) 结构,Python 可利用核心数量是真实数量的2倍!所以我在前文中会建议Python开发者购买 i7 而不是 第八代之前的 i5。

进程池: Pool() 函数创建了一个进程池类,用来管理多进程的生命周期和资源分配。这里进程池传入的参数是核心数量,意思是最多有多少个进程可以进行并行运算。

异步调度: apply_async() 是进程池的一个调度函数。第一个参数是计算函数,和 多多教Python:Python 基本功: 13. 多线程运算提速 里多线程计算教程里创建线程的参数 target 类似。第二个参数是需要传入计算函数的参数,这里传入了计算函数名字和计算调参。而异步的意义是在调度之后,虽然计算函数开始运行并且可能没有结束,异步调度都会返回一个临时结果,并且通过列表生成器 (参考: 多多教Python:Python 基本功: 12. 高纬运算的救星 Numpy) 临时的保存在一个列表里,这里就是 results。

调度结果: 如果你检查列表 results 里的类,你会发现 apply_async() 返回的是 ApplyResult,也就是调度结果类。这里用到了 Python 的异步功能,目前教程还没有讲到,简单的来说就是一个用来等待异步结果生成完毕的容器。

获取结果: 调度结果 ApplyResult 类可以调用函数 get(), 这是一个非异步函数,也就是说 get() 会等待计算函数处理完毕,并且返回结果。这里的结果就是计算函数的 return。

并行计算 Parallel Processing

在我们写完第一个多核计算 Python 文件后,就可以准备开始执行了。这里用的环境是 Mac OS, PyCharm IDE。作者首先在 Intel 第七代 4核 i7,超线程8核的 CPU 上运行:

本地计算机有: 8 核心

多进程计算 共消耗: 43.15 秒

Process finished with exit code 0

一共用了 43.15 秒,平均每一个任务用了 5.39 秒。在运行的时候如果查看 Activity Monitor:8 个 Python 独立进程同时运行

我们会发现在进程表里出现了8个 Python 程序,每一个都是一个线程,占用 CPU 比率 在 80-87% 之间。

下面我们再把同样的代码放在 Macbook Pro 上运行,用的是 Intel 第八代 i5 四核,超线程8核处理器:

本地计算机有: 8 核心

Process SpawnPoolWorker-2:

Process SpawnPoolWorker-3:

Process SpawnPoolWorker-4:

Process SpawnPoolWorker-5:

Process SpawnPoolWorker-6:

Process SpawnPoolWorker-7:

Process SpawnPoolWorker-1:

Process SpawnPoolWorker-8:

Traceback (most recent call last):

这里如果我们在运行的时候打断一下 (在 PyCharm 中点进进程终止),我们会发现8个进程也会被回收起来,这是因为我们的主进程在 PyCharm 内部会跟踪新生成的8个任务进程,一旦主进程收到了关闭/停止指令,8个任务进程也会同时被关闭。但是在其他情况下,我们需要检查是否所有进程都被正常停止,防止占用计算机资源。

本地计算机有: 8 核心

多线程计算 共消耗: 66.38 秒

Process finished with exit code 0

我们发现这里的耗时是 66.38秒,相比较于 i7 的处理速度降低了 53%。同时还因为这个计算函数需要占用大量的内存,所以内存的读写速度 (笔记本电脑通常是 1833 Mhz) 也有一定的影响。

资源共享 Resource Sharing

多线程因为共享一个进程的内存,所以在并行计算的时候会出现资源竞争的问题,这个在多多教Python:Python 基本功: 13. 多线程运算提速 已经提到过。而多进程虽然避免了这个问题,但是无法像多线程一样轻易的调用一个内存的资源。为了能让多进程之间进行通讯 (IPC),Python 的 Multiprocessing 库提供了几种方案: Pipe, Queue 和 Manager。这里 Pipe 我就直接引用一个外部我觉得很简单明了的介绍,Queue 有兴趣的小伙伴可以在教程结尾找到外部的链接,然后我会在之前的例子中加入 Manager。管道 Pipe:

Pipe可以是单向(half-duplex),也可以是双向(duplex)。我们通过mutiprocessing.Pipe(duplex=False)创建单向管道 (默认为双向)。一个进程从PIPE一端输入对象,然后被PIPE另一端的进程接收,单向管道只允许管道一端的进程输入,而双向管道则允许从两端输入。

————————————————

版权声明:本文为CSDN博主「gmHappy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

import multiprocessing as mul

def proc1(pipe):

pipe.send('hello')

print('proc1 rec:', pipe.recv())

def proc2(pipe):

print('proc2 rec:', pipe.recv())

pipe.send('hello, too')

# Build a pipe

pipe = mul.Pipe()

if __name__ == '__main__':

# Pass an end of the pipe to process 1

p1 = mul.Process(target=proc1, args=(pipe[0],))

# Pass the other end of the pipe to process 2

p2 = mul.Process(target=proc2, args=(pipe[1],))

p1.start()

p2.start()

p1.join()

p2.join()

#————————————————

#版权声明:本文为CSDN博主「gmHappy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

#原文链接:https://blog.csdn.net/ctwy291314/article/details/89358144管理员 Manager

Manager 是一个 Multiprocessing 库里的类,用来创建可以进行多进程共享的数据容器,容器种类包括了几乎所有 Python 自带的数据类,详情可以参考: 多多教Python:Python 基本功: 3. 数据类型。这里我们把 Manager 加入到前文的例子中:

import math

import datetime

import multiprocessing as mp

def train_on_parameter(name, param, result_dict, result_lock):

result = 0

for num in param:

result += math.sqrt(num * math.tanh(num) / math.log2(num) / math.log10(num))

with result_lock:

result_dict[name] = result

return

if __name__ == '__main__':

start_t = datetime.datetime.now()

num_cores = int(mp.cpu_count())

print("本地计算机有: " + str(num_cores) + " 核心")

pool = mp.Pool(num_cores)

param_dict = {'task1': list(range(10, 30000000)),

'task2': list(range(30000000, 60000000)),

'task3': list(range(60000000, 90000000)),

'task4': list(range(90000000, 120000000)),

'task5': list(range(120000000, 150000000)),

'task6': list(range(150000000, 180000000)),

'task7': list(range(180000000, 210000000)),

'task8': list(range(210000000, 240000000))}

manager = mp.Manager()

managed_locker = manager.Lock()

managed_dict = manager.dict()

results = [pool.apply_async(train_on_parameter, args=(name, param, managed_dict, managed_locker)) for name, param in param_dict.items()]

results = [p.get() for p in results]

print(managed_dict)

end_t = datetime.datetime.now()

elapsed_sec = (end_t - start_t).total_seconds()

print("多线程计算 共消耗: " + "{:.2f}".format(elapsed_sec) + " 秒")

这里我们用 Manager 来创建一个可以进行进程共享的字典类,随后作为第三个参数传入计算函数中。计算函数把计算好的结果保存在字典里,而不是直接返回。在并行运算结束之后,我们通过 print() 函数来查看字典里的结果。注意这里既然出现了可以共享的数据类,我们就要再次通过锁 (Lock) 来避免资源竞争,所以同时通过 Manager 创建了锁 Lock 类,以第四个参数传入计算函数,并且用 With 语境来锁住共享的字典类。

小结:

并行运算可以最大化的利用当代的计算能力,把原本需要几个小时,几天的处理任务变成几分钟,几小时。而通过 Python 已有的 Multiprocessing 库,几行代码就可以把你手上的计算器变成一台超级计算机。而如果你手上的计算器无法满足你的计算需求,你可以借助云,在云服务器上轻松的租凭一台 Xeon 架构的超级计算器帮你完成任务。有兴趣的小伙伴可以继续阅读一些外部的教程:

本文引用的 Pipe, Queue 教程:Python multiprocessing使用详解​blog.csdn.net

一篇简书上的多进程教程:Python3多进程multiprocessing模块的使用​www.jianshu.comv2-dbe56f55400299420c3f82d1748a420c_180x120.jpg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值