Python3で二つのリスト a, b に対して a + b と [*a, *b] を実行するのは違う

# まとめ

* `[*a, *b]` の方が遅いですよ
  * `BUILD_LIST_UNPACK` が発生するため


# アンパックについて

Python2 と Python3 では山より高く海より深い溝がある(誇張表現)と思っていますが、その中に一つに **リストのアンパック** があります。
リストやタプルの変数の前に `*` を付けることで、そのリストを展開した結果を直接扱うことができます。

分かりづらいので例を用いますと、例えば下記のようなコードを実行すると、

```python:sample.py
a1 = [0, 1, 2]

print(a1)
print(*a1)
```

```text:結果
[0, 1, 2]
0 1 2
```

このように、`*a1` はリストの中身を展開したものが表示されます。

これによってより柔軟に変数への代入ができたりします。
(気になる人は `first, *middle, last = [0, 1, 2, 3, 4]` で `middle` に何が入るか確認してみてください)

# `a + b` と `[*a, *b]` について

## 導入

アンパックを使うことで、2つのリスト a, b の連結を以下の 2通りで書くことができます。

```python:2通りのリストの連結
a = list(range(5))
b = list(range(5, 10))

print(a + b)
print([*a, *b])
```

```text:結果
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```

## 実行速度

上記より、リストの連結は `a + b` と `[*a, *b]` の 2通りで書けることが確認できました。

ここで疑問ですが、**この2通りのうちどちらの方が実行速度が速い** のでしょうか？

1. アンパックはコストが高い。だから `a + b` の方が速い
2. リストの足し算はコストが高い。だから `[*a, *b]` の方が速い
3. あくまで表現の違いで内部的には同じだから、実行速度は同じ 

僕がパッと思いついたのは上記の 3通りの答えです。

それでは実際に検証してみます。
今回は JupyterNotebook 上(正確には Google Colaboratory上)で検証したため `%time` を使いましたが、JupyterNotebook を使わない場合は Time モジュールなどを使ってください。

