《极速Python:高性能编码、计算与数据分析》系统性地介绍了Python高性能编程的各种工具和方法,并重点讲解了Python在大数据中的应用。书中配有清晰的示例和详实的分析,可帮助读者熟练掌握NumPy和Pandas,实现高性能的数据存储和I/O。本书高屋建瓴、不留遗漏,读者学习本书后,能从代码到架构对系统进行整体优化。
主要内容
● 使用Cython重构关键代码。
● 设计健壮的数据结构。
● 精简不同架构的代码。
● 实现Python GPU计算。
《极速Python:高性能编码、计算与数据分析》直击Python编程要害。对于大型数据项目,过慢的代码运行速度是毁灭性的。在机器学习和大规模数据分析中,除了使用高性能的Python代码,还要使用经过优化的库、发挥计算机硬件的多核处理能力。满足计算速度后,还要优化计算资源、控制计算成本,而《极速Python:高性能编码、计算与数据分析》为读者提供了一整套高性能编程解决方案。
若干年前,我们团队正在使用的基于Python的数据管道突然崩溃,导致某个进程持续占用CPU。该组件对公司业务至关重要,因此必须尽快解决该问题。我们核查了算法,始终没有发现问题。算法的实现步骤其实非常简单,但经过多名工程师的数小时排查,才发现问题在于程序在一个非常大的列表上进行搜索。在将列表转换为集合后,问题就迎刃而解了。最终,数据结构不仅变得更小,搜索时间也从数小时降低到毫秒级别。
这次故障对我触动很大:
● 虽然问题并不严重,但暴露出团队在开发过程中并不关注性能问题。如果经常使用代码分析器,我们就能在几分钟内发现问题,而不是耗费了好几个小时。
● 我们最终解决了问题,并且取得了双赢的结果,不仅程序查询时间更短,占用内存也更少。虽然在许多情况下,面对性能和成本需要做出取舍,但在某些情况下,兼顾两者不仅能获得满意的结果,还没有任何负面影响。
● 从更高的角度审视,结果也是双赢的。首先,查询速度更快非常有利于公司业务。其次,算法经过优化后,使用CPU的时间更短、耗能更低,也更加环保。
● 虽然单个案例意义有限,但我意识到许多程序员或许都在寻找类似的优化解决方案。
因此,我决定编写本书,以便其他程序员可以从中受益。我的目标是帮助经验丰富的Python程序员设计和实现更高效的解决方案,同时能够了解底层的权衡机制。我将采用全面且透彻的方式,通过探讨Python代码和重要的Python库,从算法角度来探究现代硬件架构及其影响,并分析CPU和存储性能。希望本书能够帮助读者在使用Python生态进行开发时,游刃有余地处理性能问题。
关 于 本 书
本书旨在帮助读者在Python生态系统中编写更高效的应用程序。更高效是指让代码使用的CPU周期更少、占用的存储空间更少、消耗的网络通信更少。
本书采用全面且透彻的方式来分析性能问题。书中不仅涉及纯Python代码的优化技术,还介绍了如何高效使用数据科学库,如NumPy和pandas。由于Python在某些场景下性能不足,为了使代码运行速度更快,本书还讲解了Cython。为了使内容尽量全面,本书还讨论了硬件对代码设计的影响,即分析现代计算机架构对算法性能的影响。另外,还探究了网络架构对效率的影响,以及GPU计算在快速数据分析领域中的运用。
目标读者
本书面向中高级读者。希望读者接触过目录中的大部分技术,并且最好亲手使用过其中一些。除了IO库和GPU计算章节,本书只提供了很少的介绍性内容,所以要求读者了解基础知识。如果你正在编写高性能代码,并面临高效处理海量数据的切实挑战,这本书可提供很多建议。
为了从本书吸收更多的知识,你应该具备多年的Python编程经验,熟悉Python控制流、列表、集合和字典,并具备一定Python标准库的使用经验,如os、sys、pickle和multiprocessing。为了充分运用书中介绍的技术,你或多或少应操作过标准的数据分析库,即NumPy和pandas,例如使用过NumPy中的数组和pandas中的数据帧。
如果你清楚如何通过C或Rust等语言接口来加速Python代码,或者了解Cython或Numba等优化工具,即使没有实际操作过,本书对你也会很有帮助。在Python中处理IO的经验对你也会有所帮助。鉴于IO库方面的学习资料较少,我们将从Apache Parque和Zarr库开始介绍。
读者应该熟练使用Linux终端(或MacOS终端)的基本命令。如果使用Windows,请安装基于UNIX的终端,并熟悉命令行或PowerShell。此外,你还需要在计算机上安装Python。
在某些示例中,我会讲解云计算技巧,但访问云或了解云计算方面的知识不是阅读本书的必要条件。如果你对云计算方法感兴趣,建议学习执行云计算的基本操作,例如,创建实例和访问云服务存储。
虽然本书不要求读者接受过学术培训,但了解复杂度的基本概念是有益的。例如,理解与数据量呈线性关系的算法优于与数据量呈指数关系的算法。如果你想使用GPU进行优化,阅读本书前无需了解相关内容。
本书内容:学习路线图
本书各章基本是独立的,读者可以翻阅任何感兴趣的章节。全书内容分为四部分。
第Ⅰ部分“基础知识”(第1~4章),涵盖了入门知识。
● 第1章介绍了海量数据带来的问题,并解释了为什么必须关注计算和存储的效率。本章还介绍了全书的学习方法,并提供了如何根据需求学习本书的建议。
● 第2章讲解了对原生Python代码进行优化的方法。本章还讨论了Python数据结构的优化、代码分析、内存分配和惰性编程技术。
● 第3章讨论了Python中的并发和并行,以及如何最佳利用多进程和多线程(包括使用线程进行并行处理的限制)。本章还介绍了异步处理,它通常用于Web服务中的低负载、多并发请求场景。
● 第4章介绍了NumPy,NumPy是用于高效处理多维数组的库。NumPy是当前所有数据处理技术的核心,是众多方法的基本库。本章分享了NumPy中的关键功能,如视图、广播和数组编程,以开发更高效的代码。
第Ⅱ部分“硬件”(第5章和第6章),主要涉及如何发挥硬件和网络的最大效率。
● 第5章介绍了Cython。Cython基于Python,可以生成非常高效的代码。Python属于高级解释性语言,因此不适合用于优化硬件。其他几种语言,如C或Rust,从设计之初就能在硬件层高效运行。Cython属于底层语言,虽然它与Python很相似,但可以将Cython编译成C代码。要想写出高效的Cython代码,关键在于掌握代码基础和实现方法。在本章中,我们就将学习如何编写高效的Cython代码。
● 第6章讨论了硬件架构对高效Python代码设计的影响。鉴于计算机的设计模式,反直觉的编程方法可能比预期的更有效率。例如,在某些情况下,即使需要承担解压缩算法的计算开销,处理压缩数据也可能比处理未压缩数据的速度更快。本章还介绍了CPU、内存、存储和网络对Python算法设计的影响。另外,本章讨论了NumExpr库,它能利用最新的硬件架构特性使NumPy代码更高效。
第Ⅲ部分“用于现代数据处理的应用和库”(第7章和第8章),探讨了最新的用于数据处理的应用和库。
● 第7章讨论了如何高效使用pandas,pandas是Python中的数据帧库。我们将研究pandas相关的技术,以优化代码。和本书大多数章节不同,这一章的内容基于前面的章节。pandas是在NumPy的基础上工作的,所以会借鉴第4章的内容,探索与NumPy相关的技术进而优化pandas。我们还将探究如何用NumExpr和Cython优化pandas。最后,介绍了Arrow库,除了可以提高处理pandas数据帧的性能,它还具有其他强大的功能。
● 第8章探讨了数据持久化的优化问题。我们讨论了能高效处理列型数据的库Parquet,以及能处理大型磁盘数组的库Zarr。本章还讨论了如何处理超过内存容量的数据集。
第Ⅳ部分“高级主题”(第9章和第10章),涉及最后两个与众不同的方法,即GPU和Dask库。
● 第9章探讨了图形处理单元(Graphical Processing Unit,GPU)在处理大型数据集方面的使用方法。我们将看到,GPU计算模型使用大量简单的处理单元,完全可以处理最新的数据科学问题。我们使用了两种不同的方法以利用GPU的优势。首先,将讨论现有GPU方面的库,这些库提供了与其他库类似的接口,例如CuPy是NumPy的GPU版本。另外,本章还介绍如何编写在GPU上运行的Python代码。
● 第10章讨论了Dask。使用Dask库能编写出轻松扩展到计算集群上的并行代码,它提供了类似于NumPy和pandas的接口。计算集群既可以位于本地,又可以位于云端。
该书最后还包括两个附录。
● 附录A介绍如何安装所必需的软件,以使用书中的示例。
● 附录B介绍Numba。Numba是Cython的替代品,可以生成高效的底层代码。Cython和Numba是生成底层代码的主要途径。为了解决真实场景中的问题,我推荐使用Numba。但是,为什么正文中用了一整章来介绍Cython,却将Numba放在附录中呢?这是因为本书的主要目标是为你打下坚实的基础,确保能在Python生态系统中编写出高效的代码,而Cython更适合深入了解内部的计算机制。
获取代码
本书包含许多源代码示例,既有行间代码,又有行内代码。为了区别于普通文本,这两种样式的源代码都采用等宽字体。有时,也会加粗代码,以强调代码发生的变动,例如,为已有代码添加新功能。
在许多示例中,不得不对初始源代码的样式进行调整。为了使页面版式更加美观,添加了换行符,并重新进行排版缩进。但在极少数情况下,如此调整后还要使用连行符(?)。此外,如果文本中对代码进行了解释,通常会删除源代码中的注释。许多代码附带有注释,以突出重要概念。
本书示例的完整代码可从GitHub仓库(https://github.com/tiagoantao/python-performance) 下载,或扫描本书封底的二维码进行下载。当发现错误或需要更新Python和库时,我会更新代码仓库。因此,请读者留意代码仓库的变动。代码仓库为每个章节列出了详细目录。
人们对代码风格各有偏好,我尽量调整了书中的代码,以使代码在纸质版的书中看起来更加美观。例如,我本来喜欢使用较长且具有描述性的变量名,但这种名称的印刷效果一般。因此,书中使用表达性的变量名,并遵循标准的Python惯例,如PEP8,同时确保印刷效果的美观。本书对类型注释也采取了同样的做法,我原本想使用类型注释,但类型注释妨碍了代码的可读性。在极少数情况下,我使用算法来增强可读性,但算法并不能应对所有极端情况,或使代码逻辑更加清晰。
在大多数情况下,本书中的代码适用于标准的Python解释器。在少数情况下,需要使用IPython,特别是进行性能分析时。你也可以使用Jupyter Notebook。
关于安装的细节可以参考附录A。如果任何章节需要使用特殊的软件,将在适当的地方注明。
硬件和软件
读者可以使用任何操作系统运行本书中的代码。不过,大部分生产代码都部署在Linux上,因此Linux是首选系统。或者,也可以直接使用MacOS X。如果使用的是Windows,建议安装Windows Subsystem for Linux(WSL) 。
除了操作系统,还可以选择Docker。读者可以使用代码仓库中提供的Docker镜像,Docker提供了容器化的Linux环境来运行代码。
建议你使用的计算机至少拥有16 GB的内存和150 GB的可用磁盘空间。第9章涉及与GPU相关的内容,需要使用NVIDIA GPU,最低基于Pascal架构。过去五年中发布的大多数GPU都符合此要求。为了充分学习本书内容,有关环境准备的详细信息,请参见附录A。
Tiago Rodrigues Antão拥有信息学工程学士学位和生物信息学博士学位。他目前从事生物技术工作,使用Python生态来处理科学计算和数据工程任务。大多数时候,他也使用底层编程语言(如C和Rust)对算法的关键部分进行优化。目前,他在基于Amazon AWS的云计算设备上进行开发,但使用的基本是本地计算集群。
除了业内经历,他在科学计算方面有两段学术经历,包括在剑桥大学和牛津大学从事数据分析博士后研究工作。作为蒙大拿大学的研究员,他从零开始创建了用于分析生物数据的整套科学计算方法。
Tiago是重要生物信息软件包Biopython(用Python编写)的共同作者之一,也是Bioinformatics with Python Cookbook(Packt出版社,2022)一书的作者,该书已出版了第3版。他还在生物信息学领域发表了多篇重要的科研论文。
第Ⅰ部分 基础知识
第1章 对高效数据处理的迫切需求 3
1.1 数据泛滥的严重性 4
1.2 现代计算架构和高性能计算 6
1.2.1 计算机内部的变化 7
1.2.2 网络的变化 8
1.2.3 云计算 9
1.3 Python的局限性 10
1.4 解决方案小结 11
1.5 本章小结 13
第2章 发挥内置功能的最高性能 15
2.1 分析同时具有IO和计算
任务的应用程序 16
2.1.1 下载数据并计算最低温度 16
2.1.2 Python的内置分析模块 18
2.1.3 使用本地缓存 19
2.2 对代码进行分析以检测性能瓶颈 20
2.2.1 可视化分析信息 21
2.2.2 行分析 22
2.2.3 代码分析小结 23
2.3 优化基本数据结构:列表、集合、字典 24
2.3.1 列表搜索的性能 25
2.3.2 使用集合进行搜索 25
2.3.3 Python中的列表、集合和字典的复杂性 26
2.4 节约内存 27
2.4.1 Python内存估算 28
2.4.2 其他表示方法的内存占用 30
2.4.3 使用数组进行紧凑表示 32
2.4.4 串联知识点:估算Python对象的内存占用 33
2.4.5 Python对象内存占用小结 34
2.5 在大数据管道中使用惰性编程和生成器 34
2.6 本章小结 36
第3章 并发、并行和异步 37
3.1 编写异步服务器框架 39
3.1.1 实现与客户通信的框架 41
3.1.2 协程 42
3.1.3 使用简单的同步客户端发送复杂数据 43
3.1.4 实现进程间通信的其他方法 44
3.1.5 异步编程小结 45
3.2 实现基本的MapReduce引擎 45
3.2.1 理解MapReduce框架 45
3.2.2 开发简单的测试场景 46
3.2.3 第一次实现MapReduce框架 47
3.3 实现MapReduce并发引擎 47
3.3.1 使用concurrent.futures实现线程服务器 47
3.3.2 使用futures异步执行 49
3.3.3 GIL和多线程 51
3.4 使用多进程实现MapReduce 52
3.4.1 基于concurrent.futures的解决方案 52
3.4.2 基于多进程模块的解决方案 53
3.4.3 监控多进程方法的进度 54
3.4.4 分块传输数据 56
3.5 知识点串联:异步多线程和多进程MapReduce 服务器 59
3.5.1 创建完整的高性能解决方案 59
3.5.2 创建强大且稳定的服务器 62
3.6 本章小结 63
第4章 高性能NumPy 65
4.1 理解NumPy的性能 66
4.1.1 数组的副本与视图 66
4.1.2 NumPy的视图机制 71
4.1.3 利用视图提高效率 75
4.2 数组编程 77
4.2.1 小结 78
4.2.2 NumPy中的广播 78
4.2.3 应用数组编程 80
4.2.4 矢量化计算 82
4.3 改进NumPy内部架构 85
4.3.1 NumPy的依赖关系 85
4.3.2 在Python发行版中调整NumPy 87
4.3.3 NumPy中的线程 88
4.4 本章小结 89
第Ⅱ部分 硬件
第5章 使用Cython重构核心代码 93
5.1 重构高效代码的方法 93
5.2 Cython快速入门 95
5.2.1 Cython实现 95
5.2.2 使用Cython注释提高性能 97
5.2.3 为什么注释可以提升性能 98
5.2.4 为函数返回值添加类型 100
5.3 分析Cython代码 101
5.3.1 使用Python的内置分析方法 101
5.3.2 使用line_profiler 103
5.4 用Cython内存视图优化数组访问 105
5.4.1 小结 107
5.4.2 清理Python内部交互 107
5.5 在Cython中编写NumPy通用函数 108
5.6 Cython高级数组访问 110
5.6.1 绕过GIL并同时运行多个线程 112
5.6.2 基本性能分析 116
5.6.3 使用Quadlife的视频示例 116
5.7 Cython并行计算 117
5.8 本章小结 118
第6章 内存层级、存储和网络 121
6.1 现代硬件架构如何影响Python性能 122
6.1.1 现代架构对性能的反直觉影响 122
6.1.2 CPU缓存如何影响算法效率 123
6.1.3 现代持久化存储 124
6.1.4 小结 125
6.2 使用Blosc进行高效
数据存储 125
6.2.1 压缩数据以节省时间 125
6.2.2 读取速度(和内存缓冲区) 127
6.2.3 不同压缩算法对存储性能的影响 128
6.2.4 探究数据表示以提高压缩率 128
6.2.5 小结 129
6.3 使用NumExpr加速NumPy 129
6.3.1 快速表达式处理 129
6.3.2 硬件架构的影响 130
6.3.3 不适合NumExpr的场景 131
6.4 本地网络对性能的影响 131
6.4.1 REST低效的原因 132
6.4.2 基于UDP和msgpack的
客户端 132
6.4.3 基于UDP的服务器 134
6.4.4 为客户端添加超时机制 135
6.4.5 优化网络计算的其他建议 135
6.5 本章小结 136
第Ⅲ部分 和于现代数据处理的应用和库
第7章 高性能pandas和Apache Arrow 139
7.1 优化数据加载的内存和时间 140
7.1.1 压缩数据与未压缩数据 140
7.1.2 推断列的类型 141
7.1.3 数据类型精度的影响 143
7.1.4 重新编码和压缩数据 144
7.2 高效数据分析方法 147
7.2.1 使用索引加速访问 147
7.2.2 行的迭代方法 148
7.3 基于NumPy、Cython和NumExpr的pandas 150
7.3.1 显式使用NumPy 151
7.3.2 基于NumExpr的pandas 151
7.3.3 Cython和pandas 153
7.4 使用Arrow将数据读入pandas 154
7.4.1 pandas和Apache Arrow的关系 155
7.4.2 读取CSV文件 156
7.4.3 使用Arrow进行分析 158
7.5 使用Arrow互操作将任务委托给更高效的语言和系统 158
7.5.1 Arrow语言互操作架构的意义 159
7.5.2 使用Arrow的Plasma服务器对数据进行零拷贝操作 160
7.6 本章小结 163
第8章 大数据存储 165
8.1 访问文件的统一接口:fsspec 165
8.1.1 使用fsspec搜索GitHub仓库中的文件 166
8.1.2 使用fsspec检查zip文件 167
8.1.3 使用fsspec访问文件 168
8.1.4 使用URL链遍历不同的文件系统 168
8.1.5 替换文件系统后端 169
8.1.6 使用PyArrow接口 169
8.2 Parquet:高效的列型数据存储格式 170
8.2.1 检查Parquet元数据 170
8.2.2 使用Parquet进行列编码 171
8.2.3 对数据集进行分区 173
8.3 使用传统方法处理大于内存的数据集 175
8.3.1 使用NumPy对文件进行内存映射 175
8.3.2 数据帧的分块读取和写入 177
8.4 使用Zarr进行大型数组持久化 178
8.4.1 Zarr的内部架构 179
8.4.2 Zarr中数组的存储 181
8.4.3 创建新数组 183
8.4.4 Zarr数组的并行读写 184
8.5 本章小结 186
第Ⅳ部分 高级主题
第9章 使用GPU进行数据分析 189
9.1 理解GPU算力 190
9.1.1 GPU的优势 190
9.1.2 CPU和GPU的关系 192
9.1.3 GPU的内部架构 193
9.1.4 软件架构 194
9.2 使用Numba生成GPU代码 194
9.2.1 安装Python的GPU软件 194
9.2.2 使用Numba进行GPU编程的基础知识 195
9.2.3 使用GPU重构Mandelbrot示例 198
9.2.4 使用NumPy编写Mandelbrot代码 200
9.3 GPU代码性能分析:CuPy程序案例 201
9.3.1 基于GPU的数据分析库 202
9.3.2 使用CuPy:NumPy基于GPU 202
9.3.3 CuPy基本操作 202
9.3.4 使用Numba编写Mandelbrot生成器 203
9.3.5 使用CUDA C实现Mandelbrot生成器 205
9.3.6 GPU代码分析工具 206
9.4 本章小结 209
第10章 使用Dask分析大数据 211
10.1 Dask的执行模型 212
10.1.1 用于比较的pandas基线 212
10.1.2 实现基于Dask的数据帧解决方案 213
10.2 Dask操作的计算开销 215
10.2.1 处理数据分区 216
10.2.2 中间计算持久化 217
10.2.3 分布式数据帧上的算法实现 218
10.2.4 重新对数据进行分区 220
10.2.5 分布式数据帧持久化 223
10.3 使用Dask的分布式调度器 224
10.3.1 dask.distributed架构 225
10.3.2 使用dask.distributed运行代码 228
10.3.3 处理大于内存的数据集 232
10.4 本章小结 234
附录A 搭建环境 235
附录B 使用Numba生成高效的底层代码 239