1

I have started recently learning programming (in Python). I have two pieces of code that use while loops:

a=100000000
#piece of code 1    
while a > 0:
    a-=10
print("done")

#piece of code 2
while True:
    a-=10
    if a <= 0:
       print("done")
       break

Both are functionally equivalent, i.e. they execute in essence the same task. For curiosity, I recorded the time necessary to execute this operation using both versions of this while loop using the time module. The results were:

piece of code 1: 0.99 s
piece of code 2: 0.89 s

They show essentially the same performance, although piece of code 2 was slightly more efficient. This is ok because this difference is basically irrelevant even for very large numbers. However, this is sort of unexpected to me, as I believe the first while loop executes fewer operations. Could someone please explain why the second piece of code is more efficient?

3
  • The second loop simulates a do-while loop that does the check one time less. Now the difference is probably something else (you'll have to check the bytecode to really understand what is happening), but it's worth knowing the logical difference between the two, and that they aren't always equivalent. Commented Nov 3, 2019 at 20:25
  • Fewer statements doesn't mean fewer operations. The first one still has to evaluate > just as many times as the second one evaluates <=. Unless measurement uncertainty is the explanation, I could imaging that > is implemented in terms of <=. Commented Nov 3, 2019 at 20:36
  • I got 0.882s and 0.900s in IPython 5.5 on Python 3.6. I tried changing the value of a and got similar results. But anyway where the results are always in the same order of magnitude, there's nothing really interesting happening here. Commented Nov 3, 2019 at 20:37

1 Answer 1

1

I put those snippets in two functions:

def first():
    a=100000000
    while a > 0:
        a-=10

def second():
    a=100000000
    while True:
        a-=10
        if a <= 0:
            break

And then used timeit.timeit and it was actually the second that was consistently timed as taking longer (although for really negligible difference).

from timeit import timeit

timeit(first, number=100)
timeit(second, number=100)

I then inspected the bytecode by basically copying the example from the dis docs. This revealed that the bytecode is almost the same for both snippets, the only difference being in how the opcodes are ordered and that the second includes one additional instruction, namely BREAK_LOOP.

import dis
from collections import Counter

first_bytecode = dis.Bytecode(first)
for instr in first_bytecode:
    print(instr.opname)

second_bytecode = dis.Bytecode(second)
for instr in second_bytecode:
    print(instr.opname)

# easier to compare with Counters
first_b = Counter(x.opname for x in first_bytecode)
sec_b = Counter(x.opname for x in second_bytecode)

Finally, one might think the order might make a difference (and it could), but in order to make a fair comparison second should first check for the break condition before subtracting from a. Consider then a third function:

def third():
    a=100000000
    while True:
        if a <= 0:
            break
        a-=10

Comparing the bytecode of the third you'll see it is in fact ordered the same as the bytecode of the first, the only difference being a single BREAK_LOOP jammed somewhere in the middle.

If anything I hope this shows you how insignificant execution of one opcode usually is with respect to the overall performance of code. I think the immortal words of Donald Knuth are particularly well suited for the occasion:

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

Sign up to request clarification or add additional context in comments.

2 Comments

I appreciate the detailed reply and the code to check more in-depth how my code is performing. These instructions will be certainly useful for me in the future.
Turns out 3% of the time this stuff IS necessary.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.