Which happens first?

Tim Peters tim.one at home.com
Sun Apr 8 00:52:20 EDT 2001


[Carlos Alberto Reis Ribeiro, staring at the disassembled Python bytecodes
 for ( 16 * ( 15j + ( 4.2 - 3.9 ) + 20  ) / -2 ) % 5]

> ...
> 4) Now the ugly part. At [25], we load a constant (with the value 2),
> and then do a unary minus operation. The question is, does it really
> need to be so?

No, not really.

> There is any good reason not to optimize this, making the conversion
> to the negative value in the compiler?

The Python compiler does no classic optimizations of any kind at all:
expressions are evaluated strictly left-to-right, parentheses are always
honored, your code is never moved, shared, rearranged, rewritten or
collapsed.  WYSIWYG.  Most of the time <wink>.

Because it does no optimizations, there's no supporting machinery for *doing*
optimizations either, so "the first one" it tries is going to be more of a
pain than you imagine.  In particular, negative integer literals don't exist
in Python's grammar (only non-negative integer literals exist), and Python
compiles straight from parse tree to bytecode.  So even that trivial little
optimization would require a (non-existent) peephole optimizer doing pattern
matching on the parse tree or bytecode stream.

Not worth the effort to anyone so far.  To the contrary, the utter lack of
optimization has saved people countless hours in not needing to track down
optimization bugs <0.3 wink>.

> 5) Other strange thing: at least in this example, the compiler did
> not made any optimization on constant expressions.

Right, and it never does.  This isn't C, and Python assignments aren't
macros, so doing stuff like

TOTAL_BYTES = BYTES_PER_SECTOR * SECTORS_PER_TRACK * NUM_TRACKS

at module level is a one-time module import cost, no matter how often
TOTAL_BYTES is dynamically referenced later.  But, yes, doing that inside a
loop is a Possibly Poor Idea.

> ...
> 1) Avoid any unnecessary calculation, specially inside tight loops.

I'd amend that to "but only inside tight loops".  For typical code, it really
doesn't matter elsewhere.

> I tend to use lots of explicit calculations, in order to make code
> easier to understand. Things like adding/subtracting one, or
> calculating an offset for a struct, for instance) are common
> applications of this idiom, for example:
>
>  >>> p = a[(3*4+2)-1]   # get the second byte of the third DWORD

I'll take your word for it that you find that easy to understand <wink>.  But
note that since Python doesn't have pointers, Python code is blissfully free
of fiddly pointer and offset calculations, unless you're faking high-level
data structures by hand (in which case you're not really programming in
Python anyway ...).

> 2) If your expression involves value of mixed types, check
> *carefully* the sequence of the calculation.

That's crucial advice in any language.

> It is possible to optimize the evaluation a little bit by moving
> the more complex (and slower) types to the end of the evaluation.

More important is that the order of coercions yield a *correct* result.  When
you first inject a float or complex into a calculation can have profound
effects on the numerical result, not just on the speed.  Optimizing for speed
is darned tricky too.  For example, floating multiplies may be *much* faster
than int multiplies in Python, because Python checks the latter for overflow
but gets no help on that from C:  Python's code for multiplying two (small,
not unbounded) ints slobbers over 100 lines of C!

don't-worry-about-speed-until-you-have-to-ly y'rs  - tim





More information about the Python-list mailing list