views:

1279

answers:

6

To check for odd and even integer, is the lowest bit checking more efficient than using the modulo?

>>> def isodd(num):
        return num & 1 and True or False

>>> isodd(10)
False
>>> isodd(9)
True
+27  A: 

Yep. The timeit module in the standard library is how you check on those things. E.g:

AmAir:stko aleax$ python -mtimeit -s'def isodd(x): x & 1' 'isodd(9)'
1000000 loops, best of 3: 0.446 usec per loop
AmAir:stko aleax$ python -mtimeit -s'def isodd(x): x & 1' 'isodd(10)'
1000000 loops, best of 3: 0.443 usec per loop
AmAir:stko aleax$ python -mtimeit -s'def isodd(x): x % 2' 'isodd(10)'
1000000 loops, best of 3: 0.453 usec per loop
AmAir:stko aleax$ python -mtimeit -s'def isodd(x): x % 2' 'isodd(9)'
1000000 loops, best of 3: 0.461 usec per loop

As you see, on my (first-day==old==slow;-) Macbook Air, the & solution is repeatably between 7 and 18 nanoseconds faster than the % solution.

timeit not only tells you what's faster, but by how much (just run the tests a few times), which usually shows how supremely UNimportant it is (do you really care about 10 nanoseconds' difference, when the overhead of calling the function is around 400?!-)...

Convincing programmers that micro-optimizations are essentially irrelevant has proven to be an impossible task -- even though it's been 35 years (over which computers have gotten orders of magnitude faster!) since Knuth wrote

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.

which as he explained is a quote from an even older statement from Hoare. I guess everybody's totally convinced that THEIR case falls in the remaining 3%!

So instead of endlessly repeating "it doesn't matter", we (Tim Peters in particular deserves the honors there) put in the standard Python library module timeit, that makes it trivially easy to measure such micro-benchmarks and thereby lets at least some programmers convince themselves that, hmmm, this case DOES fall in the 97% group!-)

Alex Martelli
The compiler isn't going to optimize those to the same instruction(s)? Excuse my Python-ignorance if it shows.
GMan
No, the Python compiler is itself optimized to be maximally simple, reliable, and fast -- it doesn't do optimizations such as changing the operation in use (for which it would have to infer that `x` is always integer, for example). Try psyco if you need this kind of low-level optimization (i.e. if every nanosecond matters).
Alex Martelli
Thank you for adding a point about the unimportance of such a small difference. Even performing the calculation hundreds, or even thousands of times, this is probably one of the worst "optimizations" you can make - not only does it hurt readability (IMO), but you don't get that much back out of it. I never want to pay more than what I get.
Thomas Owens
Oh yea, I forgot Python isn't statically typed. (right?) There goes the Python ignorance alarm again.
GMan
@Thomas, I actually edited the answer repeatedly to explain exactly how irrelevant it is, provide Knuth's quote, explain why we put timeit in the library,
Alex Martelli
Alex: +1. You did a fine job providing the facts. However, in this case, I just felt that the idea of premature optimization over something so minuscule is the worst thing a developer can do.
Thomas Owens
@Thomas, yes, but if just preaching it by the likes of Hoare and Knuth still hasn't gotten to people in 35/40 years, what chances have you or I to get through by just preaching?-) timeit in my experience is more effective because it lets people _see for themselves_!-)
Alex Martelli
And I learned something new. I'll probably abuse the hell out of timeit when I'm doing stuff in Python over the next few weeks. But, IMO, every voice helps. And now, when someone else reads this, they'll see (1) which one really is faster and then (2) reasons why they shouldn't care. Which was my goal.
Thomas Owens
@Gman, it's *DEFINITELY* the case that Python isn't statically typed (its typing is strong but dynamic); psyco, unladen swallow, etc, do or plan to do type inference or specialization JIT to get the speed back when feasible (like Javascript V8 and other JS engines, etc).
Alex Martelli
@Thomas, absolutely right, and if you see strange effects with timeit be sure to post SO questions (it's much harder to use on stuff that's not idempotent, e.g. timing `alist.append(something)`...!-)
Alex Martelli
+11  A: 

