【问题标题】:Why isn't python optimising the function declaration out of the loop?为什么python不在循环中优化函数声明?
【发布时间】:2017-10-22 21:12:18
【问题描述】:

考虑:

Code A

def foo(): 
    pass

for i in range(1000000):
    foo()

Code B

for i in range(1000000):
    def foo():
        pass
    foo()

两个代码 sn-ps 之间的唯一区别是 foo 在每次迭代时在循环内不断重新定义。

运行一些基准测试:

Code A

10 loops, best of 3: 102 ms per loop

Code B

10 loops, best of 3: 188 ms per loop

因此,不断地重新定义函数是不必要的开销。

Code B 的字节码如下所示:

  1           0 SETUP_LOOP              39 (to 42)
              3 LOAD_NAME                0 (range)
              6 LOAD_CONST               0 (1000000)
              9 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             12 GET_ITER
        >>   13 FOR_ITER                25 (to 41)
             16 STORE_NAME               1 (i)

  2          19 LOAD_CONST               1 (<code object foo at 0x103113390, file "<dis>", line 2>)
             22 LOAD_CONST               2 ('foo')
             25 MAKE_FUNCTION            0
             28 STORE_NAME               2 (foo)

  4          31 LOAD_NAME                2 (foo)
             34 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
             37 POP_TOP
             38 JUMP_ABSOLUTE           13
        >>   41 POP_BLOCK
        >>   42 LOAD_CONST               3 (None)
             45 RETURN_VALUE

如您所见,函数定义并未在循环外进行优化(见25 MAKE_FUNCTION 行)。

这看起来很简单,将函数创建移出循环,因为它的声明显然不是循环执行的条件。

是否有任何明显的障碍阻止这样做?

【问题讨论】:

  • Python 通常做的优化很少。
  • (你可以很容易地问为什么它没有优化整个循环。)
  • 你试过Pypy,一个即时编译器吗?
  • 如果你能证明生成的代码块 never 会有所不同,并且 never 会依赖i 在范围内,那么我想您可以将其移出...但是,在那一点上,您可以证明它从未改变过,您已经必须通过整个循环来做到这一点...这可能只是基于输入的本次运行是一致的下次运行可能不成立。
  • 因为在解释器中,与编译代码相反,为时已晚。你已经进入了循环。由于动态对象(类型!)的创建,python 通常无法在代码实际运行之前跨越范围边界。

标签: python function optimization


【解决方案1】:

Python 允许在运行时重新分配或修改很多东西。例如,在编译此代码时,Python 无法确定您的程序的某些部分是否会执行类似

import builtins

builtins.range = lambda *args: []

在这种情况下,将foo 定义移出循环是错误的,因为foo 定义永远不应该执行。

您可以在 Python 中做很多疯狂的事情,这些事情会以意想不到的方式改变代码的含义。尽管有这些可能性,但要进行优化确实需要 JIT 编译器,但 Python 的标准实现没有这些。

【讨论】:

  • 谢谢。这是有道理的。在回答“为什么不做”这个问题时,首先谈论“为什么不应该做”是有意义的。
【解决方案2】:

这是您建议的优化不起作用的示例

foo = None

def range(n):
    global foo
    def foo():
        print('hi')
    for i in (1,2,3):
        yield i

def foo():
    pass

for i in range(1000000):
    foo()

我的输出:

hi
hi
hi

【讨论】:

    【解决方案3】:

    如果要在运行时更改函数定义怎么办?

    for i in range(1000000):
        if(True):
            def foo():
                pass
        if("some bits decide to flip"=="some bits decide to flip"):
            def foo():
                i=i+1
                pass
        foo()
    

    【讨论】:

      【解决方案4】:

      由于 python 不知道您是否正在捕获范围内的某些内容,因此他无法对其进行优化。如果您在循环内声明函数,则取决于您是否需要重新定义它。 当 python 到达循环时,解释器不能超出循环范围。

      【讨论】:

        猜你喜欢
        • 2018-11-01
        • 1970-01-01
        • 2018-02-11
        • 1970-01-01
        • 1970-01-01
        • 2021-02-13
        • 2013-03-01
        • 1970-01-01
        • 2011-08-27
        相关资源
        最近更新 更多