Python 是否会对循环中的函数调用进行优化?

17

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

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

def inner_function(line):
    # do something
    pass

这不一定是文件处理,例如可以从函数 drawing line 调用绘制点的函数。想法是逻辑上这两个函数必须分开,但从性能角度来看,它们应该尽可能快地一起运行。

Python 是否会自动检测和优化这些操作?如果不会,有没有办法让 Python 进行优化呢?也许可以使用一些外部优化器?……


1
在这里,"优化"是什么意思?内联吗? - Eli Bendersky
1
通常来说,函数调用的开销与函数体相比是可以忽略不计的。因此不确定是否值得这样做。 - Andrey
将 pass 函数或循环中花费的时间与从文件加载行并解析它们所需的时间进行比较,这两者哪个更耗时? - Luka Rahne
1
列表推导式会更糟糕,因为它需要构建一个结果列表。 - Marcelo Cantos
你是认真的吗?你想让代码运行快,但你却使用Python? - x13n
显示剩余2条评论
5个回答

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的工具将内部函数反汇编为字节码并插入外部函数中,然后重新组装以搭配使用。不过仔细想想,如果你的代码对性能要求足够高以至于需要进行这样的hack,那么最好还是用C重新编写它。Python提供了出色的FFI选项。 所有这些都与官方的CPython实现相关。 一个运行时-JIT解释器(例如PyPy或不幸已停止开发的Unladen Swallow)理论上可以检测到正常情况并执行内联操作。遗憾的是,我对PyPy不太熟悉,不知道它是否会这样做,但它肯定可以。

3
PyPy将内联调用,就像这样;我找不到文档,但在http://morepypy.blogspot.com/2011/02/pypy-faster-than-c-on-carefully-crafted.html上有一个愚蠢的例子。 - Katriel
@katriealex:没错,这很有道理。这种内联是一种相对简单的优化。JIT解释器做的事情要复杂得多。 - Eli Bendersky
+1,但公正地说,我仍然认为这很糟糕,我使用Python是因为它的其他吸引人功能,而不是因为它的动态特性。事实上,我很愿意放弃其中很多... - Konrad Rudolph
3
@phihag: 我不同意。今天(2011年8月)当人们说“Python”时,99%的可能是指官方实现。也许几年后情况会有所不同。 - Eli Bendersky
只需将其重写为C或Cython。 - endolith
显示剩余2条评论

13
哪种Python?PyPy的JIT编译器会在几百或几十次迭代后(取决于每次迭代执行的操作码数量)开始跟踪执行,沿途忘记Python函数调用,并将收集到的信息编译成一段优化的机器代码,该代码可能没有任何使函数调用本身发生的逻辑残留。跟踪是线性的,JIT的后端甚至不知道有一个函数调用,它只看到两个函数的指令混合在一起,就像它们被执行的那样。(这是完美的情况,例如在循环中存在分支或所有迭代都采用相同的分支。某些代码不适合这种JIT编译,会在产生大量加速之前很快使跟踪失效,尽管这种情况相当罕见。)
现在,CPython,许多人在谈论“Python”或Python解释器时所指的东西并不那么聪明。它是一个简单的字节码VM,会在每次迭代中忠实地执行与调用函数相关联的逻辑。但是再说一遍,如果性能如此重要,为什么还要使用解释器呢?如果需要尽可能地降低这种开销,请考虑使用本机代码(例如作为C扩展或在Cython中)编写热循环。
除非每次迭代只进行少量的数字计算,否则无论如何都不会有太大的改进。

5
如果你所说的“Python”是指通常使用的实现CPython,那么不行。
如果你所说的“Python”是指任何Python语言的实现,那么可以。PyPy 可以进行大量优化,我相信其方法JIT应该可以处理这种情况。

3

CPython(“标准”Python实现)不进行此类优化。

但是请注意,如果您正在计算函数调用的CPU周期,则可能对于您的问题,CPython不是正确的工具。 如果您可以100%确定要使用的算法已经是最佳算法(这是最重要的事情),并且您的计算确实是CPU限制的,则可以考虑以下选项:

  • 使用PyPy而不是CPython
  • 使用Cython
  • 编写C ++模块并将其与sip进行接口
  • 如果可能,请使用numpy simd方法实现算法
  • 如果可能,请使用例如PyCuda将计算移动到GPU硬件上

1

调用函数来调用pass语句显然会带来相当高的(∞)开销。你的真正程序是否遭受不必要的开销取决于内部函数的大小。如果它只是设置一个像素,那么我建议使用一种不同的方法,使用在C或C++等本地语言中编码的绘图原语。

对于Python,有一些(有点实验性的)JIT编译器可以优化函数调用,但主流的Python不会这样做。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接