2015-05-14 3 views
9

Мне было интересно, почему понимание списков происходит намного быстрее, чем добавление списка. Я, хотя разница не была выразительной, но это так.Почему такое понимание списка так быстро?

>>> 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% быстрее. Зачем?

+3

Проверьте это http://stackoverflow.com/questions/22108488/are-list-comprehensions-and-functional-functions-faster-than-for-loops –

+0

Теперь все начали измерения скорости кода Python. .. – ForceBru

+1

Чтобы быть справедливым, вы не должны использовать 'range' в этом конкурсе, если вы находитесь на Python 2. В Python 2' range' сам генерирует список, поэтому вы считаете создание этого списка в 'range' + добавление всех элементов в 't'. –

ответ

23

List comprehensions работать лучше здесь, потому что вам не нужно, чтобы загрузить атрибут на добавление списка и назовите его как функцию *

Рассмотрим следующий пример:

>>> import dis 
>>> def f1(): 
... for i in range(5): 
...  l.append(i) 
... 
>>> def f2(): 
... [i for i in range(5)] 
... 
>>> dis.dis(f1) 
    2   0 SETUP_LOOP    33 (to 36) 
       3 LOAD_GLOBAL    0 (range) 
       6 LOAD_CONST    1 (5) 
       9 CALL_FUNCTION   1 
      12 GET_ITER    
     >> 13 FOR_ITER    19 (to 35) 
      16 STORE_FAST    0 (i) 

    3   19 LOAD_GLOBAL    1 (l) 
      22 LOAD_ATTR    2 (append) 
      25 LOAD_FAST    0 (i) 
      28 CALL_FUNCTION   1 
      31 POP_TOP    
      32 JUMP_ABSOLUTE   13 
     >> 35 POP_BLOCK   
     >> 36 LOAD_CONST    0 (None) 
      39 RETURN_VALUE   
>>> dis.dis(f2) 
    2   0 BUILD_LIST    0 
       3 LOAD_GLOBAL    0 (range) 
       6 LOAD_CONST    1 (5) 
       9 CALL_FUNCTION   1 
      12 GET_ITER    
     >> 13 FOR_ITER    12 (to 28) 
      16 STORE_FAST    0 (i) 
      19 LOAD_FAST    0 (i) 
      22 LIST_APPEND    2 
      25 JUMP_ABSOLUTE   13 
     >> 28 POP_TOP    
      29 LOAD_CONST    0 (None) 
      32 RETURN_VALUE   
>>> 

Вы можете видеть, что в байтовом коде 22 у нас есть атрибут append в первой функции, так как у нас нет такой вещи во второй функции, используя понимание списка.

Также обратите внимание, что на каждой итерации вы будете иметь append attr, поэтому ваш код займет примерно 2 раза медленнее второй функции, используя понимание списка.

Кроме того, в зависимости от вашего Python и кода, списочный может работать намного быстрее, чем вручную заявление for цикла (часто примерно в два раза быстрее) , потому что их итерация выполняются на скорости языка C внутри интерпретатора, скорее , чем с ручным кодом Python. Специально для больших наборов данных, часто большое преимущество производительности для использования этого выражения. **


* Вы можете прочитать больше о Efficiency of list comprehensions

** Обучение Python by Mark Lutz

+0

Хороший ответ. Но если вы собираетесь процитировать код, пожалуйста, также включите источник, который http://blog.cdleary.com/2010/04/efficiency-of-list-comprehensions/ Edit: я вижу, что он добавлен - спасибо вы. –

+2

@JoelHinz Я прочитал эту статью несколько недель назад! мой код полностью отличается от этого, а также объяснение! в любом случае спасибо за напоминание! – Kasramvd

+0

Приносим извинения - вы правы. Я наткнулся на эту статью вчера, и я не исправил ее достаточно хорошо. –

1

Ссылаясь на статью this, это связано с тем, что атрибут appendlist не просматривается, не загружается и не вызывается как функция, что требует времени, а это добавляет итерации.

6

Даже с учетом времени, необходимого для поиска и загрузки функции append, понимание списка по-прежнему выполняется быстрее, потому что список создается на C, а не создается один элемент за раз в Python.

# 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) 
+0

примеры «Медленный» и «Быстрый» создают списки с элементами «100000000» (если мы исправляем ошибку отступа) ('setup' не повторяется для цикла' number'). Понимание списка создает список с 10000 элементами 10000 раз. Возможно, вы имели в виду 'python -mtimeit 't = []" "для i в диапазоне (10000): t.append (i)" 'vs.' python -mtimeit "t = []" "t_append = t.append" "для i в диапазоне (10000): t_append (i)" 'vs.' python -mtimeit 't = [i для i в диапазоне (10000)] "', хотя это не меняет вывод (медленный, Быстрее). – jfs