13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-04-25

まとめ

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

アンパックについて

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

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

sample.py
a1 = [0, 1, 2]

print(a1)
print(*a1)
結果
[0, 1, 2]
0 1 2

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

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

a + b[*a, *b] について

導入

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

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

print(a + b)
print([*a, *b])
結果
[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 さんご指摘ありがとうございました!

実行速度検証(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)
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 モジュールで見ていきます。

モジュールインポート
from dis import dis
sample1(a+b)の内訳
dis(sample1)  # x1 + x2
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
sample2([*a,*b])の内訳
dis(sample2)  # [*x1, *x2]
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 があります。
リストのアンパックを行うとこのバイトコード命令が発生するようです。

結論

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

おまけ(%timeit について)

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

%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)
結果
sample3, count:  1000000
1000000 loops, best of 3: 199 ns per loop
--------------------------
sample4, count:  1000000
1000000 loops, best of 3: 232 ns per loop
13
5
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?