有很多方法可以对Python代码进行分析,但我将提到两种:cProfile
(或profile
)模块和PyCallGraph
。
这是你应该使用的,尽管解释结果可能有点令人生畏。它通过记录每个函数何时进入或退出以及调用函数是什么(并跟踪异常)来工作。
你可以像这样在cProfile中运行一个函数:
import cProfile
cProfile.run('myFunction()', 'myFunction.profile')
import pstats
stats = pstats.Stats('myFunction.profile')
stats.strip_dirs().sort_stats('time').print_stats()
这将向您展示程序中大部分时间花费在哪些函数中。
PyCallGraph
提供了一种最美观、最简单的方式来对 Python 程序进行分析,以便了解程序中时间的分配情况。然而,它会增加显著的执行开销。
运行 pycallgraph 的方法如下:
pycallgraph graphviz ./myprogram.py
简单!你将获得一个png图像作为输出(也许需要一段时间...)
如果你想在Python中实现某些功能,而已经存在一个模块(甚至是标准库中的),那么请使用该模块!
大多数标准库模块都是用C编写的,它们将比等效的Python实现(例如二分搜索)快上数百倍。
解释器会为你做一些事情,比如循环。真的吗?是的!你可以使用map
、reduce
和filter
关键字来显著加速紧密循环:
考虑:
for x in xrange(0, 100):
doSomethingWithX(x)
vs:
map(doSomethingWithX, xrange(0,100))
显然,这会更快,因为解释器只需要处理单个语句而不是两个,但这有点模糊...事实上,这有两个原因更快:
在for循环中,每次循环时Python都必须检查doSomethingWithX
函数的确切位置!即使使用缓存也会有一定的开销。
(请注意,本节内容主要涉及微小的优化,您不应该让它们影响您的正常、易读的编码风格!) 如果您来自编译语言(像C或Fortran)的编程背景,则某些关于不同Python语句性能的事情可能会令人惊讶:
try:
操作廉价,if
操作昂贵如果您有以下代码:
if somethingcrazy_happened:
uhOhBetterDoSomething()
else:
doWhatWeNormallyDo()
如果发生了什么疯狂的事情,doWhatWeNormallyDo()
将会抛出异常,那么将代码排列成这样会更快:
try:
doWhatWeNormallyDo()
except SomethingCrazy:
uhOhBetterDoSomething()
为什么?因为解释器可以直接开始执行你通常做的事情;而在第一种情况下,每次执行if语句时,解释器都必须进行符号查找,因为自上次执行该语句以来,名称可能指代不同的内容!(如果somethingcrazy_happened是全局的,名称查找可能会很困难。)
由于名称查找的成本,将全局值缓存到函数中,并将简单的布尔测试内置到此类函数中,也可以更好地优化:
未优化的函数:
def foo():
if condition_that_rarely_changes:
doSomething()
else:
doSomethingElse()
优化方案是利用解释器已经在查找函数名称的事实,而不是使用变量!
当条件成立时:
foo = doSomething # now foo() calls doSomething()
当条件变为假时:
foo = doSomethingElse # now foo() calls doSomethingElse()
PyPy是用Python编写的Python实现。这是否意味着它会无限慢地运行代码?其实不是这样的。PyPy实际上使用了即时编译器(JIT)来运行Python程序。
如果您不使用任何外部库(或者您使用的库与PyPy兼容),那么这是一种极为简单的方法,可以(几乎肯定地)加速程序中重复的任务。
基本上,JIT可以生成将执行Python解释器所需操作的代码,但速度要快得多,因为它是为单个情况生成的,而不必处理每个可能的合法Python表达式。
当然,你应该首先优化你的算法和数据结构,考虑像缓存这样的东西,甚至是否需要在第一时间做这么多事情,但无论如何:
这个页面提供了大量有关如何加速Python代码的信息,尽管其中一些已经过时了。
这里是BDFL himself关于优化循环的主题。
从我的有限经验中,还有很多事情我没有提到,但是这个答案已经够长了!
这完全基于我的最近一些使用Python代码时,速度不够快的经验。我想再次强调,我并不认为我提出的任何建议都是好主意,但有时候你必须...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:
列表推导式很酷,也很“Pythonic”,但是如果您不创建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]