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的ORM框架SQLObject入门实例
Apr 28 Python
寻找网站后台地址的python脚本
Sep 01 Python
Pycharm学习教程(2) 代码风格
May 02 Python
python 重定向获取真实url的方法
May 11 Python
把csv文件转化为数组及数组的切片方法
Jul 04 Python
python爬取百度贴吧前1000页内容(requests库面向对象思想实现)
Aug 10 Python
python 中值滤波,椒盐去噪,图片增强实例
Dec 18 Python
浅谈selenium如何应对网页内容需要鼠标滚动加载的问题
Mar 14 Python
基于pycharm实现批量修改变量名
Jun 02 Python
Pandas把dataframe或series转换成list的方法
Jun 14 Python
浅谈matplotlib中FigureCanvasXAgg的用法
Jun 16 Python
selenium与xpath之获取指定位置的元素的实现
Jan 26 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循环语句笔记(foreach,list)
2011/11/29 PHP
Laravel框架中扩展函数、扩展自定义类的方法
2014/09/04 PHP
iis6手工创建网站后无法运行php脚本的解决方法
2017/06/08 PHP
phpstorm 配置xdebug的示例代码
2019/03/31 PHP
laravel通用化的CURD的实现
2019/12/13 PHP
JavaScript静态的动态
2006/09/18 Javascript
js技巧--转义符"\"的妙用
2007/01/09 Javascript
一个可以随意添加多个序列的tag函数
2009/07/21 Javascript
JavaScript中数组对象的那些自带方法介绍
2013/03/12 Javascript
浅谈jquery中的each方法$.each、this.each、$.fn.each
2016/06/23 Javascript
javascript设计模式Constructor(构造器)模式
2016/08/19 Javascript
JS常用函数和常用技巧小结
2016/10/15 Javascript
Mongoose学习全面理解(推荐)
2017/01/21 Javascript
微信小程序 判断手机号的实现代码
2017/04/19 Javascript
深入理解AngularJs-scope的脏检查(一)
2017/06/19 Javascript
bootstrap基本配置_动力节点Java学院整理
2017/07/14 Javascript
使用JS编写的随机抽取号码的小程序
2017/08/11 Javascript
基于js 字符串indexof与search方法的区别(详解)
2017/12/04 Javascript
全新打包工具parcel零配置vue开发脚手架
2018/01/11 Javascript
Webstorm2016使用技巧(SVN插件使用)
2018/10/29 Javascript
学习使用ExpressJS 4.0中的新Router的用法
2018/11/06 Javascript
微信小程序实现张图片合成为一张并下载
2019/07/16 Javascript
vue.js购物车添加商品组件的方法
2019/09/17 Javascript
Python HTMLParser模块解析html获取url实例
2015/04/08 Python
tensorflow入门之训练简单的神经网络方法
2018/02/26 Python
Python基于辗转相除法求解最大公约数的方法示例
2018/04/04 Python
python之生产者消费者模型实现详解
2019/07/27 Python
在macOS上搭建python环境的实现方法
2019/08/13 Python
Python开发之身份证验证库id_validator验证身份证号合法性及根据身份证号返回住址年龄等信息
2020/03/20 Python
python实现网页录音效果
2020/10/26 Python
纯CSS3实现给头像加个光芒四射且旋转的背景动画效果
2014/05/07 HTML / CSS
印尼美容产品购物网站:PerfectBeauty.id
2017/12/01 全球购物
平面设计自荐信
2013/10/07 职场文书
《四季》教学反思
2014/04/08 职场文书
纪检干部个人对照检查材料
2014/09/23 职场文书
单位委托书格式范本
2014/09/29 职场文书