Python猫荐书系列之五:Python高性能编程

稍微关心编程语言的使用趋势的人都知道,最近几年,国内最火的两种语言非 Python 与 Go 莫属,于是,隔三差五就会有人问:这两种语言谁更厉害/好找工作/高工资……

对于编程语言的争论,就是猿界的生理周期,每个月都要闹上一回。到了年末,各类榜单也是特别抓人眼球,闹得更凶。

其实,它们各有对方所无法比拟的优势以及用武之地,很多争论都是没有必要的。身为一个正在努力学习 Python 的(准)中年程序员,我觉得吧,先把一门语言精进了再说。没有差劲的语言,只有差劲的程序员,等真的把语言学好了,必定是“山重水复疑无路,柳暗花明又一村”。

铺垫已了,进入今天的正题,Python 猫荐书系列之五——

Python高性能编程

本书适合已入门 Python、还想要进阶和提高的读者阅读。

所有计算机语言说到底都是在硬件层面的数据操作,所以高性能编程的一个终极目标可以说是“高性能硬件编程”。然而,Python 是一门高度抽象的计算机语言,它的一大优势是开发团队的高效,不可否认地存在这样或那样的设计缺陷,以及由于开发者的水平而造成的人为的性能缺陷。

本书的一大目的就是通过介绍各种模块和原理,来促成在快速开发 Python 的同时避免很多性能局限,既减低开发及维护成本,又收获系统的高效。

1、性能分析是基础

首先的一个关键就是性能分析,借此可以找到性能的瓶颈,使得性能调优做到事半功倍。

性能调优能够让你的代码能够跑得“足够快”以及“足够瘦”。性能分析能够让你用最小的代价做出最实用的决定。

书中介绍了几种性能分析的工具:

(1)基本技术如 IPython 的 %timeit 魔法函数、time.time()、以及一个计时修饰器,使用这些技术来了解语句和函数的行为。

(2)内置工具如 cProfile,了解代码中哪些函数耗时最长,并用 runsnake 进行可视化。

(3)line_profiler 工具,对选定的函数进行逐行分析,其结果包含每行被调用的次数以及每行花费的时间百分比。

(4)memory_profiler 工具,以图的形式展示RAM的使用情况随时间的变化,解释为什么某个函数占用了比预期更多的 RAM。

(5)Guppy 项目的 heapy 工具,查看 Python 堆中对象的数量以及每个对象的大小,这对于消灭奇怪的内存泄漏特别有用。

(6)dowser 工具,通过Web浏览器界面审查一个持续运行的进程中的实时对象。

(7)dis 模块,查看 CPython 的字节码,了解基于栈的 Python 虚拟机如何运行。

(8)单元测试,在性能分析时要避免由优化手段带来的破坏性后果。

作者强调了性能分析的重要性,同时也对如何确保性能分析的成功提了醒,例如,将测试代码与主体代码分离、避免硬件条件的干扰(如在BIOS上禁用了TurboBoost、禁用了操作系统改写SpeedStep、只使用主电源等)、运行实验时禁用后台工具如备份和Dropbox、多次实验、重启并重跑实验来二次验证结果,等等。

性能分析对于高性能编程的作用,就好比复杂度分析对于算法的作用,它本身不是高性能编程的一部分,但却是最终有效的一种评判标准。

2、数据结构的影响

高性能编程最重要的事情是了解数据结构所能提供的性能保证。

高性能编程的很大一部分是了解你查询数据的方式,并选择一个能够迅速响应这个查询的数据结构。

书中主要分析了 4 种数据结构:列表和元组就类似于其它编程语言的数组,主要用于存储具有内在次序的数据;而字典和集合就类似其它编程语言的哈希表/散列集,主要用于存储无序的数据。

本书在介绍相关内容的时候很克制,所介绍的都是些影响“速度更快、开销更低”的内容,例如:内置的 Tim 排序算法、列表的 resize 操作带来的超额分配的开销、元组的内存滞留(intern机制)带来的资源优化、散列函数与嗅探函数的工作原理、散列碰撞带来的麻烦与应对、Python 命名空间的管理,等等。

散列碰撞的结果

理解了这些内容,就能更加了解在什么情况下使用什么数据结构,以及如何优化这些数据结构的性能。

