Python在循环中优化函数调用吗?

17 投票
5 回答
4651 浏览
提问于 2025-04-17 00:41

假设我有一段代码,它在循环中调用某个函数上百万次,我希望这段代码运行得更快:

def outer_function(file):
    for line in file:
        inner_function(line)

def inner_function(line):
    # do something
    pass

这不一定是文件处理的情况,比如说一个绘制点的函数可能会被绘制线的函数调用。逻辑上这两个函数应该是分开的,但从性能的角度来看,它们应该尽可能快地一起工作。

那么,Python会自动检测并优化这些情况吗?如果不会,有没有办法给它一些提示,让它这样做?比如使用一些额外的外部优化工具?...

5 个回答

5

如果你说的“Python”是指CPython,也就是大家常用的那种实现,那么答案是否定的。

但如果你说的“Python”是指任何一种Python语言的实现,那答案就是肯定的。比如PyPy,它可以进行很多优化,我相信它的JIT方法可以处理这种情况。

13

哪种 Python?PyPy 的 JIT 编译器在执行了几百次或几十次(具体取决于每次迭代执行了多少个操作码)后,会开始追踪执行过程,忽略掉 Python 函数调用的细节,然后把收集到的信息编译成一段优化过的机器代码。这段代码可能完全不包含导致函数调用发生的逻辑。追踪是线性的,JIT 的后端甚至不知道发生过函数调用,它只看到两个函数的指令混合在一起被执行。(这是最理想的情况,比如在循环中有分支,或者所有迭代都走同一条分支。有些代码不适合这种 JIT 编译,导致追踪很快失效,没能带来太多速度提升,不过这种情况比较少见。)

而 CPython,很多人提到的“Python”或 Python 解释器,其实没那么聪明。它是一个简单的字节码虚拟机,会在每次迭代中忠实地执行与函数调用相关的逻辑。但是,如果性能真的那么重要,为什么还要用解释器呢?如果想尽量减少这种开销,可以考虑把这个频繁执行的循环用原生代码写出来(比如作为 C 扩展或者用 Cython)。

不过,除非你每次迭代只进行一点点数字运算,否则无论哪种方式都不会有太大的性能提升。

15

Python 不会把函数调用直接嵌入到代码里,这是因为它的动态特性。理论上,inner_function 可能会做一些事情,把名字 inner_function 重新绑定到其他东西上——Python 在编译时无法知道这种情况会发生。例如:

def func1():
    global inner_func
    inner_func = func2
    print 1

def func2():
    print 2

inner_func = func1

for i in range(5):
    inner_func()

输出结果是:

1
2
2
2
2

你可能会觉得这很糟糕。但再想想——Python 的函数式和动态特性其实是它最吸引人的地方之一。Python 允许做很多事情,但这往往会影响性能,而在大多数情况下,这种影响是可以接受的。

不过,你可以尝试用像 byteplay 这样的工具来搞定它——把内部函数拆解成字节码,然后插入到外部函数中,再重新组合。如果你的代码对性能要求这么高,值得去做这些复杂的操作,那不如直接用 C 语言重写。Python 对外部函数接口(FFI)支持得很好。


以上内容都是针对官方的 CPython 实现。 理论上,像 PyPy 或已经停止更新的 Unladen Swallow 这样的运行时 JIT 解释器可以检测到正常情况并进行嵌入。不过,我对 PyPy 不够了解,不知道它是否真的这样做,但它确实有这个能力。

撰写回答