Python加速程序运行的方法


Posted in Python onJuly 29, 2020

问题

你的程序运行太慢,你想在不使用复杂技术比如C扩展或JIT编译器的情况下加快程序运行速度。

解决方案

关于程序优化的第一个准则是“不要优化”,第二个准则是“不要优化那些无关紧要的部分”。 如果你的程序运行缓慢,首先你得使用14.13小节的技术先对它进行性能测试找到问题所在。

通常来讲你会发现你得程序在少数几个热点位置花费了大量时间, 比如内存的数据处理循环。一旦你定位到这些点,你就可以使用下面这些实用技术来加速程序运行。

使用函数

很多程序员刚开始会使用Python语言写一些简单脚本。 当编写脚本的时候,通常习惯了写毫无结构的代码,比如:

# somescript.py

import sys
import csv

with open(sys.argv[1]) as f:
   for row in csv.reader(f):

     # Some kind of processing
     pass

很少有人知道,像这样定义在全局范围的代码运行起来要比定义在函数中运行慢的多。 这种速度差异是由于局部变量和全局变量的实现方式(使用局部变量要更快些)。 因此,如果你想让程序运行更快些,只需要将脚本语句放入函数中即可:

# somescript.py
import sys
import csv

def main(filename):
  with open(filename) as f:
     for row in csv.reader(f):
       # Some kind of processing
       pass

main(sys.argv[1])

速度的差异取决于实际运行的程序,不过根据经验,使用函数带来15-30%的性能提升是很常见的。

尽可能去掉属性访问

每一次使用点(.)操作符来访问属性的时候会带来额外的开销。 它会触发特定的方法,比如 __getattribute__() __getattr__() ,这些方法会进行字典操作操作。

通常你可以使用 from module import name 这样的导入形式,以及使用绑定的方法。 假设你有如下的代码片段:

import math

def compute_roots(nums):
  result = []
  for n in nums:
    result.append(math.sqrt(n))
  return result

# Test
nums = range(1000000)
for n in range(100):
  r = compute_roots(nums)

在我们机器上面测试的时候,这个程序花费了大概40秒。现在我们修改 compute_roots() 函数如下:

from math import sqrt

def compute_roots(nums):

  result = []
  result_append = result.append
  for n in nums:
    result_append(sqrt(n))
  return result

修改后的版本运行时间大概是29秒。唯一不同之处就是消除了属性访问。 用 sqrt() 代替了 math.sqrt() The result.append() 方法被赋给一个局部变量 result_append ,然后在内部循环中使用它。

不过,这些改变只有在大量重复代码中才有意义,比如循环。 因此,这些优化也只是在某些特定地方才应该被使用。

理解局部变量

之前提过,局部变量会比全局变量运行速度快。 对于频繁访问的名称,通过将这些名称变成局部变量可以加速程序运行。 例如,看下之前对于 compute_roots() 函数进行修改后的版本:

import math

def compute_roots(nums):
  sqrt = math.sqrt
  result = []
  result_append = result.append
  for n in nums:
    result_append(sqrt(n))
  return result

在这个版本中,sqrtmath 模块被拿出并放入了一个局部变量中。 如果你运行这个代码,大概花费25秒(对于之前29秒又是一个改进)。 这个额外的加速原因是因为对于局部变量 sqrt 的查找要快于全局变量 sqrt

对于类中的属性访问也同样适用于这个原理。 通常来讲,查找某个值比如 self.name 会比访问一个局部变量要慢一些。 在内部循环中,可以将某个需要频繁访问的属性放入到一个局部变量中。例如:

# Slower
class SomeClass:
  ...
  def method(self):
     for x in s:
       op(self.value)

# Faster
class SomeClass:

  ...
  def method(self):
     value = self.value
     for x in s:
       op(value)

避免不必要的抽象

任何时候当你使用额外的处理层(比如装饰器、属性访问、描述器)去包装你的代码时,都会让程序运行变慢。 比如看下如下的这个类:

