优化Python代码

20 投票
4 回答
42614 浏览
提问于 2025-04-17 00:13

我最近在InterviewStreet.com上做一个编程挑战,遇到了一些效率问题。有没有人能建议我哪里可以改代码,让它运行得更快、更高效呢?

这是我的代码

如果你感兴趣的话,这是问题的描述

4 个回答

2

这个回答展示了我如何找到需要优化的代码。假设有一行代码你可以替换掉,而这行代码大约占用了40%的时间。也就是说,它在调用栈上占用了40%的时间。 如果你对调用栈进行10次取样,这行代码大概会出现在其中的4次左右。其实,出现的次数并不重要。只要它在两次或更多次的取样中出现,并且你可以替换掉它,你就能节省掉它所消耗的时间。

3

首先,你需要对你的代码进行性能分析,这样才能知道问题出在哪里。有很多方法可以做到这一点,这里有一个例子: 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]

我建议把这些操作合并成一个简单的循环,或者如果你真的想用列表推导式,可以把多个转换封装成一个函数,这个函数可以处理原始输入数据。

108

如果你想问的是如何优化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 实现快几百倍,比如说 二分查找

让解释器尽量多帮你做事

解释器会为你做一些事情,比如循环。真的?没错!你可以使用 mapreducefilter 这些关键字来显著加快紧密循环的速度:

考虑:

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_happenedglobal 的话,查找名字的开销可能不小)。

你是说谁??

由于名字查找的成本,将全局值缓存到函数中,或者将简单的布尔测试直接写入函数中可能会更好:

未优化的函数:

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 代码的信息,尽管有些内容可能有点过时。

  • 这是 BDFL 本人 关于优化循环的看法。

还有很多东西,即使是我自己有限的经验中也有遗漏,但这个回答已经够长了!

这些都是基于我最近在一些 速度不够快 的 Python 代码中的经验,我想再次强调,我并不认为我建议的任何方法都是好主意,但有时候,你不得不这样做……

撰写回答