只要三行Python代码,就可以让你的数据处理脚本快别人4倍

427次阅读
没有评论

共计 2547 个字符,预计需要花费 7 分钟才能阅读完成。

提醒:本文最后更新于 2020-08-07 14:42,文中所关联的信息可能已发生改变,请知悉!

只要三行 Python 代码,就可以让你的数据处理脚本快别人 4 倍
Python 是一门非常适合处理数据和自动化完成重复性工作的编程语言

我们在用数据训练机器学习模型之前,通常都需要对数据进行预处理

而 Python 就非常适合完成这项工作,比如需要重新调整几十万张图像的尺寸,用 Python 没问题!

你几乎总是能找到一款可以轻松完成数据处理工作的 Python 库。

然而

虽然 Python 易于学习,使用方便,但它并非运行速度最快的语言。

默认情况下,Python 程序使用一个 CPU 以单个进程运行。

不过如果你是在最近几年配置的电脑,通常都是四核处理器,也就是有 4 个 CPU。

这就意味着在你苦苦等待 Python 脚本完成数据处理工作时

你的电脑其实有 75% 甚至更多的计算资源就在那闲着没事干!

今天就教大家怎样通过并行运行 Python 函数,充分利用你的电脑的全部处理能力。

得益于 Python 的 concurrent.futures 模块,

我们只需 3 行代码

就能将一个普通数据处理脚本变为能并行处理数据的脚本,提速 4 倍。

普通 Python 处理数据方法

比方说:

我们有一个全是图像数据的文件夹,想用 Python 为每张图像创建缩略图。

下面是一个短暂的脚本:

用 Python 的内置 glob 函数获取文件夹中所有 JPEG 图像的列表,

然后用 Pillow 图像处理库为每张图像保存大小为 128 像素的缩略图:

只要三行 Python 代码,就可以让你的数据处理脚本快别人 4 倍
这段脚本沿用了一个简单的模式

你会在数据处理脚本中经常见到这种方法:

  • 首先获得你想处理的文件(或其它数据)的列表
  • 写一个辅助函数,能够处理上述文件的单个数据
  • 使用 for 循环调用辅助函数,处理每一个单个数据,一次一个。

咱们用一个包含 1000 张 JPEG 图像的文件夹测试一下这段脚本,

看看运行完要花多长时间:

只要三行 Python 代码,就可以让你的数据处理脚本快别人 4 倍
运行程序花了 8.9 秒,但是电脑的真实工作强度怎样呢?

我们再运行一遍程序

看看程序运行时的活动监视器情况:

只要三行 Python 代码,就可以让你的数据处理脚本快别人 4 倍
电脑有 75% 的处理资源处于闲置状态!这是什么情况?

这个问题的原因就是我的电脑有 4 个 CPU,但 Python 只使用了一个。

所以程序只是卯足了劲用其中一个 CPU,另外 3 个却无所事事。

因此我需要一种方法能将工作量分成 4 个我能并行处理的单独部分。

幸运的是,Python 中有个方法很容易能让我们做到!

试试创建多进程

下面是一种可以让我们并行处理数据的方法:

  • 将 JPEG 文件划分为 4 小块。运行 Python 解释器的 4 个单独实例。
  • 让每个 Python 实例处理这 4 块数据中的一块。
  • 将这 4 部分的处理结果合并,获得结果的最终列表。

4 个 Python 拷贝程序在 4 个单独的 CPU 上运行,

处理的工作量应该能比一个 CPU 大约高出 4 倍,

对吧?

最妙的是,Python 已经替我们做完了最麻烦的那部分工作。

我们只需告诉它想运行哪个函数以及使用多少实例就行了,剩下的工作它会完成。

整个过程我们只需要改动 3 行代码。

首先

我们需要导入 concurrent.futures 库

这个库就内置在 Python 中:

只要三行 Python 代码,就可以让你的数据处理脚本快别人 4 倍
接着,我们需要告诉 Python 启动 4 个额外的 Python 实例。

我们通过让 Python 创建一个 Process Pool 来完成这一步:

只要三行 Python 代码,就可以让你的数据处理脚本快别人 4 倍
默认情况下:

它会为你电脑上的每个 CPU 创建一个 Python 进程,

所以如果你有 4 个 CPU,就会启动 4 个 Python 进程。

最后一步:

让创建的 Process Pool 用这 4 个进程在数据列表上执行我们的辅助函数。

完成这一步,我们要将已有的 for 循环:

只要三行 Python 代码,就可以让你的数据处理脚本快别人 4 倍
替换为新的调用 executor.map():

只要三行 Python 代码,就可以让你的数据处理脚本快别人 4 倍
该 executor.map() 函数调用时需要输入辅助函数和待处理的数据列表。

这个函数能帮我完成所有麻烦的工作

包括将列表分为多个子列表、将子列表发送到每个子进程、运行子进程以及合并结果等。

干得漂亮!

只要三行 Python 代码,就可以让你的数据处理脚本快别人 4 倍
这也能为我们返回每个函数调用的结果。

Executor.map() 函数会按照和输入数据相同的顺序返回结果。

所以我用了 Python 的 zip() 函数作为捷径,一步获取原始文件名和每一步中的匹配结果。

这里是经过这三步改动后的程序代码:

只要三行 Python 代码,就可以让你的数据处理脚本快别人 4 倍
我们来运行一下这段脚本

看看它是否以更快的速度完成数据处理:

只要三行 Python 代码,就可以让你的数据处理脚本快别人 4 倍
脚本在 2.2 秒就处理完了数据!比原来的版本提速 4 倍!

之所以能更快的处理数据

是因为我们使用了 4 个 CPU 而不是 1 个。

但是

如果你仔细看看,会发现“用户”时间几乎为 9 秒。

那为何程序处理时间为 2.2 秒,但不知怎么搞得运行时间还是 9 秒?

这似乎不太可能啊?

这是

因为“用户”时间是所有 CPU 时间的总和,

我们最终完成工作的 CPU 时间总和一样,都是 9 秒,

但我们使用 4 个 CPU 完成的,实际处理数据时间只有 2.2 秒!

注意:

启用更多 Python 进程以及给子进程分配数据都会占用时间,因此靠这个方法并不能保证总是能大幅提高速度。

这种方法总能帮我的数据处理脚本提速吗?

如果你有一列数据

并且每个数据都能单独处理时,使用我们这里所说的 Process Pools 是一个提速的好方法。

下面是一些适合使用并行处理的例子:

  • 从一系列单独的网页服务器日志里抓取统计数据。
  • 从一堆 XML,CSV 和 JSON 文件中解析数据。
  • 对大量图片数据做预处理,建立机器学习数据集。

但也要记住,Process Pools 并不是万能的。

使用 Process Pool 需要在独立的 Python 处理进程之间来回传递数据。

如果你要处理的数据不能在处理过程中被有效地传递,这种方法就行不通了。

简而言之,你处理的数据必须是 Python 知道怎么应对的类型。

同时

也无法按照一个预想的顺序处理数据。

如果你需要前一步的处理结果来进行下一步,这种方法也行不通。

只要三行 Python 代码,就可以让你的数据处理脚本快别人 4 倍

那 GIL 的问题呢?

你可能知道 Python 有个叫全局解释器锁(Global Interpreter Lock)的东西,即 GIL。

这意味着即使你的程序是多线程的,每个线程也只能执行一个 Python 指令。

GIL 确保任何时候都只有一个 Python 线程执行。

换句话说:

多线程的 Python 代码并不能真正地并行运行,从而无法充分利用多核 CPU。

但是 Process Pool 能解决这个问题!

因为我们是运行单独的 Python 实例,每个实例都有自己的 GIL。

这样我们获得是真正能并行处理的 Python 代码!

不要害怕并行处理!

有了 concurrent.futures 库

Python 就能让你简简单单地修改一下脚本后,立刻让你电脑上所有 CPU 投入到工作中。

不要害怕尝试这种方法,一旦你掌握了

它就跟一个 for 循环一样简单

却能让你的数据处理脚本快到飞起。

正文完
 
水东柳
版权声明:本站原创文章,由 水东柳 2018-07-30发表,共计2547字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)