另外,关于这 4 种数据结构,书中还得出了一些有趣的结论:对于一个拥有100 000 000个元素的大列表,实际分配的可能是112 500 007个元素;初始化一个列表比初始化一个元组慢5.1 倍;字典或集合默认的最小长度是8(也就是说,即使你只保存3个值,Python仍然会分配 8 个元素)、对于有限大小的字典不存在一个最佳的散列函数。

3、矩阵和矢量计算

矢量计算是计算机工作原理不可或缺的部分,也是在芯片层次上对程序进行加速所必须了解的部分。

然而,原生 Python 并不支持矢量操作,因为 Python 列表存储的不是实际的数据,而是对实际数据的引用。在矢量和矩阵操作时,这种存储结构会造成极大的性能下降。比如,grid[5][2] 中的两个数字其实是索引值,程序需要根据索引值进行两次查找,才能获得实际的数据。

同时,因为数据被分片存储,我们只能分别对每一片进行传输,而不是一次性传输整个块,因此,内存传输的开销也很大。

减少瓶颈最好的方法是让代码知道如何分配我们的内存以及如何使用我们的数据进行计算。

Numpy 能够将数据连续存储在内存中并支持数据的矢量操作,在数据处理方面,它是高性能编程的最佳解决方案之一。

Numpy 带来性能提升的关键在于,它使用了高度优化且特殊构建的对象,取代了通用的列表结构来处理数组,由此减少了内存碎片;此外,自动矢量化的数学操作使得矩阵计算非常高效。

Numpy 在矢量操作上的缺陷是一次只能处理一个操作。例如,当我们做 A * B + C 这样的矢量操作时,先要等待 A * B 操作完成,并保存数据在一个临时矢量中,然后再将这个新的矢量和 C 相加。

Numexpr 模块可以将矢量表达式编译成非常高效的代码,可以将缓存失效以及临时变量的数量最小化。另外,它还能利用多核 CPU 以及 Intel 芯片专用的指令集来将速度最大化。

书中尝试了多种优化方法的组合,通过详细的分析,展示了高性能编程所能带来的性能提升效果。

4、编译器

书中提出一个观点:让你的代码运行更快的最简单的办法就是让它做更少的工作。

编译器把代码编译成机器码,是提高性能的关键组成部分。

不同的编译器有什么优势呢,它们对于性能提升会带来多少好处呢?书中主要介绍了如下编译工具:

  • Cython ——这是编译成C最通用的工具,覆盖了Numpy和普通的Python代码(需要一些C语言的知识)。
  • Shed Skin —— 一个用于非Numpy代码的,自动把Python转换成C的转换器。
  • Numba —— 一个专用于Numpy代码的新编译器。
  • Pythran —— 一个用于Numpy和非numpy代码的新编译器。
  • PyPy —— 一个用于非Numpy代码的,取代常规Python可执行程序的稳定的即时编译器。

书中分析了这几种编译器的工作原理、优化范围、以及适用场景等,是不错的入门介绍。此外,作者还提到了其它的编译工具,如Theano、Parakeet、PyViennaCL、ViennaCL、Nuitka 与 Pyston 等,它们各有取舍,在不同领域提供了支撑之力。

5、密集型任务

高性能编程的一个改进方向是提高密集型任务的处理效率,而这样的任务无非两大类:I/O 密集型与 CPU 密集型。

I/O 密集型任务主要是磁盘读写与网络通信任务,占用较多 I/O 时间,而对 CPU 要求较少;CPU 密集型任务恰恰相反,它们要消耗较多的 CPU 时间,进行大量的复杂的计算,例如计算圆周率与解析视频等。

改善 I/O 密集型任务的技术是异步编程 ,它使得程序在 I/O 阻塞时,并发执行其它任务,并通过“事件循环”机制来管理各项任务的运行时机,从而提升程序的执行效率。

书中介绍了三种异步编程的库:Gevent、Tornado 和 Asyncio,对三种模块的区别做了较多分析。

改善 CPU 密集型任务的主要方法是利用多核 CPU 进行多进程的运算。

Multiprocessing 模块使用基于进程和基于线程的并行处理,在队列上共享任务,以及在进程间共享数据,是处理 CPU 密集型任务的重要技术。

