优化Python代码
4 个回答
这个回答展示了我如何找到需要优化的代码。假设有一行代码你可以替换掉,而这行代码大约占用了40%的时间。也就是说,它在调用栈上占用了40%的时间。 如果你对调用栈进行10次取样,这行代码大概会出现在其中的4次左右。其实,出现的次数并不重要。只要它在两次或更多次的取样中出现,并且你可以替换掉它,你就能节省掉它所消耗的时间。
首先,你需要对你的代码进行性能分析,这样才能知道问题出在哪里。有很多方法可以做到这一点,这里有一个例子: https://codereview.stackexchange.com/questions/3393/im-trying-to-understand-how-to-make-my-application-more-efficient
你在代码中进行了很多索引访问,比如:
for pair in range(i-1, j):
if coordinates[pair][0] >= 0 and coordinates[pair][1] >= 0:
这段代码可以更简单地写成:
for coord in coordinates[i-1:j]:
if coord[0] >= 0 and cood[1] >= 0:
列表推导式很酷,也很“Python风格”,但这段代码如果不创建4个列表,可能会运行得更快:
N = int(raw_input())
coordinates = []
coordinates = [raw_input() for i in xrange(N)]
coordinates = [pair.split(" ") for pair in coordinates]
coordinates = [[int(pair[0]), int(pair[1])] for pair in coordinates]
我建议把这些操作合并成一个简单的循环,或者如果你真的想用列表推导式,可以把多个转换封装成一个函数,这个函数可以处理原始输入数据。
如果你想问的是如何优化Python代码(我觉得这个问题很重要;)那么有很多有趣的方法可以尝试,但首先:
你可能不需要过于 obsessively 优化 Python 代码!如果你已经在用最快的算法解决问题,但 Python 运行得还是不够快,那你可能应该考虑换一种语言。
不过,确实有几种方法可以让 Python 代码更快(因为有时候你真的想让 Python 代码更快):
性能分析(先做这个!)
有很多方法可以分析 Python 代码的性能,但我会提到两种:cProfile
(或 profile
模块)和 PyCallGraph
。
cProfile
这个是你应该使用的,虽然解读结果可能有点复杂。它的工作原理是记录每个函数的进入和退出时间,以及调用这个函数的其他函数(并跟踪异常)。
你可以这样在 cProfile 中运行一个函数:
import cProfile
cProfile.run('myFunction()', 'myFunction.profile')
然后查看结果:
import pstats
stats = pstats.Stats('myFunction.profile')
stats.strip_dirs().sort_stats('time').print_stats()
这会告诉你大部分时间花在哪些函数上。
PyCallGraph
PyCallGraph
提供了一种更直观、也许更简单的方式来分析 Python 程序的性能——它是理解程序中时间花费的好方法,但它会增加执行的开销。
要运行 PyCallGraph:
pycallgraph graphviz ./myprogram.py
很简单!你会得到一个 PNG 格式的图像作为输出(可能需要等一会儿...)
使用库
如果你在 Python 中尝试做的事情已经有现成的模块(甚至可能在标准库中),那就直接使用那个模块吧!
大多数标准库模块都是用 C 写的,它们的执行速度比相应的 Python 实现快几百倍,比如说 二分查找。
让解释器尽量多帮你做事
解释器会为你做一些事情,比如循环。真的?没错!你可以使用 map
、reduce
和 filter
这些关键字来显著加快紧密循环的速度:
考虑:
for x in xrange(0, 100):
doSomethingWithX(x)
和:
map(doSomethingWithX, xrange(0,100))
显然,前者可能会更快,因为解释器只需要处理一条语句,而不是两条,但这有点模糊……实际上,这样更快有两个原因:
- 所有的流程控制(比如我们是否完成了循环……)都是在解释器中完成的
- doSomethingWithX 函数名只解析一次
在 for 循环中,每次循环时 Python 都要检查 doSomethingWithX
函数的位置!即使有缓存,这也是一种开销。
记住 Python 是一种解释型语言
(注意,这一部分确实是关于一些微小的优化,不要让这些影响你正常、可读的编码风格!)如果你来自编译语言的背景,比如 C 或 Fortran,那么 Python 不同语句的性能可能会让你感到惊讶:
try:
是便宜的, if
是昂贵的
如果你的代码是这样的:
if somethingcrazy_happened:
uhOhBetterDoSomething()
else:
doWhatWeNormallyDo()
而 doWhatWeNormallyDo()
如果发生了什么疯狂的事情会抛出异常,那么将代码安排成这样会更快:
try:
doWhatWeNormallyDo()
except SomethingCrazy:
uhOhBetterDoSomething()
为什么?因为解释器可以直接开始执行你通常的操作;在第一种情况下,解释器每次执行 if 语句时都必须进行符号查找,因为这个名字可能在上次执行后发生了变化!(而且,特别是如果 somethingcrazy_happened
是 global
的话,查找名字的开销可能不小)。
你是说谁??
由于名字查找的成本,将全局值缓存到函数中,或者将简单的布尔测试直接写入函数中可能会更好:
未优化的函数:
def foo():
if condition_that_rarely_changes:
doSomething()
else:
doSomethingElse()
优化的方法,不用变量,利用解释器本身在查找函数名的特点!
当条件为真时:
foo = doSomething # now foo() calls doSomething()
当条件为假时:
foo = doSomethingElse # now foo() calls doSomethingElse()
PyPy
PyPy 是用 Python 编写的 Python 实现。难道这意味着它的代码运行会慢得多吗?其实不是。PyPy 实际上使用了即时编译器(JIT)来运行 Python 程序。
如果你不使用任何外部库(或者你使用的库与 PyPy 兼容),那么这是一个非常简单的方法,可以(几乎肯定地)加速你程序中的重复任务。
基本上,JIT 可以生成代码,执行的速度比 Python 解释器快得多,因为它是为特定情况生成的,而不是处理所有可能的合法 Python 表达式。
接下来该看哪里
当然,你首先应该考虑改善你的算法和数据结构,考虑缓存,或者甚至思考一下你是否真的需要做这么多事情,但无论如何:
还有很多东西,即使是我自己有限的经验中也有遗漏,但这个回答已经够长了!
这些都是基于我最近在一些 速度不够快 的 Python 代码中的经验,我想再次强调,我并不认为我建议的任何方法都是好主意,但有时候,你不得不这样做……