三行Python代码,让你的数据处理脚本快别人4倍
Python是一门非常适合处理数据和自动化完成重复性工作的编程语言,我们在用数据训练机器学习模型之前,通常都需要对数据进行预处理,而Python就非常适合完成这项工作,比如需要重新调整几十万张图像的尺寸,用Python没问题!你几乎总是能找到一款可以轻松完成数据处理工作的Python库。
然而,虽然Python易于学习,使用方便,但它并非运行速度最快的语言。默认情况下,Python程序使用一个CPU以单个进程运行。不过如果你是在最近几年配置的电脑,通常都是四核处理器,也就是有4个CPU。这就意味着在你苦苦等待Python脚本完成数据处理工作时,你的电脑其实有75%甚至更多的计算资源就在那闲着没事干!
今天我(作者Adam Geitgey——译者注)就教大家怎样通过并行运行Python函数,充分利用你的电脑的全部处理能力。得益于Python的 concurrent.futures 模块,我们只需3行代码,就能将一个普通数据处理脚本变为能并行处理数据的脚本,提速4倍。
普通Python处理数据方法
比方说,我们有一个全是图像数据的文件夹,想用Python为每张图像创建缩略图。
下面是一个短暂的脚本,用Python的内置glob函数获取文件夹中所有JPEG图像的列表,然后用Pillow图像处理库为每张图像保存大小为128像素的缩略图:
1 import glob
2 import os
3 from PIL import Image
4
5
6 def make_image_thumbnail(filename):
7 # 缩略图会被命名为"<original_filename>_thumbnail.jpg"
8 base_filename, file_extension = os.path.splitext(filename)
9 thumbnail_filename = f"{base_filename}_thumbnail{file_extension}"
10
11 # 创建和保存缩略图
12 image = Image.open(filename)
13 image.thumbnail(size=(128, 128))
14 image.save(thumbnail_filename, "JPEG")
15
16 return thumbnail_filename
17
18
19 # 循环文件夹中所有JPEG图像,为每张图像创建缩略图
20 for image_file in glob.glob("*.jpg"):
21 thumbnail_file = make_image_thumbnail(image_file)
22
23 print(f"A thumbnail for {image_file} was saved as {thumbnail_file}")
这段脚本沿用了一个简单的模式,你会在数据处理脚本中经常见到这种方法:
首先获得你想处理的文件(或其它数据)的列表
写一个辅助函数,能够处理上述文件的单个数据
使用for循环调用辅助函数,处理每一个单个数据,一次一个。
咱们用一个包含1000张JPEG图像的文件夹测试一下这段脚本,看看运行完要花多长时间:
1 $ time python3 thumbnails_1.py
2 A thumbnail for 1430028941_4db9dedd10.jpg was saved as 1430028941_4db9dedd10_thumbnail.jpg
3 [... about 1000 more lines of output ...]
4 real 0m8.956s
5 user 0m7.086s
6 sys 0m0.743s
运行程序花了8.9秒,但是电脑的真实工作强度怎样呢?
我们再运行一遍程序,看看程序运行时的活动监视器情况:
电脑有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中:
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():
1 image_files = glob.glob("*.jpg")
2 for image_file, thumbnail_file in zip(image_files,executor.map(make_image_thumbnail, image_files)):
该executor.map()函数调用时需要输入辅助函数和待处理的数据列表。
这个函数能帮我完成所有麻烦的工作,包括将列表分为多个子列表、将子列表发送到每个子进程、运行子进程以及合并结果等。干得漂亮!
这也能为我们返回每个函数调用的结果。
Executor.map()函数会按照和输入数据相同的顺序返回结果。所以我用了Python的zip()函数作为捷径,一步获取原始文件名和每一步中的匹配结果。
这里是经过这三步改动后的程序代码:
1 import glob
2 import os
3 from PIL import Image
4 import concurrent.futures
5
6
7 def make_image_thumbnail(filename):
8 # 缩略图会被命名为 "<original_filename>_thumbnail.jpg"
9 base_filename, file_extension = os.path.splitext(filename)
10 thumbnail_filename = f"{base_filename}_thumbnail{file_extension}"
11
12 # 创建和保存缩略图
13 image = Image.open(filename)
14 image.thumbnail(size=(128, 128))
15 image.save(thumbnail_filename, "JPEG")
16
17 return thumbnail_filename
18
19
20 # 创建Process Pool,默认为电脑的每个CPU创建一个
21 with concurrent.futures.ProcessPoolExecutor() as executor:
22 # 获取需要处理的文件列表
23 image_files = glob.glob("*.jpg")
24
25 # 处理文件列表,但通过Process Pool划分工作,使用全部CPU!
26 for image_file, thumbnail_file in zip(image_files, executor.map(make_image_thumbnail, image_files)):
27 print(f"A thumbnail for {image_file} was saved as {thumbnail_file}")
我们来运行一下这段脚本,看看它是否以更快的速度完成数据处理:
1 $ time python3 thumbnails_2.py
2 A thumbnail for 1430028941_4db9dedd10.jpg was saved as 1430028941_4db9dedd10_thumbnail.jpg
3 [... about 1000 more lines of output ...]
4 real 0m2.274s
5 user 0m8.959s
6 sys 0m0.951s
脚本在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知道怎么应对的类型。
同时,也无法按照一个预想的顺序处理数据。如果你需要前一步的处理结果来进行下一步,这种方法也行不通。
那GIL的问题呢?
你可能知道Python有个叫全局解释器锁(Global Interpreter Lock)的东西,即GIL。这意味着即使你的程序是多线程的,每个线程也只能执行一个Python指令。GIL确保任何时候都只有一个Python线程执行。换句话说,多线程的Python代码并不能真正地并行运行,从而无法充分利用多核CPU。
但是Process Pool能解决这个问题!因为我们是运行单独的Python实例,每个实例都有自己的GIL。这样我们获得是真正能并行处理的Python代码!
不要害怕并行处理!
有了concurrent.futures库,Python就能让你简简单单地修改一下脚本后,立刻让你电脑上所有CPU投入到工作中。
三行Python代码,让你的数据处理脚本快别人4倍的更多相关文章
- day02编程语言,Python语言介绍,Python解释器安装,环境变量,Python代码执行,pip,应用程序使用文件的三步骤,变量,变量的三大组成,比较,pycharm
复习 重点: 1.进制转换:二进制 与十六进制 2.内存分布:栈区 与堆区 # 二进制1111转换十六进制 => 8 4 2 1 => f 10101100111011 => 2a7 ...
- 编程语言分类,Python代码执行,应用程序使用文件的三步骤,变量,常量,垃圾回收机制
编程语言分为 机器语言(直接用二进制01跟计算机直接沟通交流,直接操作硬件) 优点:计算机能够直接读懂,速度快 缺点:开发效率极低 汇编语言(用简单的英文标签来表示二进制数,直接操作硬件) 优点:开发 ...
- 如何在batch脚本中嵌入python代码
老板叫我帮他测一个命令在windows下消耗的时间,因为没有装windows那个啥工具包,没有timeit那个命令,于是想自己写一个,原理很简单: REM timeit.bat echo %TIME% ...
- shell脚本命令 运行python文件&python命令行运行python代码
单独的python文件运行的时候 报错: 在shell脚本中,运行shell脚本命令:在Python命令行中,运行Python代码.然而,“python hello.py”是一个脚本命令,不是pyth ...
- 【Azure Redis 缓存】使用Python代码获取Azure Redis的监控指标值 (含Powershell脚本方式)
问题描述 通过Metrics监控页面,我们能得知当前资源(如Redis)的运行情况与各种指标.如果我们需要把指标下载到本地或者生成JSON数据导入到第三方的监控平台呢?Azure是否可以通过Pytho ...
- Python 代码片段收藏
list 列表相关 list 中最小值.最大值 import operator values = [1, 2, 3, 4, 5] min_index, min_value = min(enumerat ...
- 5行Python代码就能实现刷爆全网的动态条形图!
说起动态图表,最火的莫过于动态条形图了. 在B站上搜索「数据可视化」这个关键词,可以看到很多与动态条形图相关的视频. 好多视频都达到了上百万的播放量,属实厉害. 目前网上实现动态条形图现成的工具也很多 ...
- 学习 27 门编程语言的长处,提升你的 Python 代码水平
Python猫注:Python 语言诞生 30 年了,如今的发展势头可谓如火如荼,这很大程度上得益于其易学易用的优秀设计,而不可否认的是,Python 从其它语言中偷师了不少.本文作者是一名资深的核心 ...
- if __name__== "__main__" 的意思(作用)python代码复用
if __name__== "__main__" 的意思(作用)python代码复用 转自:大步's Blog http://www.dabu.info/if-__-name__ ...
随机推荐
- 《剑指offer》面试题56 - II. 数组中数字出现的次数 II
问题描述 在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次.请找出那个只出现一次的数字. 示例 1: 输入:nums = [3,4,3,3] 输出:4 示例 2: 输入:nums ...
- Spark基础知识详解
Apache Spark是一种快速通用的集群计算系统. 它提供Java,Scala,Python和R中的高级API,以及支持通用执行图的优化引擎. 它还支持一组丰富的高级工具,包括用于SQL和结构化数 ...
- 【刷题-LeetCode】188 Best Time to Buy and Sell Stock IV
Best Time to Buy and Sell Stock IV Say you have an array for which the i-th element is the price of ...
- 使用EdgyGeo Cesium工具查询下载数据集
Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ EdgyGeo, Inc. 带来了一件非常棒的工作支撑架构.工程 ...
- ManualResetEvent实现线程的暂停与恢复
背景 前些天遇到一个需求,在没有第三方源码的情况下,刷新一个第三方UI,并且拦截到其ajax请求的返回结果.当结果为AVALIABLE的时候,停止刷新并语音提示,否则继续刷新. 分析这个需求,发现需要 ...
- uniapp如何生成自己的小程序码并且携带参数
生成小程序码需要用到的参数appId appSecret这两个参数可以再微信公众平台里面登录获取 也可以用测试号里面的获取小程序码步骤1.首先要请求官方的API`https://api.weixin ...
- CMake语法—环境变量(Environment Variable)
目录 CMake语法-环境变量(Environment Variable) 1 定义环境变量 2 应用环境变量 2.1 代码结构 2.2 示例代码 2.3 运行结果 3 小结 CMake语法-环境变量 ...
- vscode开发PHP攻略
前言 此文主要介绍如何使用vscode开发PHP,开发体验可以说和php死桃木不相上下(虽然我没用过php死桃木) PHP扩展组合 一.卡巴斯基组合 PHP IntelliSense PHP Debu ...
- mvvm与mvc的定义与区别
mvvm: 即Model-View-ViewModel(模型-视图-视图模型)的简写. 模型(Model):后端传递的数据 视图(View):即前端渲染的页面 视图模型:是 mvvm 的核心,是连接 ...
- kibana 对es的简单操作。
一.查询和查看. #1.查询所有的数据 GET _search { "query": { "match_all":{} } } #2. 查看ES集群的健康状态 ...