To be totally honest, I don't think it matters.

The first issue is readability. What makes more sense to other developers? I, personally, would expect a modulo when checking the evenness/oddness of a number. I would expect that most other developers would expect the same thing. By introducing a different, and unexpected, method, you might make code reading, and therefore maintenance, more difficult.

The second is just a fact that you probably won't ever have a bottleneck when doing either operation. I'm for optimization, but early optimization is the worst thing you can do in any language or environment. If, for some reason, determining if a number is even or odd is a bottleneck, then find the fastest way of solving the problem. However, this brings me back to my first point - the first time you write a routine, it should be written in the most readable way possible.

Thomas Owens
I'm half expecting down-votes on this, but I think this is an important point for anyone who is wondering this (or any similar) question, so it's going to stay.
Thomas Owens
GMan
Not a down vote persay... But, it has been the Pythonic solution in cases like this to profile the "obvious" options and prefer the fastest.
semiuseless
semiuseless: I'm still learning Python, but I personally think that the modulo operator would be more Pythonic simply because you are doing what is expected. It's, to me at least, far more obvious and clear.
Thomas Owens
I'm with you on that. As Donald Knuth said, "premature optimization is the root of all evil."
Fernando
@gutofb7: I gave the complete quote _and_ the link to the PDF of Knuth's great article (also the first article proposing indentation as the sole mean of indicating blocks, I believe) in my answer about 8 minutes before your comment;-). BTW, in this case I believe %2 and -).
Alex Martelli
+4  A: 

"return num & 1 and True or False" ? Wah! If you're speed-crazy (1) "return num & 1" (2) inline it: if somenumber % 2 == 1 is legible AND beats isodd(somenumber) because it avoids the Python function call.

John Machin
Yep, avoiding the function call is a big win (though the ==1 is really redundant) -- avoiding the call cuts about 300 nanoseconds from the 450 or so I measured in my answer.
Alex Martelli
+1  A: 

John brings up a good point. The real overhead is in the function call:

me@localhost ~> python -mtimeit -s'9 % 2'
10000000 loops, best of 3: 0.0271 usec per loop
me@localhost ~> python -mtimeit -s'10 % 2'
10000000 loops, best of 3: 0.0271 usec per loop

me@localhost ~> python -mtimeit -s'9 & 1'
10000000 loops, best of 3: 0.0271 usec per loop
me@localhost ~> python -mtimeit -s'9 & 1'
10000000 loops, best of 3: 0.0271 usec per loop

me@localhost ~> python -mtimeit -s'def isodd(x): x % 2' 'isodd(10)'
1000000 loops, best of 3: 0.334 usec per loop
me@localhost ~> python -mtimeit -s'def isodd(x): x % 2' 'isodd(9)'
1000000 loops, best of 3: 0.358 usec per loop

me@localhost ~> python -mtimeit -s'def isodd(x): x & 1' 'isodd(10)'
1000000 loops, best of 3: 0.317 usec per loop
me@localhost ~> python -mtimeit -s'def isodd(x): x & 1' 'isodd(9)'
1000000 loops, best of 3: 0.319 usec per loop

Interestingly both methods remore the same time without the function call.

Jeffrey Hulten
Selinap
+1  A: 

The best optimization you can get is to not put the test into a function. 'number % 2' and 'number & 1' are very common ways of checking odd/evenness, experienced programmers will recognize the pattern instantly, and you can always throw in a comment such as '# if number is odd, then blah blah blah' if you really need it to be obvious.

# state whether number is odd or even
if number & 1:
    print "Your number is odd"
else:
    print "Your number is even"
too much php
A: 

Apart from the evil optimization, it takes away the very idiomatic "var % 2 == 0" that every coder understands without looking twice. So this is violates pythons zen as well for very little gain.

Furthermore a = b and True or False has been superseded for better readability by

return True if num & 1 else False

Tom