views:

336

answers:

4

In pondering optimization of code, I was wondering which was more expensive in python:

if x:
    d = 1
 else:
    d = 2
or
d = 2
if x:
    d = 1

Any thoughts? I like the reduced line count in the second but wondered if reassignment was more costly than the condition switching.

+2  A: 

The second one should obviously be more expensive, it does the same operations if x is false and twice the assignments if x is true.

Assumption: assignment is more expensive in python than a conditional jump, which makes sense since its interpreted and it has to read a run time hash to get the new value then upload it in the same hash.

Blindy
I think you can disregard Python entirely, assignment is almost guaranteed to be slower than conditional jumps even in bare machine code. Unless your comparison functions are really nasty (not the case for straight numbers, obviously), the first form should almost always average out as superior, assuming the conditions being tested both have a reasonable chance of occurring.
Nicholas Knight
When you come against cache erasure due to conditional jumps, nothing is as straight forward as it seems. But for Python specifically it seems like a good bet.
Blindy
+5  A: 

You should probably benchmark this, but there's also a third form that uses the ternary operator:

d = 1 if x else 2
Sander Rijken
`d = 2 - bool(x)`
Chris Lutz
that only works for the 1 vs 2 case, if you want 15 and 31, it doesn't work
Sander Rijken
Ewww, Chris, that literally made me feel ill. :(
Nicholas Knight
@Sander - We can do `d = 31 - 16 * bool(x)` if you like. @Nicholas: That's my Perl-y upbringing coming out. Don't mind it :)
Chris Lutz
I was going for a simple example, I'm using more complicated if statements and non-numeric assignments.
Crad
@Chris: Yikes! (at the 31 - 16 * bool(x) thing) :-)
Sander Rijken
+16  A: 

Don't ponder, don't wonder, measure -- with timeit at the shell command line (by far the best, simplest way to use it!). Python 2.5.4 on Mac OSX 10.5 on a laptop...:

$ python -mtimeit -s'x=0' 'if x: d=1' 'else: d=2'
10000000 loops, best of 3: 0.0748 usec per loop
$ python -mtimeit -s'x=1' 'if x: d=1' 'else: d=2'
10000000 loops, best of 3: 0.0685 usec per loop
$ python -mtimeit -s'x=0' 'd=2' 'if x: d=1'
10000000 loops, best of 3: 0.0734 usec per loop
$ python -mtimeit -s'x=1' 'd=2' 'if x: d=1'
10000000 loops, best of 3: 0.101 usec per loop

so you see: the "just-if" form can save 1.4 nanoseconds when x is false, but costs 40.2 nanoseconds when x is true, compared with the "if/else" form; so, in a micro-optimization context, you should use the former only if x is 30 times more likely to be false than true, or thereabouts. Also:

$ python -mtimeit -s'x=0' 'd=1 if x else 2'
10000000 loops, best of 3: 0.0736 usec per loop
$ python -mtimeit -s'x=1' 'd=1 if x else 2'
10000000 loops, best of 3: 0.076 usec per loop

...the ternary operator of the if/else has its own miniscule pluses and minuses.

When the differences are as tiny as this, you should measure repeatedly, establish what the noise level is, and ensure you're not taking differences "in the noise" as significant. For example, to compare statement vs expression if/else in the "x is true" case, repeat each a few times:

$ python -mtimeit -s'x=1' 'd=1 if x else 2'
10000000 loops, best of 3: 0.076 usec per loop
$ python -mtimeit -s'x=1' 'd=1 if x else 2'
10000000 loops, best of 3: 0.0749 usec per loop
$ python -mtimeit -s'x=1' 'd=1 if x else 2'
10000000 loops, best of 3: 0.0742 usec per loop
$ python -mtimeit -s'x=1' 'd=1 if x else 2'
10000000 loops, best of 3: 0.0749 usec per loop
$ python -mtimeit -s'x=1' 'd=1 if x else 2'
10000000 loops, best of 3: 0.0745 usec per loop

now you can state that the expression forms takes (on this machine and versions of key software) 74.2 to 76.0 nanoseconds -- the range is much more expressive than any single number would be. And similarly:

$ python -mtimeit -s'x=1' 'if x: d=1' 'else: d=2'
10000000 loops, best of 3: 0.0688 usec per loop
$ python -mtimeit -s'x=1' 'if x: d=1' 'else: d=2'
10000000 loops, best of 3: 0.0681 usec per loop
$ python -mtimeit -s'x=1' 'if x: d=1' 'else: d=2'
10000000 loops, best of 3: 0.0687 usec per loop
$ python -mtimeit -s'x=1' 'if x: d=1' 'else: d=2'
10000000 loops, best of 3: 0.0679 usec per loop
$ python -mtimeit -s'x=1' 'if x: d=1' 'else: d=2'
10000000 loops, best of 3: 0.0692 usec per loop

now you can state confidently that the statement form takes (under identical conditions) 67.9 to 69.2 nanoseconds; so its advantage, for x true, wrt the expression form, is 4.8 to 8.1 nanoseconds (it's quite fair to restrict this latter interval estimation to 6.3 to 6.8 nanoseconds, comparing min/min and max/max instead of min/max and max/min as the wider, more prudential estimate does).

How much time and energy is worth devoting to such microscopic differences, once you've realized for any given care that they are microscopic, is, of course, a different issue.

Alex Martelli
SuperMartelli to the rescue! +1 for a giant block of benchmarks.
Chris Lutz
I've noticed that some people seem to get more enjoyment from lecturing people on what they should do, but others get more enjoyment from educating us on the finer points. Thank you, Alex.
Peter Rowell
+1  A: 

I would argue that the one that is most readable is the most optimized (for readability at least).

The if...else construct makes it clear that you are dealing with an either/or case.

The assignment construct might make more sense if (d==2) is the usual value and your if tests for an unusual case. This construct becomes less clear if your assignment gets moved away from the if.

In this simple example it doesn't really matter. For more complex code, I would almost always optimize for readability, even at the expense of a few CPU cycles.

Mark Peters
One of the main philosophies of Python is "code is written once, but read many times". If you start contemplating ridiculously trivial optimisations like this, your time would be much better spend rewriting that part of the module as a C extension, and keeping your Python code nice and readable. `2-bool(x)` might be 1.2 nanoseconds quicker, but it's much less obvious than `if x: d = 1`, and most likely slower than `if(x){d=1;}`
dbr