Why itertools rocks

    zip vs izip или «ну кому ещё объяснить фишку итераторов?»



    Таск прост: [0,1,2,3,...,99999] -> [(0,1), (2,3), ..., (99998, 99999)], т.е. выбрать элементы попарно. 100тыс это не так уж много — посему сделаем это по 150 раз для каждого варианта.

    Т.к. тест тестит именно скорость выборки а не засовывания результатов в список — возвращать будем только последний элемент (высчитываются все равно все) — лишь чтобы сравнить с эталонным.

    Copy Source | Copy HTML
    1. def bench():
    2.     import sys
    3.     from time import time
    4.     from itertools import izip, islice
    5.  
    6.     iterations = range(150)
    7.     x = range(100000)
    8.  
    9.     def chk(name, func):
    10.         t = time()
    11.         for i in iterations:
    12.             result = func(x)
    13.         res = 'ok' if tuple(result) == (x[-2], x[-1]) else '!!'
    14.         print '[%s] %10s: %0.4f' % (res, name, time()-t)
    15.         if res == '!!':
    16.             print 'auch!'
    17.             print (x[-2], x[-1])
    18.             print result
    19.         sys.stdout.flush()
    20.  
    21.     def test_zip(d):
    22.         for (val1, val2) in zip(d[::2], d[1::2]):
    23.             res = val1, val2
    24.         return res
    25.  
    26.     def test_izip(d):
    27.         for (val1, val2) in izip(islice(d, 0, None, 2), islice(d, 1, None, 2)):
    28.             res = val1, val2
    29.         return res
    30.  
    31.     def test_for(d):
    32.         for ix in range(0, len(x), 2):
    33.             res = x[ix], x[ix+1]
    34.         return res
    35.  
    36.     def test_for_slice(d):
    37.         for ix in range(0, len(x), 2):
    38.             res = x[ix:ix+2]
    39.         return res
    40.  
    41.     def test_ifor(d):
    42.         for ix in xrange(0, len(x), 2):
    43.             res = x[ix], x[ix+1]
    44.         return res
    45.  
    46.     def test_for_enum(d):
    47.         for idx, val1 in enumerate(x[::2]):
    48.             res = val1, x[idx*2+1]
    49.         return res
    50.  
    51.     def test_for_count(d):
    52.         idx = -1
    53.         for val1 in x[::2]:
    54.             idx += 2
    55.             res = val1, x[idx]
    56.         return res
    57.  
    58.     def test_for_islice(d):
    59.         idx = -1
    60.         for val1 in islice(x, 0, None, 2):
    61.             idx += 2
    62.             res = val1, x[idx]
    63.         return res
    64.  
    65.     chk('zip', test_zip)
    66.     chk('izip', test_izip)
    67.     chk('for', test_for)
    68.     chk('for+slice', test_for_slice)
    69.     chk('ifor', test_ifor)
    70.     chk('for+enum', test_for_enum)
    71.     chk('for+cnt', test_for_count)
    72.     chk('for+islice', test_for_islice)


    Результаты (C2Q@6700)
    In [780]: bench()
    [ok]        zip: 9.1446
    [ok]       izip: 1.1836
    [ok]        for: 1.6922
    [ok]  for+slice: 2.4799
    [ok]       ifor: 1.5846
    [ok]   for+enum: 1.9567
    [ok]    for+cnt: 1.3093
    [ok] for+islice: 1.3616
    


    Иными словами, они почти так же эффективны как сырой счетчик (for+cnt) в режиме «ничего лишнего». У большинства не итерированных функций (как zip vs izip выше) функции, возвращающие итераторы опять же выигрывают и когда списки много меньше:

    # values for bench() above
    iterations = range(1500000)
    x = range(10)
    
    [ok]        zip: 3.7247
    [ok]       izip: 3.2949
    [ok]        for: 3.2703
    [ok]  for+slice: 3.9095
    [ok]       ifor: 2.9077
    [ok]   for+enum: 3.9383
    [ok]    for+cnt: 2.3443
    [ok] for+islice: 2.3495
    


    Но, тут уже обычный for+счетчик выигрывают экономя на вызове функций.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 31
    • 0
      А как по мне — доступно)
      • +4
        Я тут позволил себе раскрасить эту простыню — s-c.me/c_
        • +1
          Это такой намёк на то, что её можно скопипастить в топик.
          • 0
            спс)
            • 0
              добротная кстати раскрашивалка. Запоминается — легче не придумаешь)
        • НЛО прилетело и опубликовало эту надпись здесь
          • +1
            тот у кого всегда в голове висел дурацкий вопрос на счет реальной скорости итераторов при перещелкивании простых значений — меня поймёт. Я наверное с год терпел пока наконец не сел и не смастерил быстренько все что там вверху.

            А запостил сюда потому что до глубины души был уверен что простой цикл итератор разорвёт как тузик грелку. Только потом уже когда залез в исходники itertools.c — понял что там далеко не все так просто)
            • 0
              кхм… а может быть с «ничего лишнего» я и немного перегнул палку…
            • 0
              В тьетьем питоне в направлении итераторов серьёзно поработали, и itertools там вроде бы больше не нужны.
              В частности: range, zip, filter, map — сразу сделаны итераторами.
              • 0
                а они там в поставке питона присутствуют? Если да — то всё таки нужны. А если нет — то и впрямь значит не нужны =). Очевидно, Ватсон).
                • 0
                  What’s New In Python 3.0: Views And Iterators Instead Of Lists

                  но кое что из itertools таки ещё полезно.
                  • 0
                    стоит иметь ввиду что itertools это все таки сам питон тоже. Стандартная библиотека, а не кем-то на коленке написанная.
                    • 0
                      да, про отсутствие необходимости itertools я погорячился.

                      но всё, что у вас в примерах — izip, islice, xrange из itertools перенесены в core
              • 0
                и таки надо учиться писать алгоритмы которые не используют «статические» списки.
                более функциональное программирование.
                • 0
                  иногда очень уж эффективно. Это фактически аналог ссылок на пямять в си с небольшим овертаймом.

                  Так, например, надо было мне в базу почти полмиллиона записей засунуть (не спрашивайте зачем — правдо надо). Для них данные — в трёх списках. Казалось бы собрать один список и отдать его в executemany будет круто, но требуется почти 2 секунды лишнего времени на сие действо (а сам запрос — 3сек).

                  А вот так — 3секунды на запрос и 0.0000 на формирование «списка» )
                  data = ([node_id] + grant + extra for (node_id, grant, extra) in izip(self.tree_data, self.grant_data, self.extra_data))
                  • 0
                    я чото недогоняю, наверно.
                    data = (...) — это как?
                    • +1
                      генератор же! ай-ай-ай как не стыдно):

                      In [115]: x = ([z, z+1] for z in [1,2,3])
                      In [116]: type x
                      --------> type(x)
                      Out[116]: <type 'generator'>
                      In [117]: x.next()
                      Out[117]: [1, 2]
                      In [118]: x.next()
                      Out[118]: [2, 3]
                      In [119]: x.next()
                      Out[119]: [3, 4]
                      In [120]: x.next()
                      ---------------------------------------------------------------------------
                      StopIteration Traceback (most recent call last)

                      /home/mocksoul/workspace/ipidev/repos/ipimanager/topic-access-refactor/in ()
                      • 0
                        В питоне эти оплоты функционального программирования иногда очень в тему приходятся. Так — как минимум некоторые вещи можно сделать весьма компактными. А т.к. внутри функциональных выражений не может быть присваивания (априори) — питон оптимизирует их, т.к. не надо следить за переменными.
                        • 0
                          … ушол переписывать кучу кода… :)
                        • 0
                          о! стыдно! :)

                          ну, по сути, я это и имел ввиду, говоря «ещё надо научиться использовать» :)
                          • 0
                            насиловать себя пытаясь это понять, однако, не стоит. Само придёт. Можно форсировать канеш, например, покопавшись в erlang, lisp и иже с ним).
                            • 0
                              если не «насиловать», то можно так и остаться на уровне алголо-ориентированных алгоритмов по урокам паскаля и це.
                              • 0
                                ну главное всё таки результат, а на Си результаты бывают огого какие)
                                • 0
                                  существенную роль играет всёже и эффективность разработки (по времени, по соотношению траха/фана, итд).

                                  владея более широким спектром приёмов программирования — эту стоимость можно существенно сократить.
                                  а владея другими подходами программирования — так это на порядок.

                                  эта эффективность иногда кажется даже важнее, чем эффективность результата.
                                    • 0
                                      это в соседнем окошке открыто и было :)
                                      но там дисклэймер: «это для тех, кто разработкой зарабатывает, а не развивается» :)
                                      • 0
                                        написал над кодом что вообще в коде происходит. Учитесь наздоровье =).
                                        • 0
                                          ну я понял, что происходит.
                                          и глядя на это, задумался, что у меня всё нифига не эффективно.

                                          вобще, за пост спасибо.
                                          весьма наглядные примеры.
                                          • 0
                                            я знал что он найдёт своего читателя))
                                            • 0
                                              так совпало, что я какраз сейчас обрабатываю списки, и накопилось много ужасного кода.
                  • 0
                    Спасибо:)

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.