Производительность операторов потока управления python - PullRequest
0 голосов
/ 08 июня 2018

Я читаю вики Python о loops и там говорится, что

В Python версии 2.0 также были добавлены списки.Они предоставляют синтаксически более компактный и более эффективный способ написания вышеприведенного цикла for:

Однако я обнаружил, что при тестировании этого я получаю некоторые неожиданные результаты.

In [22]: def while_loop(n):
    ...:     i = 0
    ...:     while i < n:
    ...:         i+=1
    ...:

In [23]: def while_loop_2(n):
    ...:     while n > 0:
    ...:         n-=1
    ...:

In [24]: def for_loop(n):
    ...:     for _ in range(n):
    ...:         pass
    ...:

In [30]: %timeit(for_loop(1000000))
10 loops, best of 3: 23.9 ms per loop

In [31]: %timeit(while_loop(1000000))
10 loops, best of 3: 37.1 ms per loop

In [32]: %timeit(while_loop_2(1000000))
10 loops, best of 3: 38 ms per loop

In [33]: %timeit([1 for _ in range(1000000)])
10 loops, best of 3: 43.2 ms per loop

Что приводит меня к некоторым вопросам:

  1. Почему цикл for намного быстрее, чем понимание списка?(Кажется, это почти в два раза быстрее)

  2. Почему while_loop_2 медленнее, чем while_loop?Почему разница увеличения или уменьшения счетчика дает разницу в скорости?Моя наивность заставляет меня верить, что меньше строк кода = быстрее - ясно, что это не так

РЕДАКТИРОВАТЬ: Это было сделано в Python 2.7.В 3.6 while_loop_2 на самом деле быстрее, чем while_loop.Итак, новый вопрос:

Какая разница в циклах while между Python 2.7 и 3.x?

Ответы [ 2 ]

0 голосов
/ 08 июня 2018

Вы забыли проверить above loop в ссылке:

newlist = []
for word in oldlist:
    newlist.append(word.upper())

итерация с добавлением в список:

In [104]: %%timeit
     ...: alist = []
     ...: for i in range(10000):
     ...:    alist.append(i)
     ...: 
1.07 ms ± 10.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

эквивалентное понимание списка

In [105]: timeit [i for i in range(10000)]
491 µs ± 20.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Это действительно быстрее, чем цикл добавления списка.

создание того же списка из объекта диапазона:

In [106]: timeit list(range(10000))
265 µs ± 679 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Ничего не делать итерация:

In [107]: %%timeit
     ...: for i in range(10000):
     ...:    pass
     ...: 
273 µs ± 9.15 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Некоторые другие создания списка:

In [109]: timeit list(map(lambda i:i, range(10000)))
1.41 ms ± 3.12 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [110]: timeit list(i for i in range(10000))
784 µs ± 19.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
0 голосов
/ 08 июня 2018

В качестве преамбулы вы должны знать, что ваши "сравнения" должны анализироваться изолированно (а не сравниваться друг с другом), потому что

  1. Цикл for - это фиксированный итератор, который ничего не делает внутриего тело
  2. Циклы while выполняют декремент / приращение в своем теле, а
  3. Понимание списка - это больше, чем просто цикл for, и с этим я перехожу к ответу на вопрос #1.

# 1, потому что цикл for повторяется.Понимание списка повторяется, и создает список в памяти.Это, конечно, способствует общему времени.Одного этого должно быть достаточно, чтобы убедить вас, но если это не так, взгляните на разобранный байт-код, чтобы увидеть, что делает каждый.Для этого вы можете использовать модуль dis.Я на самом деле использую dis, чтобы ответить на ваш третий вопрос.


# 2, поэтому я не могу воспроизвести на python3.6.

%%timeit
i = 0; n = 100000
while i < n: i += 1

11.5 ms ± 65.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit
n = 100000
while  n > 0: n -= 1

10.8 ms ± 380 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Как правило, цикл на основе декремента должен быть немного быстрее, потому что сравнение с 0 (n > 0) обычно быстрее, чем сравнение с ненулевыми значениями (i < n).Но дельта обычно «серьезно, не беспокойтесь об этом», вроде небольшая.


Чтобы ответить на вопрос № 3, нам нужно немного покопаться.Давайте посмотрим на байт-код.

import dis

python3.6

dis.dis(
'''n = 100000
while  n > 0: n -= 1'''
)

  1           0 LOAD_CONST               0 (100000)
              2 STORE_NAME               0 (n)

  2           4 SETUP_LOOP              20 (to 26)
        >>    6 LOAD_NAME                0 (n)
              8 LOAD_CONST               1 (0)
             10 COMPARE_OP               4 (>)
             12 POP_JUMP_IF_FALSE       24
             14 LOAD_NAME                0 (n)
             16 LOAD_CONST               2 (1)
             18 INPLACE_SUBTRACT
             20 STORE_NAME               0 (n)
             22 JUMP_ABSOLUTE            6
        >>   24 POP_BLOCK
        >>   26 LOAD_CONST               3 (None)
             28 RETURN_VALUE

python2.7

dis.dis(
'''n = 100000
while  n > 0: n -= 1'''
)
          0 JUMP_FORWARD    15648 (to 15651)
          3 SLICE+2        
          4 <49>           
          5 <48>           
          6 <48>           
          7 <48>           
          8 <48>           
          9 <48>           
         10 UNARY_POSITIVE 
         11 CONTINUE_LOOP   26984
         14 IMPORT_NAME      8293 (8293)
         17 SLICE+2        
         18 JUMP_FORWARD    15904 (to 15925)
         21 SLICE+2        
         22 <48>           
         23 INPLACE_DIVIDE 
         24 SLICE+2        
         25 JUMP_FORWARD    11552 (to 11580)
         28 DELETE_SUBSCR  
         29 SLICE+2        
         30 <49>

Обратите внимание, что существуют огромные различия в сгенерированном байт-коде.Разница лежит здесь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...