【问题标题】:Does Python optimize function calls from loops?Python 是否优化循环中的函数调用?
【发布时间】:2011-11-06 18:59:32
【问题描述】:

说,我有一个代码,它从循环中调用了数百万次的函数,我希望代码更快:

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

def inner_function(line):
    # do something
    pass

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

Python 会自动检测和优化这些事情吗?如果没有 - 有没有办法给它一个线索呢?也许使用一些额外的外部优化器?...

【问题讨论】:

  • 这里的“优化”是什么意思?内联?
  • 与函数体相比,通常函数调用开销可以忽略不计。所以不确定是否值得。
  • 与从文件加载行并解析它们的时间相比,在传递函数或循环中花费的时间是多少?
  • 列表理解会更糟,因为它必须构建一个结果列表。
  • 认真的吗?您希望代码速度快,但您使用的是 Python?

标签: python optimization compiler-optimization


【解决方案1】:

由于其动态特性,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 实现有关。 运行时 JITting 解释器(如 PyPy 或可悲地不复存在的 Unladen Swallow)理论上可以检测正常情况并执行内联。唉,我对 PyPy 还不够熟悉,不知道它是否这样做,但它绝对可以。

【讨论】:

  • PyPy 会像这样内联调用;我找不到文档,但在 morepypy.blogspot.com/2011/02/… 有一个愚蠢的例子
  • @katriealex:是的,这是有道理的。这种内联是一种相对简单的优化。 JIT 口译员做更多有趣的事情
  • 该免责声明应该放在顶部 - 这是迄今为止此答案中最重要的声明。
  • +1,但公平地说,我仍然认为这很可怕,我使用 Python 是因为它的其他吸引人的特性,而不是因为它的动态特性。事实上,我很乐意放弃很多……
  • @phihag:我不同意。今天(2011 年 8 月)当一个人说“Python”时,有 99% 的机会他指的是官方实现。也许几年后情况会有所不同
【解决方案2】:

哪个 Python? PyPy 的 JIT 编译器将 - 在几百或几十次(取决于每次迭代执行多少操作码)迭代之后 - 开始跟踪执行,在此过程中忘记 Python 函数调用,并将收集到的信息编译成一段优化的机器代码可能没有 任何 使函数调用本身发生的逻辑残余。跟踪是线性的,JIT 的后端甚至不知道有函数调用,它只是看到两个函数的指令在执行时混合在一起。 (这是完美的情况,例如,当循环中有分支或所有迭代都采用相同的分支时。一些代码不适合这种 JIT 编译并在它们产生很大的加速之前迅速使跟踪无效,尽管这是相当很少见。)

现在,很多人在谈到“Python”或 Python 解释器时所指的 CPython 并不那么聪明。它是一个简单的字节码虚拟机,将尽职尽责地执行与在每次迭代中一次又一次调用函数相关的逻辑。但是话说回来,如果性能那么很重要,你为什么还要使用解释器呢?如果尽可能降低此类开销非常重要,请考虑在本机代码中编写该热循环(例如,作为 C 扩展或 Cython)。

除非您每次迭代只进行少量的数字运算,否则无论哪种方式您都不会获得大的改进。

【讨论】:

    【解决方案3】:

    如果“Python”是指CPython,通常使用的实现,不是。

    如果您所说的“Python”是指 Python 语言的任何实现,是的。 PyPy 可以优化很多,我相信它的方法 JIT 应该可以处理这样的情况。

    【讨论】:

      【解决方案4】:

      CPython(“标准”python 实现)不做这种优化。

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

      • 使用 PyPy 代替 CPython
      • 使用Cython
      • 编写 C++ 模块并将其与sip 连接
      • 如果可能,使用numpy simd 方法实现您的算法
      • 如果可能,使用例如PyCuda 在 GPU 硬件上移动计算

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

          猜你喜欢
          • 2013-11-15
          • 2013-08-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-09-10
          • 2013-07-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多