class A:
  def __init__(self, x, y):
    self.x = x
    self.y = y
  @property
  def y(self):
    return self._y
  @y.setter
  def y(self, value):
    self._y = value

现在进行一个简单测试:

>>> from timeit import timeit
>>> a = A(1,2)
>>> timeit('a.x', 'from __main__ import a')
0.07817923510447145
>>> timeit('a.y', 'from __main__ import a')
0.35766440676525235
>>>

可以看到,访问属性y相比属性x而言慢的不止一点点,大概慢了4.5倍。 如果你在意性能的话,那么就需要重新审视下对于y的属性访问器的定义是否真的有必要了。 如果没有必要,就使用简单属性吧。 如果仅仅是因为其他编程语言需要使用getter/setter函数就去修改代码风格,这个真的没有必要。

使用内置的容器

内置的数据类型比如字符串、元组、列表、集合和字典都是使用C来实现的,运行起来非常快。 如果你想自己实现新的数据结构(比如链接列表、平衡树等), 那么要想在性能上达到内置的速度几乎不可能,因此,还是乖乖的使用内置的吧。

避免创建不必要的数据结构或复制

有时候程序员想显摆下,构造一些并没有必要的数据结构。例如,有人可能会像下面这样写:

values = [x for x in sequence]
squares = [x*x for x in values]

也许这里的想法是首先将一些值收集到一个列表中,然后使用列表推导来执行操作。 不过,第一个列表完全没有必要,可以简单的像下面这样写:

squares = [x*x for x in sequence]

与此相关,还要注意下那些对Python的共享数据机制过于偏执的程序所写的代码。 有些人并没有很好的理解或信任Python的内存模型,滥用 copy.deepcopy() 之类的函数。 通常在这些代码中是可以去掉复制操作的。

讨论

在优化之前,有必要先研究下使用的算法。 选择一个复杂度为 O(n log n) 的算法要比你去调整一个复杂度为 O(n**2) 的算法所带来的性能提升要大得多。

如果你觉得你还是得进行优化,那么请从整体考虑。 作为一般准则,不要对程序的每一个部分都去优化,因为这些修改会导致代码难以阅读和理解。 你应该专注于优化产生性能瓶颈的地方,比如内部循环。

你还要注意微小优化的结果。例如考虑下面创建一个字典的两种方式:

a = {
  'name' : 'AAPL',
  'shares' : 100,
  'price' : 534.22
}

b = dict(name='AAPL', shares=100, price=534.22)

后面一种写法更简洁一些(你不需要在关键字上输入引号)。 不过,如果你将这两个代码片段进行性能测试对比时,会发现使用 dict() 的方式会慢了3倍。 看到这个,你是不是有冲动把所有使用 dict() 的代码都替换成第一种。 不够,聪明的程序员只会关注他应该关注的地方,比如内部循环。在其他地方,这点性能损失没有什么影响。

如果你的优化要求比较高,本节的这些简单技术满足不了,那么你可以研究下基于即时编译(JIT)技术的一些工具。 例如,PyPy工程是Python解释器的另外一种实现,它会分析你的程序运行并对那些频繁执行的部分生成本机机器码。 它有时候能极大的提升性能,通常可以接近C代码的速度。 不过可惜的是,到写这本书为止,PyPy还不能完全支持Python3. 因此,这个是你将来需要去研究的。你还可以考虑下Numba工程, Numba是一个在你使用装饰器来选择Python函数进行优化时的动态编译器。 这些函数会使用LLVM被编译成本地机器码。它同样可以极大的提升性能。 但是,跟PyPy一样,它对于Python 3的支持现在还停留在实验阶段。

最后我引用John Ousterhout说过的话作为结尾:“最好的性能优化是从不工作到工作状态的迁移”。 直到你真的需要优化的时候再去考虑它。确保你程序正确的运行通常比让它运行更快要更重要一些(至少开始是这样的).

