Is unpacking faster than indexing?

This is a strange story.

One morning, I woke up and wrote a few lines of code. Then, there came an interesting situation. I need to assign two elements of an array to two variables. There are many ways of achieving this in python.

If you're a javascript developer, your instinct should say this is the way.

const [x,y] = items;

Suppose, I've got an array [1,2] and I need to assign two variables to this value. Something like follow:

a = 1
b = 2

I did the natural thing I was used to doing, unpacking the variable.

a, b = items

This works with charm. But suddenly I remember of an another way. What if I index the array and grab the elements that way.

a, b = items[0], items[1]

Or even.

a = items[0]
b = items[1]

Now, I want to profile this code. I know this is over-engineering at max but let's see what exactly happens.

First I wrote the same implementation in python and disassembled the code.

In [15]: def x():
    ...:     a , b = items
    ...:     return

In [16]: def y():
    ...:     a = items[0]
    ...:     b = items[1]
    ...:     return

In [17]: dis(x)
  2           0 LOAD_GLOBAL              0 (items)
              2 UNPACK_SEQUENCE          2
              4 STORE_FAST               0 (a)
              6 STORE_FAST               1 (b)

  3           8 LOAD_CONST               0 (None)
             10 RETURN_VALUE

In [18]: dis(y)
  2           0 LOAD_GLOBAL              0 (items)
              2 LOAD_CONST               1 (0)
              4 BINARY_SUBSCR
              6 STORE_FAST               0 (a)

  3           8 LOAD_GLOBAL              0 (items)
             10 LOAD_CONST               2 (1)
             12 BINARY_SUBSCR
             14 STORE_FAST               1 (b)

  4          16 LOAD_CONST               0 (None)
             18 RETURN_VALUE

In [19]:

Do you see the extra overhead when I index the value? First, it need to load the constant i.e. index value, then perform the subscription. The store the value in a. This is a long way of doing it.

If I look at the other code, which is unpacking. One instruction UNPACK_SEQUENCE is capable of returning all the values and it's followed by two store instructions.

Even by intuition, I suppose the first function (x) will run faster.

Now, let's compare the execution time.

In [19]: %timeit x()
84.3 ns ± 3.59 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

In [20]: %timeit y()
108 ns ± 2.42 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

To no surprise, the unpacking beats the indexing by around ~24ns. This isn't the kind of optimization you should be thinking when writing a code. Write a clean, readable testable code.

Don't abuse your codebase with lines like the following:

a ,= items
# use this
b = items[0]