Как указал пользователь 2357112, исходный сценарий теста производительности не работал, как я ожидал. «после первого выполнения s1 в вашем списке нет 1, поэтому никакие дальнейшие выполнения s1 и s2 фактически не используют ветвь x == 1». Модифицированная версия:
import timeit
import random
random.seed(0)
a = [ random.randrange(10) for _ in range(10000)]
change_from = 1
change_to = 6
setup = "from __main__ import a, change_from, change_to"
# s1 is replaced for a simple for loop, which is faster than the original
s1 = """\
for i,x in enumerate(a):
if x == change_from:
a[i] = change_to
change_from, change_to = change_to, change_from
"""
s2 = """\
a = [change_to if x==change_from else x for x in a]
change_from, change_to = change_to, change_from
"""
print(timeit.timeit(stmt=s1,number=10000, setup=setup))
print(timeit.timeit(stmt=s2, number=10000, setup=setup))
Этот скрипт заменяет все вхождения от 1 до 6, а при следующем запуске все вхождения от 6 до 1. И так далее. Результат:
7.841739330212443
5.5166219217914065
Почему понимание списка происходит быстрее?
И как разобраться в таком вопросе? Комментарий бортрайдера выглядит интересно, спасибо.
Используется следующая версия Python: Python 3.6.0 (v3.6.0:41df79263a11, 23 декабря 2016 г., 08:06:12) [MSC v.1900 64 бит (AMD64)] на win32
Поскольку подробного ответа я не получил, я попытался разобраться.
Если я представляю простой цикл for с помощью этой функции:
def func1(a):
for i,x in enumerate(a):
if x == 1:
a[i] = 6
return(a)
и разобрав его, я получил следующее:
func1:
7 0 SETUP_LOOP 48 (to 51)
3 LOAD_GLOBAL 0 (enumerate)
6 LOAD_FAST 0 (a)
9 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
12 GET_ITER
>> 13 FOR_ITER 34 (to 50)
16 UNPACK_SEQUENCE 2
19 STORE_FAST 1 (i)
22 STORE_FAST 2 (x)
8 25 LOAD_FAST 2 (x)
28 LOAD_CONST 1 (1)
31 COMPARE_OP 2 (==)
34 POP_JUMP_IF_FALSE 13
9 37 LOAD_CONST 2 (6)
40 LOAD_FAST 0 (a)
43 LOAD_FAST 1 (i)
46 STORE_SUBSCR
47 JUMP_ABSOLUTE 13
>> 50 POP_BLOCK
10 >> 51 LOAD_FAST 0 (a)
54 RETURN_VALUE
Это просто. Он перебирает a и, если находит значение 1, заменяет его на 6 с помощью STORE_SUBSCR.
Если я представляю вариант понимания с этой функцией:
def func2(a): a = [6 if x==1 else x вместо x в a] return(a)
и разобрав его, я получил следующее:
func2:
7 0 LOAD_CONST 1 (<code object <listcomp> at 0x00000000035731E0, file "<file_path>", line 7>)
3 LOAD_CONST 2 ('func2.<locals>.<listcomp>')
6 MAKE_FUNCTION 0
9 LOAD_FAST 0 (a)
12 GET_ITER
13 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
16 STORE_FAST 0 (a)
8 19 LOAD_FAST 0 (a)
22 RETURN_VALUE
Это короче предыдущего. Однако он начинается с загрузки объекта кода. У func2 есть следующие константы кода:
>>> func2.__code__.co_consts
(None, <code object <listcomp> at 0x00000000035731E0, file "<file_path>", line 7>, 'func2.<locals>.<listcomp>')
и объект кода listcomp выглядит так:
>>> dis.dis(func2.__code__.co_consts[1].co_code)
0 BUILD_LIST 0
3 LOAD_FAST 0 (0)
>> 6 FOR_ITER 30 (to 39)
9 STORE_FAST 1 (1)
12 LOAD_FAST 1 (1)
15 LOAD_CONST 0 (0)
18 COMPARE_OP 2 (==)
21 POP_JUMP_IF_FALSE 30
24 LOAD_CONST 1 (1)
27 JUMP_FORWARD 3 (to 33)
>> 30 LOAD_FAST 1 (1)
>> 33 LIST_APPEND 2
36 JUMP_ABSOLUTE 6
>> 39 RETURN_VALUE
Таким образом, две реализации выполняют аналогичные шаги. Основное отличие состоит в том, что в версии для понимания FOR_ITER заменяется на CALL_FUNCTION.
Из этого я должен понять, почему понимание списка происходит быстрее, но я этого не делаю. Итак, мой первоначальный вопрос все еще актуален:
Почему понимание списка происходит быстрее?
how should one figure out this kind of question?
stackoverflow.com/questions/41051553/ - person boardrider   schedule 18.01.2017