以上就是Python加速程序运行的方法的详细内容,更多关于Python加速程序运行的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
在Python的Django框架中实现Hacker News的一些功能
Apr 17 Python
使用rpclib进行Python网络编程时的注释问题
May 06 Python
python Socket之客户端和服务端握手详解
Sep 18 Python
python中in在list和dict中查找效率的对比分析
May 04 Python
python 遍历目录(包括子目录)下所有文件的实例
Jul 11 Python
Python wxpython模块响应鼠标拖动事件操作示例
Aug 23 Python
Linux系统(CentOS)下python2.7.10安装
Sep 26 Python
在PyCharm中三步完成PyPy解释器的配置的方法
Oct 29 Python
python输出决策树图形的例子
Aug 09 Python
Python整数与Numpy数据溢出问题解决
Sep 11 Python
python中的unittest框架实例详解
Feb 05 Python
python实现学员管理系统(面向对象版)
Jun 05 Python
如何在python中判断变量的类型
Jul 29 #Python
Python中的With语句的使用及原理
Jul 29 #Python
解决c++调用python中文乱码问题
Jul 29 #Python
Python 实现简单的客户端认证
Jul 29 #Python
Tensorflow使用Anaconda、pycharm安装记录
Jul 29 #Python
学python爬虫能做什么
Jul 29 #Python
Python 创建TCP服务器的方法
Jul 28 #Python
You might like
PHP base64+gzinflate压缩编码和解码代码
2008/10/03 PHP
基于php无限分类的深入理解
2013/06/02 PHP
PHP进程同步代码实例
2015/02/12 PHP
php判断访问IP的方法
2015/06/19 PHP
jQuery ajax BUG:object doesn't support this property or method
2010/07/06 Javascript
jquery如何通过name名称获取当前name的value值
2013/12/20 Javascript
元素未显示设置width/height时IE中使用currentStyle获取为auto
2014/05/04 Javascript
jQuery搜索子元素的方法
2015/02/10 Javascript
JavaScript控制浏览器全屏及各种浏览器全屏模式的方法、属性和事件
2015/12/20 Javascript
javascript跑马灯抽奖实例讲解
2020/04/17 Javascript
javascript原型继承工作原理和实例详解
2016/04/07 Javascript
利用JS实现点击按钮后图片自动切换的简单方法
2016/10/24 Javascript
js中getBoundingClientRect的作用及兼容方案详解
2018/02/01 Javascript
javascript标准库(js的标准内置对象)总结
2018/05/26 Javascript
详解关于JSON.parse()和JSON.stringify()的性能小测试
2019/03/14 Javascript
python网络编程学习笔记(10):webpy框架
2014/06/09 Python
python下PyGame的下载与安装过程及遇到问题
2019/08/04 Python
CSS3实例分享--超炫checkbox复选框和radio单选框
2014/09/01 HTML / CSS
时尚圣经:The Fashion Bible
2019/03/03 全球购物
洲际酒店集团英国官网:IHG英国
2019/07/10 全球购物
意大利奢侈品零售商:ilDuomo Novara
2019/09/11 全球购物
在c#中using和new这两个关键字有什么意义
2013/05/19 面试题
如何用Java判断一个文件或目录是否存在
2012/11/19 面试题
英文版区域经理求职信
2013/10/23 职场文书
考试违纪检讨书
2014/02/02 职场文书
销售职业生涯规划范文
2014/03/14 职场文书
教学改革实施方案
2014/03/31 职场文书
交通事故赔偿协议书
2014/04/15 职场文书
英文推荐信格式范文
2014/05/09 职场文书
公司地址变更通知
2015/04/25 职场文书
谢师宴学生致辞
2015/07/27 职场文书
PyQt5 显示超清高分辨率图片的方法
2021/04/11 Python
mysql数据库入门第一步之创建表
2021/05/14 MySQL
Logback 使用TurboFilter实现日志级别等内容的动态修改操作
2021/08/30 Java/Android
Spring IOC容器Bean的作用域及生命周期实例
2022/05/30 Java/Android
3050和2060哪个好 性能差多少 差距有多大 谁更有性价比
2022/06/17 数码科技