书中没有隐瞒它的局限性:Amdahl 定律揭示的优化限度、适应于单机多核而多机则有其它选择、全局解释锁 GIL 的束缚、以及进程间通信(同步数据和检查共享数据)的开销。针对进程间通信问题,书中还分析了多种解决方案,例如 Less Na?ve Pool、Manager、Redis、RawValue、MMap 等。

6、集群与现场教训

集群是一种多服务器运行相同任务的结构,也就是说,集群中的各节点提供相同的服务,其优点是系统扩展容易、具备容灾恢复能力。

集群需要克服的挑战有:机器间信息同步的延迟、机器间配置与性能的差异、机器的损耗与维护、其它难以预料的问题。书中列举了两个惨痛的教训:华尔街公司骑士资本由于软件升级引入的错误,损失4.62亿美元;Skype 公司 24 小时全球中断的严重事故。

书中给我们重点介绍了三个集群化解决方案:Parallel Python、IPython Parallel 和 NSQ。引申也介绍了一些普遍使用的方案,如 Celery、Gearman、PyRes、SQS。

关于现场教训,它们不仅仅是一些事故或者故事而已,由成功的公司所总结出来的经验更是来之不易的智慧。书中单独用一章内容分享了六篇文章,这些文章出自几个使用 Python 的公司/大型组织,像是Adaptive Lab、RadimRehurek、Smesh、PyPy 与 Lanyrd ,这些国外组织的一线实践经验,应该也能给国内的 Python 社区带来一些启示。

7、写在最后

众所周知,Python 应用前景大、简单易学、方便开发与部署,然而与其它编程语言相比,它的性能几乎总是落于下风。如何解决这个难题呢?本期荐书的书目就是一种回应。

《Python高性能编程》全书从微观到宏观对高性能编程的方方面面做了讲解,主要包含以下主题:计算机内部结构的背景知识、列表和元组、字典和集合、迭代器和生成器、矩阵和矢量计算、编译器、并发、集群和工作队列等。这些内容为编写更快的 Python 指明了答案。

本篇文章主要以梳理书中的内容要点为主,平均而兼顾地理清了全书脉络(PS:介绍得太面面俱到了,但愿不被指责为一篇流水账的读书笔记才好……)。我认为,鉴于书中谈及的这些话题,它就足以成为我们荐书栏目的一员了。除去某些句段的糟糕翻译、成书时间比较早(2014年)而造成的过时外,这本书总体质量不错,可称为是一份优秀的高性能编程的指引手册。

关于荐书栏目,我最后多说几句。本栏目原计划两周左右出一篇,但由于其它系列文章花费了我不少时间,而要写好一篇荐书/书评也特别费劲,最后生生造成了现在两月一更的尴尬局面……这篇文章是个错误的示范,我不该试图全面通读与概括其内容的。因此,我决定今后选一些易读的书目,在写作上也尽量走短小精悍风,希望能持续地将本栏目运作下去。若你有什么建议(如书目推荐、书评推荐、写作建议、甚至是投稿),我随时欢迎,先行致谢啦。

往期荐书回顾:
第一期:《编写高质量代码改善 Python 程序的 91 个建议
第二期:《Python最佳实践指南
第三期:《黑客与画家
第四期:《Python源码剖析

-----------------

本文原创并首发于微信公众号【Python猫】,后台回复“爱学习”,免费获得20+本精选电子书。

原文地址:https://www.cnblogs.com/pythonista/p/10263854.html

时间: 2024-10-11 10:31:34

Python猫荐书系列之五:Python高性能编程的相关文章

Python全栈自动化系列之Python编程基础(if条件判断)

一.if语句 1)单个if语句用法: 语法: if 条件: 条件成立执行的代码块 else: 条件不成立执行的代码块 例如: 需求点:用户输入考试成绩,请判断是否及格? num = int(input("请输入成绩:")) if num >= 60: print("考试及格") else: print("考试不及格")运行结果: 2)if-elif语句用法:语法: if 条件1: # 条件1成立执行的代码 elif 条件2: # 条件2成立

Python使用Zero-Copy和Buffer Protocol实现高性能编程

