咱们用一个包含1000张JPEG图像的文件夹测试一下这段脚本,看看运行完要花多长时间:
$ time python3 thumbnails_1.pyA thumbnail for 1430028941_4db9dedd10.jpg was saved as 1430028941_4db9dedd10_thumbnail.jpg[... about 1000 more lines of output ...]real 0m8.956suser 0m7.086ssys 0m0.743s复制代码运行程序花了8.9秒,但是电脑的真实工作强度怎样呢?
我们再运行一遍程序,看看程序运行时的活动监视器情况:
电脑有75%的处理资源处于闲置状态!这是什么情况?
这个问题的原因就是我的电脑有4个CPU,但Python只使用了一个。所以程序只是卯足了劲用其中一个CPU,另外3个却无所事事。因此我需要一种方法能将工作量分成4个我能并行处理的单独部分。幸运的是,Python中有个方法很容易能让我们做到! 试试创建多进程下面是一种可以让我们并行处理数据的方法:
1.将JPEG文件划分为4小块。
2.运行Python解释器的4个单独实例。
3.让每个Python实例处理这4块数据中的一块。
4.将这4部分的处理结果合并,获得结果的最终列表。
4个Python拷贝程序在4个单独的CPU上运行,处理的工作量应该能比一个CPU大约高出4倍,对吧?
最妙的是,Python已经替我们做完了最麻烦的那部分工作。我们只需告诉它想运行哪个函数以及使用多少实例就行了,剩下的工作它会完成。整个过程我们只需要改动3行代码。
首先,我们需要导入concurrent.futures库,这个库就内置在Python中:
import concurrent.futures复制代码接着,我们需要告诉Python启动4个额外的Python实例。我们通过让Python创建一个Process Pool来完成这一步:
with concurrent.futures.ProcessPoolExecutor() as executor:复制代码默认情况下,它会为你电脑上的每个CPU创建一个Python进程,所以如果你有4个CPU,就会启动4个Python进程。
最后一步是让创建的Process Pool用这4个进程在数据列表上执行我们的辅助函数。完成这一步,我们要将已有的for循环:
for image_file in glob.glob("*.jpg"):thumbnail_file = make_image_thumbnail(image_file)复制代码替换为新的调用executor.map():
image_files = glob.glob("*.jpg")for image_file, thumbnail_file in zip(image_files, executor.map(make_image_thumbnail, image_files)):复制代码该executor.map()函数调用时需要输入辅助函数和待处理的数据列表。这个函数能帮我完成所有麻烦的工作,包括将列表分为多个子列表、将子列表发送到每个子进程、运行子进程以及合并结果等。干得漂亮!
这也能为我们返回每个函数调用的结果。Executor.map()函数会按照和输入数据相同的顺序返回结果。所以我用了Python的zip()函数作为捷径,一步获取原始文件名和每一步中的匹配结果。
这里是经过这三步改动后的程序代码:
import globimport osfrom PIL import Imageimport concurrent.futuresdef make_image_thumbnail(filename): # 缩略图会被命名为 "<original_filename>_thumbnail.jpg" base_filename, file_extension = os.path.splitext(filename) thumbnail_filename = f"{base_filename}_thumbnail{file_extension}" # 创建和保存缩略图 image = Image.open(filename) image.thumbnail(size=(128, 128)) image.save(thumbnail_filename, "JPEG") return thumbnail_filename# 创建Process Pool,默认为电脑的每个CPU创建一个with concurrent.futures.ProcessPoolExecutor() as executor: # 获取需要处理的文件列表 image_files = glob.glob("*.jpg") # 处理文件列表,但通过Process Pool划分工作,使用全部CPU! for image_file, thumbnail_file in zip(image_files, executor.map(make_image_thumbnail, image_files)): print(f"A thumbnail for {image_file} was saved as {thumbnail_file}")复制代码我们来运行一下这段脚本,看看它是否以更快的速度完成数据处理:
$ time python3 thumbnails_2.pyA thumbnail for 1430028941_4db9dedd10.jpg was saved as 1430028941_4db9dedd10_thumbnail.jpg[... about 1000 more lines of output ...]real 0m2.274suser 0m8.959ssys 0m0.951s复制代码脚本在2.2秒就处理完了数据!比原来的版本提速4倍!之所以能更快的处理数据,是因为我们使用了4个CPU而不是1个。
但是如果你仔细看看,会发现“用户”时间几乎为9秒。那为何程序处理时间为2.2秒,但不知怎么搞得运行时间还是9秒?这似乎不太可能啊?
这是因为“用户”时间是所有CPU时间的总和,我们最终完成工作的CPU时间总和一样,都是9秒,但我们使用4个CPU完成的,实际处理数据时间只有2.2秒! 注意:启用更多Python进程以及给子进程分配数据都会占用时间,因此靠这个方法并不能保证总是能大幅提高速度。如果你要处理非常大的数据集,这里有篇设置将数据集切分成多少小块的文章,可以读读,会对你帮助甚大. 这种方法总能帮我的数据处理脚本提速吗?如果你有一列数据,并且每个数据都能单独处理时,使用我们这里所说的Process Pools是一个提速的好方法。下面是一些适合使用并行处理的例子: