zip vs izip или «ну кому ещё объяснить фишку итераторов?»
Таск прост: [0,1,2,3,...,99999] -> [(0,1), (2,3), ..., (99998, 99999)], т.е. выбрать элементы попарно. 100тыс это не так уж много — посему сделаем это по 150 раз для каждого варианта.
Т.к. тест тестит именно скорость выборки а не засовывания результатов в список — возвращать будем только последний элемент (высчитываются все равно все) — лишь чтобы сравнить с эталонным.
Результаты (C2Q@6700)
Иными словами, они почти так же эффективны как сырой счетчик (for+cnt) в режиме «ничего лишнего». У большинства не итерированных функций (как zip vs izip выше) функции, возвращающие итераторы опять же выигрывают и когда списки много меньше:
Но, тут уже обычный for+счетчик выигрывают экономя на вызове функций.
Таск прост: [0,1,2,3,...,99999] -> [(0,1), (2,3), ..., (99998, 99999)], т.е. выбрать элементы попарно. 100тыс это не так уж много — посему сделаем это по 150 раз для каждого варианта.
Т.к. тест тестит именно скорость выборки а не засовывания результатов в список — возвращать будем только последний элемент (высчитываются все равно все) — лишь чтобы сравнить с эталонным.
Copy Source | Copy HTML
- def bench():
- import sys
- from time import time
- from itertools import izip, islice
-
- iterations = range(150)
- x = range(100000)
-
- def chk(name, func):
- t = time()
- for i in iterations:
- result = func(x)
- res = 'ok' if tuple(result) == (x[-2], x[-1]) else '!!'
- print '[%s] %10s: %0.4f' % (res, name, time()-t)
- if res == '!!':
- print 'auch!'
- print (x[-2], x[-1])
- print result
- sys.stdout.flush()
-
- def test_zip(d):
- for (val1, val2) in zip(d[::2], d[1::2]):
- res = val1, val2
- return res
-
- def test_izip(d):
- for (val1, val2) in izip(islice(d, 0, None, 2), islice(d, 1, None, 2)):
- res = val1, val2
- return res
-
- def test_for(d):
- for ix in range(0, len(x), 2):
- res = x[ix], x[ix+1]
- return res
-
- def test_for_slice(d):
- for ix in range(0, len(x), 2):
- res = x[ix:ix+2]
- return res
-
- def test_ifor(d):
- for ix in xrange(0, len(x), 2):
- res = x[ix], x[ix+1]
- return res
-
- def test_for_enum(d):
- for idx, val1 in enumerate(x[::2]):
- res = val1, x[idx*2+1]
- return res
-
- def test_for_count(d):
- idx = -1
- for val1 in x[::2]:
- idx += 2
- res = val1, x[idx]
- return res
-
- def test_for_islice(d):
- idx = -1
- for val1 in islice(x, 0, None, 2):
- idx += 2
- res = val1, x[idx]
- return res
-
- chk('zip', test_zip)
- chk('izip', test_izip)
- chk('for', test_for)
- chk('for+slice', test_for_slice)
- chk('ifor', test_ifor)
- chk('for+enum', test_for_enum)
- chk('for+cnt', test_for_count)
- 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+счетчик выигрывают экономя на вызове функций.