cᴏʟᴅsᴘᴇᴇᴅ cᴏʟᴅsᴘᴇᴇᴅ - 2 months ago 10
Python Question

Why isn't python optimising this code?

Consider:

Code A


def foo():
pass

for i in range(1000000):
foo()


Code B


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


The only difference between the two code snippets are that
foo
is constantly redefined inside the loop at each iteration.

Running some benchmark tests:

Code A


10 loops, best of 3: 102 ms per loop


Code B


10 loops, best of 3: 188 ms per loop


So, constant redefinition of the function is an unwanted overhead.

Here's what the byte code of
Code B
looks like:

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


As you can see, the function definition has not been optimised out of the loop (see line
25 MAKE_FUNCTION
).

This would seem simple enough, to move the function creation out of the loop, since its declaration obviously is not conditional to loop execution.

Is there any glaring obstacle that prevents this from being done?

Answer Source

Python allows a lot of things to be reassigned or modified at runtime. For example, when compiling this code, Python can't determine whether some part of your program might do something like

import builtins

builtins.range = lambda *args: []

in which case moving the foo definition out of the loop would be wrong, because the foo definition should never execute.

There are lots and lots of crazy things you can do in Python that can change the meaning of code in unexpected ways. To optimize in spite of such possibilities really requires a JIT compiler, but the standard implementation of Python doesn't have one of those.