Pull to refresh

Why itertools rocks

Python
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. iterations = range(150)
  6. x = range(100000)
  7. def chk(name, func):
  8. t = time()
  9. for i in iterations:
  10. result = func(x)
  11. res = 'ok' if tuple(result) == (x[-2], x[-1]) else '!!'
  12. print '[%s] %10s: %0.4f' % (res, name, time()-t)
  13. if res == '!!':
  14. print 'auch!'
  15. print (x[-2], x[-1])
  16. print result
  17. sys.stdout.flush()
  18. def test_zip(d):
  19. for (val1, val2) in zip(d[::2], d[1::2]):
  20. res = val1, val2
  21. return res
  22. def test_izip(d):
  23. for (val1, val2) in izip(islice(d, 0, None, 2), islice(d, 1, None, 2)):
  24. res = val1, val2
  25. return res
  26. def test_for(d):
  27. for ix in range(0, len(x), 2):
  28. res = x[ix], x[ix+1]
  29. return res
  30. def test_for_slice(d):
  31. for ix in range(0, len(x), 2):
  32. res = x[ix:ix+2]
  33. return res
  34. def test_ifor(d):
  35. for ix in xrange(0, len(x), 2):
  36. res = x[ix], x[ix+1]
  37. return res
  38. def test_for_enum(d):
  39. for idx, val1 in enumerate(x[::2]):
  40. res = val1, x[idx*2+1]
  41. return res
  42. def test_for_count(d):
  43. idx = -1
  44. for val1 in x[::2]:
  45. idx += 2
  46. res = val1, x[idx]
  47. return res
  48. def test_for_islice(d):
  49. idx = -1
  50. for val1 in islice(x, 0, None, 2):
  51. idx += 2
  52. res = val1, x[idx]
  53. return res
  54. chk('zip', test_zip)
  55. chk('izip', test_izip)
  56. chk('for', test_for)
  57. chk('for+slice', test_for_slice)
  58. chk('ifor', test_ifor)
  59. chk('for+enum', test_for_enum)
  60. chk('for+cnt', test_for_count)
  61. 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:pythonitertoolsитераторитераторыбенчмаркbenchbenchmark
Hubs: Python
Total votes 16: ↑11 and ↓5 +6
Views6.1K

Comments 31

Only those users with full accounts are able to leave comments. Log in, please.

Popular right now