Reputation: 845
Is there any performance difference between this:
for item in collection:
if item == badItem:
break
else
doFunction(item)
And this:
for item in collection:
if item == badItem:
break
doFunction(item)
Assume I'm doing this a couple hundred million times so any performance difference will help.
EDIT: I'm not actually implementing this based off the results of this question, I'm just wondering theoretically what is faster. I'm just curious.
Upvotes: 0
Views: 257
Reputation: 17188
Grant's answer has it right: if you're concerned about performance, first get the code running, then measure what needs to be improved, and finally improve that stuff.
For posterity, here are my timing results. The short answer: there's no real difference, even over billions of iterations.
With Else:
min: 0.001327058799944325
max: 0.0037289344766406884
mean: 0.002665085947631951
Without Else:
min: 0.0013189987034252226
max: 0.003550914613782652
mean: 0.002147321588495288
And the code:
C:\>python
Python 3.3.2 (v3.3.2:d047928ae3f6, May 16 2013, 00:03:43) [MSC v.1600 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> else_version = timeit.repeat('for i in c:\n\tif i == 0: break\n\telse:i += 1','import random;c=[random.randint(0,1) for _ in range(1000000)]', number = 10000, repeat=10)
>>> nelse_version = timeit.repeat('for i in c:\n\tif i == 0: break\n\ti += 1','import random;c=[random.randint(0,1) for _ in range(1000000)]', number = 10000, repeat=10)
>>> min(else_version)
0.001327058799944325
>>> max(else_version)
0.0037289344766406884
>>> sum(else_version)/10
0.002665085947631951
>>>
>>> min(nelse_version)
0.0013189987034252226
>>> max(nelse_version)
0.003550914613782652
>>> sum(nelse_version)/10
0.002147321588495288
>>>
Whatever the cost of having the else
statement might actually be, it's clearly dwarfed by any actual operations you're doing (such as your __eq__
implementation, or what your actual doFunction
is, or even just other stuff happening on your machine).
Upvotes: 1
Reputation: 215039
Here are dis'es of both versions, side by side:
0 SETUP_LOOP 40 (to 43) | 0 SETUP_LOOP 40 (to 43) 3 LOAD_GLOBAL 0 (collection) | 3 LOAD_GLOBAL 0 (collection) 6 GET_ITER | 6 GET_ITER 7 FOR_ITER 32 (to 42) | 7 FOR_ITER 32 (to 42) 10 STORE_FAST 0 (item) | 10 STORE_FAST 0 (item) | 13 LOAD_FAST 0 (item) | 13 LOAD_FAST 0 (item) 16 LOAD_GLOBAL 1 (badItem) | 16 LOAD_GLOBAL 1 (badItem) 19 COMPARE_OP 2 (==) | 19 COMPARE_OP 2 (==) 22 POP_JUMP_IF_FALSE 29 | 22 POP_JUMP_IF_FALSE 29 | 25 BREAK_LOOP | 25 BREAK_LOOP 26 JUMP_ABSOLUTE 7 | 26 JUMP_FORWARD 0 (to 29) | 29 LOAD_GLOBAL 2 (doFunction) | 29 LOAD_GLOBAL 2 (doFunction) 32 LOAD_FAST 0 (item) | 32 LOAD_FAST 0 (item) 35 CALL_FUNCTION 1 | 35 CALL_FUNCTION 1 38 POP_TOP | 38 POP_TOP 39 JUMP_ABSOLUTE 7 | 39 JUMP_ABSOLUTE 7 42 POP_BLOCK | 42 POP_BLOCK 43 LOAD_CONST 0 (None) | 43 LOAD_CONST 0 (None) 46 RETURN_VALUE | 46 RETURN_VALUE
As you can see, the only difference is JUMP_ABSOLUTE
(with else
) vs. JUMP_FORWARD
(without it). Since both opcodes are immediately after BREAK_LOOP
, they won't run in any case, so both versions are fully equivalent.
That said, an else
after a breaking statement (break/continue/return
) is usually considered a code smell (and takes an extra useless line).
If you're interested in maximal performance, it might be worth considering .index
or itertools.takewhile
instead of a plain loop with if
.
Upvotes: 5
Reputation: 32449
As Grant Birchmeier said: measure, measure, measure.
On my box using Python 3.3.1 (default, Apr 17 2013, 22:30:32) [GCC 4.7.3] on linux
I get these results:
testA 0.7911653139999544
testB 0.7868194140028208
testC 0.7771379340010753
Using:
collection = [random.randint (1, 10000000) for _ in range (10000000) ]
badItem = 0
collection [5000000] = 0
def doFunction (item): pass
def testA ():
for item in collection:
if item == badItem: break
else: doFunction (item)
def testB ():
for item in collection:
if item == badItem: break
doFunction (item)
def testC ():
badIndex = collection.index (badItem)
for item in collection [:badIndex]:
doFunction (item)
YMMV. I am just comparing ints and no real world data. I have no idea how costly is your __eq__
, what doFunction
does, etc.
Upvotes: 1
Reputation: 18504
This sounds like pre-mature optimization: Don't do it.
You should make your program work correctly before you try to optimize it, if you even have to.
If your finished app turns out to be slower than you need, then measure, measure, measure. Use a profiling tool. The parts that are slow will probably surprise you. Don't waste time fixing parts that aren't provably slow.
But back to the first point: Don't try to optimize a program that isn't feature-complete.
Upvotes: 2