26

Take a look at this:

>>> def f():
...     return (2+3)*4
... 
>>> dis(f)
  2           0 LOAD_CONST               5 (20)
              3 RETURN_VALUE  

Evidently, the compiler has pre-evaluated (2+3)*4, which makes sense.

Now, if I simply change the order of the operands of *:

>>> def f():
...     return 4*(2+3)
... 
>>> dis(f)
  2           0 LOAD_CONST               1 (4)
              3 LOAD_CONST               4 (5)
              6 BINARY_MULTIPLY     
              7 RETURN_VALUE  

The expression is no longer fully pre-evaluated! What is the reason for this? I am using CPython 2.7.3.

arshajii
  • 127,459
  • 24
  • 238
  • 287
  • 2
    Looks like a flaw in the peephole optimizer to me. You could check the bug tracker and see if it's a known problem. – user2357112 Jul 23 '13 at 01:20
  • 4
    Stop using old python 2.x... On 3.3 (at least) it works as it should. – JBernardo Jul 23 '13 at 01:23
  • 1
    @JBernardo 2.x and 3.x are very different; I cannot seamlessly switch between them. – arshajii Jul 23 '13 at 01:26
  • 2
    it has 5 years already. People should start transitioning – JBernardo Jul 23 '13 at 01:27
  • 3
    @JBernardo: It's been far less than 5 years since critical extension modules have been ported to python 3. For example, SciPy has only officially had support for Python 3 for 2 years. Moving large existing codebases to a new language is expensive and takes time. Python 2.7 is expected to remain active and viable for many years. – Mr Fooz Jul 23 '13 at 01:51
  • @JBernardo Interestingly, looks like the issue is still there in 3.1 and 3.2. – arshajii Jul 25 '13 at 14:51

2 Answers2

9

In the first case the unoptimized code is LOAD 2 LOAD 3 ADD LOAD 4 MULTIPLY and in the second case it's LOAD 4 LOAD 2 LOAD 3 ADD MULTIPLY. The pattern matcher in fold_binops_on_constants() must handle the first ADD ok (replacing LOAD LOAD ADD with LOAD) and then follows on to do the same thing to MULTIPLY. In the second case by the time the ADD (now the second argument to MULTIPLY instead of the first) is turned into a constant the scanner is too far ahead to see L L M (when the "cursor" was on LOAD 4 it didn't look like a L L M yet).

Ben Jackson
  • 90,079
  • 9
  • 98
  • 150
  • So this is a shortcoming of the compiler, then? – arshajii Jul 23 '13 at 01:35
  • 3
    @arshajii: You could probably fix the bug in `peephole.c` by simply backing up one instruction after a fold. However the right solution is to not rely on the peephole optimizer and instead fold constants in `compile.c` where it is emitting the binops in the first place. A pass over the AST could propagate constant expression information up from the leaves and there would be no room for this kind of bug. – Ben Jackson Jul 23 '13 at 01:37
5

Looks like this issue was patched in Python 3.3, as can be seen here.

>>> def f():
...     return (2+3)*4
... 
>>> dis(f)
  2           0 LOAD_CONST               5 (20)
              3 RETURN_VALUE  
>>> def f():
...     return 4*(2+3)
... 
>>> dis(f)
  2           0 LOAD_CONST               5 (20)
              3 RETURN_VALUE 
arshajii
  • 127,459
  • 24
  • 238
  • 287