无论你程序是做什么的,它经常都需要处理大量的数据.这些数据大部分表现形式为strings(字符串).然而,当你对字符串大批量的拷贝,切片和修改操作时是相当低效的.为什么? 让我们假设一个读取二进制数据的大文件示例,然后将部分数据拷贝到另外一个文件.要展示该程序所使用的内存,我们使用memory_profiler,一个强大的Python包,让我们可以一行一行观察程序所使用的内存. @profile def read_random(): with open("/dev/urandom",

Python全栈自动化系列之Python编程基础(基础语法)

一.第一个Python程序: 1)pritnt函数使用,打印"Hello Python" 2)print函数默认是换行,若不想换行,可以使end=""实现 3)print函数可以打印多个你需要打印的内容 二.python中的 关键字 三.变量的命名规范 1)变量名可以由字母.数字.下划线(_)任意组合组成,注意不能以数字开头 2)变量名不能使用Python中的关键字,但可以包含关键字 3)变量名不能包含空格 4)变量名尽量做到见名知意 四.标识符(凡是我们自己起的命

Python全栈自动化系列之Python编程基础(while循环)

一.while循环 1)语法: while 条件: 代码块 改变条件的表达式 需求点:打印100遍hello python # 定义一个变量i用来记数,记录打印了多少遍hello python i = 0 while i<100: i = i + 1 print("这是第{}遍打印:hello python".format(i)) 运行结果: 2)死循环:内部条件一直满足(使用while循环时注意死循环) while True: print("hellp python&

Python全栈自动化系列之Python编程基础(模块和包)

一.模块 1)定义 模块:模块是一个Python文件,以.py结尾,包含了Python对象定义和Python函数包:Python中的包就是一个包含一个__init__.py文件的目录(文件夹) 2)模块的作用 a.模块让你能够有逻辑地组织你的Python代码段 b.把相关功能的代码写到一个模块里面能让你的代码更好用,更易懂 c.模块能定义函数.类和变量,模块里也能包含可执行的代码 注意点: ①在进行模块导入的时候,会将导入的模块从上往下执行一遍 ②模块导入时,同级目录导入,Pycharm有可能识

Python全栈自动化系列之Python编程基础(基本数据类型)

一.数值类型数据 1.整数(int):整数 2.浮点数(float):小数 3.布尔值(bool):只有两个值True和False 二.数据类型转换 1.整数和浮点数转换成字符串:使用str 2.字符串和浮点数转换成整数:使用int 3.整数和字符串转换成浮点数:使用float 4.整数.字符串以及浮点数转换成布尔类型:使用bool,转换成功后展示“True” 注意点:使用字符串去转换int以及float时,字符串里面不能含有字母 三.常见运算符 1.算术运算符:加+.减-.乘*.除/.取余%.

Python全栈自动化系列之Python编程基础(列表、元组、字典)

一.列表(list1 = [1,2,3,"蓝色海洋",“abcd”]) 1)列表的定义:列表使用[]来表示,列表中的元素可以是任意数据类型,列表中的元素使用逗号隔开 2)列表的基本操作:a.下标取值,例如:print(list1[1])   b.切片:切出来还是列表,例如:print(list1[1:3])   c.len方法:计算列表的长度,例如:len(list1) 3)列表的常用方法: 增加:①append方法:往尾部追加元素,例如:list1.append(66666)   ②

Python全栈自动化系列之Python编程基础(操作文件)

一.打开文件 open函数: 1)常见参数:第一个,要打开文件或者文件的路径:第二个参数,文件打开的模式,第三个参数,encoding,用来指定文件打开的编码格式(注意,使用rb模式时就不要使用了) 2)常见文件打开的模式: ①r:只读模式,读取普通文件 ②rb:只读模式,是以二进制的编码格式去打开文件,可以读取图片.视频等 3)参数一详解: ①当读取同级目录下的文件,可以直接写文件名 ②当读取的不在同一级目录下的文件时,必须要写上文件的绝对路径 注意:使用open这个方法时,读完文件记得使用c

Python全栈自动化系列之Python编程基础(异常捕获)

一.异常捕获关键字介绍 try:监测有可能出现异常的代码 except:捕获异常,对异常进行处理 else:没有发生异常的处理方式 finallay:不管代码有没有异常都执行 语法: try: # try下面放有可能会出现异常的代码excrpt: # except下面放捕获到异常之后处理的代码else: # else下面代码没有发生异常会执行finally: # finally不管代码有没有异常都执行 二.异常捕获时,在except后面可以指定捕获异常的类型 如果要捕获多个异常类型: 方式一:使