web-dev-qa-db-ja.com

なぜリストの理解はリストに追加するよりもずっと速いのですか?

なぜリストを理解するのがリストに追加するよりもずっと速いのかと思っていました。違いは表現力にすぎないと思いましたが、違いはありません。

>>> import timeit 
>>> timeit.timeit(stmt='''\
t = []
for i in range(10000):
    t.append(i)''', number=10000)
9.467898777974142

>>> timeit.timeit(stmt='t= [i for i in range(10000)]', number=10000)
4.1138417314859

リストの理解は50%高速です。どうして?

35
rafaelc

リスト内包表記は、基本的に通常のforループの単なる「構文糖」です。この場合、パフォーマンスが向上する理由は、リストのappend属性をロードし、各反復で関数として呼び出す必要がないためです。つまり、一般的にの場合、関数のフレーム(他の場合は複数の関数)の中断と再開は要求に応じてリストを作成するよりも遅いため、リスト内包表記は高速に実行されます。

以下の例を検討してください。

# Python-3.6

In [1]: import dis

In [2]: def f1():
   ...:     l = []
   ...:     for i in range(5):
   ...:         l.append(i)
   ...:         

In [3]: def f2():
   ...:     [i for i in range(5)]
   ...:     

In [4]: dis.dis(f1)
  2           0 BUILD_LIST               0
              3 STORE_FAST               0 (l)

  3           6 SETUP_LOOP              33 (to 42)
              9 LOAD_GLOBAL              0 (range)
             12 LOAD_CONST               1 (5)
             15 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             18 GET_ITER
        >>   19 FOR_ITER                19 (to 41)
             22 STORE_FAST               1 (i)

  4          25 LOAD_FAST                0 (l)
             28 LOAD_ATTR                1 (append)
             31 LOAD_FAST                1 (i)
             34 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             37 POP_TOP
             38 JUMP_ABSOLUTE           19
        >>   41 POP_BLOCK
        >>   42 LOAD_CONST               0 (None)
             45 RETURN_VALUE

In [5]: dis.dis(f2)
  2           0 LOAD_CONST               1 (<code object <listcomp> at 0x7fe48b2265d0, file "<ipython-input-3-9bc091d521d5>", line 2>)
              3 LOAD_CONST               2 ('f2.<locals>.<listcomp>')
              6 MAKE_FUNCTION            0
              9 LOAD_GLOBAL              0 (range)
             12 LOAD_CONST               3 (5)
             15 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             18 GET_ITER
             19 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             22 POP_TOP
             23 LOAD_CONST               0 (None)
             26 RETURN_VALUE

オフセット22では、リスト内包表記を使用する2番目の関数にはappend属性がありません。これらの余分なバイトコードはすべて、追加のアプローチを遅くします。また、各反復でappend属性のロードも行うことに注意してください。これにより、リスト内包表記を使用する2番目の関数よりもコードの時間が約2倍遅くなります。

67
Kasrâmvd

append関数の検索とロードにかかる時間を考慮しても、リストはPythonで一度に1つのアイテムを作成するのではなくCで作成されるため、リストの理解はさらに高速です。

# Slow
timeit.timeit(stmt='''
    for i in range(10000):
        t.append(i)''', setup='t=[]', number=10000)

# Faster
timeit.timeit(stmt='''
    for i in range(10000):
        l(i)''', setup='t=[]; l=t.append', number=10000)

# Faster still
timeit.timeit(stmt='t = [i for i in range(10000)]', number=10000)
12
chepner

this の記事を引用しているのは、appendlist属性が検索されず、ロードされ、関数として呼び出されないためです。繰り返し以上。

6
adarsh