Pull to refresh

Why itertools rocks

Reading time 4 min
Views 6.5K
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+счетчик выигрывают экономя на вызове функций.
Tags:
Hubs:
+6
Comments 31
Comments Comments 31

Articles