**注記(2018/04/26 追記)**
下記のコードでは関数の中で `for` 文を回していますが、今回のような計測用途であれば、`%timeit` を使うともっとスマートに記述することができます(下部おまけ参照)。
[@69guitar1015](https://qiita.com/69guitar1015) さんご指摘ありがとうございました！

```python:実行速度検証(JupyterNotebook上)
x1 = list(range(5))
x2 = list(range(5,10))
count = 1000000

def sample1(a, b, c):
    """二つのリスト a, b に対して a + b を指定回数実行"""
    print("sample1, count: ", c)
    for i in range(c):
        z = a + b

def sample2(a, b, c):
    """二つのリスト a, b に対して [*a, *b] を指定回数実行"""
    print("sample2, count: ", c)
    for i in range(c):
        z = [*a, *b]

print("x1 + x2: ", x1 + x2)
print("[*x1, *x2]: ", [*x1, *x2])
print("-----------------------")
% time sample1(x1, x2, count)
print("-----------------------")
% time sample2(x1, x2, count)
print("-----------------------")
% time sample1(x1, x2, count*10)
print("-----------------------")
% time sample2(x1, x2, count*10)
print("-----------------------")
% time sample1(x1, x2, count*100)
print("-----------------------")
% time sample2(x1, x2, count*100)
```

```text:sample1がa+bでsample2が[*a,*b]
x1 + x2:  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[*x1, *x2]:  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
-----------------------
sample1, count:  1000000
CPU times: user 88 ms, sys: 1e+03 µs, total: 89 ms
Wall time: 90.2 ms
-----------------------
sample2, count:  1000000
CPU times: user 109 ms, sys: 0 ns, total: 109 ms
Wall time: 109 ms
-----------------------
sample1, count:  10000000
CPU times: user 890 ms, sys: 0 ns, total: 890 ms
Wall time: 895 ms
-----------------------
sample2, count:  10000000
CPU times: user 1.18 s, sys: 0 ns, total: 1.18 s
Wall time: 1.2 s
-----------------------
sample1, count:  100000000
CPU times: user 10.5 s, sys: 0 ns, total: 10.5 s
Wall time: 10.5 s
-----------------------
sample2, count:  100000000
CPU times: user 12.2 s, sys: 0 ns, total: 12.2 s
Wall time: 12.2 s
```

上記の結果から、Python3 以前からある **`a + b` の方が速い** ことが確認できました。

## 解析(dis モジュール)

ではその内訳を dis モジュールで見ていきます。

```python:モジュールインポート
from dis import dis
```

```python:sample1(a+b)の内訳
dis(sample1)  # x1 + x2
```

```text:sample1(a+b)の内訳
  7           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('sample1, count: ')
              4 LOAD_FAST                2 (c)
              6 CALL_FUNCTION            2
              8 POP_TOP

  8          10 SETUP_LOOP              24 (to 36)
             12 LOAD_GLOBAL              1 (range)
             14 LOAD_FAST                2 (c)
             16 CALL_FUNCTION            1
             18 GET_ITER
        >>   20 FOR_ITER                12 (to 34)
             22 STORE_FAST               3 (i)

  9          24 LOAD_FAST                0 (a)
             26 LOAD_FAST                1 (b)
             28 BINARY_ADD
             30 STORE_FAST               4 (z)
             32 JUMP_ABSOLUTE           20
        >>   34 POP_BLOCK
        >>   36 LOAD_CONST               2 (None)
             38 RETURN_VALUE
```

```python:sample2([*a,*b])の内訳
dis(sample2)  # [*x1, *x2]
```

```text:sample2([*a,*b])の内訳
 13           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('sample2, count: ')
              4 LOAD_FAST                2 (c)
              6 CALL_FUNCTION            2
              8 POP_TOP

 14          10 SETUP_LOOP              24 (to 36)
             12 LOAD_GLOBAL              1 (range)
             14 LOAD_FAST                2 (c)
             16 CALL_FUNCTION            1
             18 GET_ITER
        >>   20 FOR_ITER                12 (to 34)
             22 STORE_FAST               3 (i)

 15          24 LOAD_FAST                0 (a)
             26 LOAD_FAST                1 (b)
             28 BUILD_LIST_UNPACK        2
             30 STORE_FAST               4 (z)
             32 JUMP_ABSOLUTE           20
        >>   34 POP_BLOCK
        >>   36 LOAD_CONST               2 (None)
             38 RETURN_VALUE
```

ぱっと見では気づかないかもですが、よーく見ると下の結果には `BUILD_LIST_UNPACK` があります。
リストのアンパックを行うとこのバイトコード命令が発生するようです。

* [32.12. dis — Disassembler for Python bytecode — Python 3.6.5 documentation](https://docs.python.org/3/library/dis.html#opcode-BUILD_LIST_UNPACK)

# 結論

* `a + b` と `[*a, *b]` は違う
* どうしてもパフォーマンスが気になってしまう人は `a + b` を使うと良い

# おまけ(`%timeit` について)

上記では関数内で `for` 文を回していましたが、下記のように `%timeit` を使えば計測のために `for` 文を使わずにすみます。

```python:%timeit使用例
def sample3(a, b):
    z = a + b

def sample4(a, b):
    z = [*a, *b]

p1 = list(range(5))
p2 = list(range(5, 10))

print("sample3, count: ", 1000000)
%timeit -n 1000000 sample3(p1, p2)
print("--------------------------")
print("sample4, count: ", 1000000)
%timeit -n 1000000 sample4(p1, p2)
```

```text:結果
sample3, count:  1000000
1000000 loops, best of 3: 199 ns per loop
--------------------------
sample4, count:  1000000
1000000 loops, best of 3: 232 ns per loop
```