2

Are these two code snippets exactly the same (like they would be in c++) or will they both produce slightly different running times?

first)

x = 'hello joe'

if x == 'hello':
  print('nope')
elif x == 'hello joe':
  print(x)

second)

x = 'hello jo'

if x == 'hello':
  print('nope')
else:
  if x == 'hello joe':
    print(x)

I wanted to find out myself but I am not sure how I might go about watching this code run in its assembly form in real time. Which brings me to my second question: how might I see the compiled assembly instructions that are made when I compile a Python program?

Patrick Haugh
  • 55,247
  • 13
  • 83
  • 91
Rob
  • 537
  • 3
  • 20
  • 3
    have you tried the `dis` module? https://stackoverflow.com/questions/19560057/how-to-read-python-bytecode – Jean-François Fabre Aug 07 '18 at 19:17
  • Depends on your interpreter/compiler. I guess you're asking about CPython? – Aran-Fey Aug 07 '18 at 19:18
  • Awesome, thanks guys! – Rob Aug 07 '18 at 19:19
  • If we break this down to assembler you are looking at more cycles in the CPU to process the first example over the second so unless the first example gets converted to an else then the second will always be a faster run time. The compare command takes a min of 2 cycles on an Intel processor. – SudoKid Aug 07 '18 at 19:23
  • 1
    @EmettSpeer What program are you using to compile the Python shown directly to Intel assembly language? Python byte code is executed by an interpreter, not bare CPU. – chepner Aug 07 '18 at 19:36
  • @chepner I didn't run the code or use any tooling to do the conversion. I just already know that a compare on a modern Intel/AMD CPU is 2 cycles and a jump is a single cycle. This means that preforming an if is a min of 2 cycles on the CPU with a jump of 1 if not matched. If you do an extra compare in your else that's a min of 2 more cycles with a possible 3rd for a jump. Though with my not seeing the second `if` my statement is 100% incorrect and both are equal in assembler as well. – SudoKid Aug 07 '18 at 20:06
  • @EmettSpeer And how is your analysis relevant if my Python implementation isn't running on an Intel architecture? – chepner Aug 07 '18 at 20:29
  • @chepner 1) I have admitted fault as I failed to fully read the provided code. 2) Well my statement is directly regarding the x86 line of CPU's it will still hold true just with possible different cycle counts on all other CPU's no matter architecture. 3) My statement is regarding the logical flow of code which only holds true if the poster didn't have a second `if`. The key there being only if the second `if` is not used. Which I have now noted 3 times I missed in my first pass. In which case in assembler of any kind both examples are equal. – SudoKid Aug 07 '18 at 20:43

1 Answers1

7

First, let's put your code(s) in a function

def func():               # line 1
    x = 'hello joe'       # line 2

    if x == 'hello':      # line 4
      print('nope')       # line 5
    else:                 # line 6
     if x == 'hello joe': # line 7
      print(x)            # line 8

now disassemble with that (using CPython 3.4):

import dis
dis.dis(func)

we get:

  2           0 LOAD_CONST               1 ('hello joe')
              3 STORE_FAST               0 (x)

  4           6 LOAD_FAST                0 (x)
              9 LOAD_CONST               2 ('hello')
             12 COMPARE_OP               2 (==)
             15 POP_JUMP_IF_FALSE       31

  5          18 LOAD_GLOBAL              0 (print)
             21 LOAD_CONST               3 ('nope')
             24 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             27 POP_TOP
             28 JUMP_FORWARD            25 (to 56)

  7     >>   31 LOAD_FAST                0 (x)
             34 LOAD_CONST               1 ('hello joe')
             37 COMPARE_OP               2 (==)
             40 POP_JUMP_IF_FALSE       56

  8          43 LOAD_GLOBAL              0 (print)
             46 LOAD_FAST                0 (x)
             49 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             52 POP_TOP
             53 JUMP_FORWARD             0 (to 56)
        >>   56 LOAD_CONST               0 (None)
             59 RETURN_VALUE

now change to elif:

  2           0 LOAD_CONST               1 ('hello joe')
              3 STORE_FAST               0 (x)

  4           6 LOAD_FAST                0 (x)
              9 LOAD_CONST               2 ('hello')
             12 COMPARE_OP               2 (==)
             15 POP_JUMP_IF_FALSE       31

  5          18 LOAD_GLOBAL              0 (print)
             21 LOAD_CONST               3 ('nope')
             24 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             27 POP_TOP
             28 JUMP_FORWARD            25 (to 56)

  6     >>   31 LOAD_FAST                0 (x)
             34 LOAD_CONST               1 ('hello joe')
             37 COMPARE_OP               2 (==)
             40 POP_JUMP_IF_FALSE       56

  7          43 LOAD_GLOBAL              0 (print)
             46 LOAD_FAST                0 (x)
             49 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             52 POP_TOP
             53 JUMP_FORWARD             0 (to 56)
        >>   56 LOAD_CONST               0 (None)
             59 RETURN_VALUE

The only differences are the line numbers.

    else:                 # line 6
     if x == 'hello joe': # line 7

becomes (and shifts the rest as well)

    elif x == 'hello joe': # line 6

There are as many instructions in both versions. The else and if keywords in that case seem to have been converted exactly the same way as elif. Not guaranteed in all implementations. Personally I'd stick to the shortest code (elif) because it's more "meaningful" and if a code should be faster, it would probably be that one.

Jean-François Fabre
  • 131,796
  • 23
  • 122
  • 195
  • I am not exactly sure what the line number represent. Are they locations in memory or blocks in memory? As you said it would seem that elif is really just a shorthand keyword for else if{}. If that is the case, then maybe it takes longer for an elif because it has to take the time to look up what elif means? Could this explain the difference in the line numbers? – Rob Aug 07 '18 at 20:29
  • 1
    I have updated my post to add line numbers of the source I've shown and why the numbers change. note that the byte compilation is done when loading the program, not execution. – Jean-François Fabre Aug 07 '18 at 20:33
  • Oh there actual line numbers.. of course they are, I over complicate everything. – Rob Aug 07 '18 at 20:35