Qua*_*cht 0 python benchmarking iterator list-comprehension generator
我试图对一些itertools针对生成器和列表推导的方法进行基准测试.我的想法是,我想通过过滤基本列表中的一些条目来构建迭代器.
这是我提出的代码(在接受的答案后编辑):
from itertools import ifilter
import collections
import random
import os
from timeit import Timer
os.system('cls')
# define large arrays
listArrays = [xrange(100), xrange(1000), xrange(10000), xrange(100000)]
#Number of element to be filtered out
nb_elem = 100
# Number of times we run the test
nb_rep = 1000
def discard(it):
collections.deque(it, maxlen=0)
def testGenerator(arr, sample):
discard(x for x in sample if x in arr)
def testIterator(arr, sample):
discard(ifilter(sample.__contains__, arr))
def testList(arr, sample):
discard([x for x in sample if x in arr])
if __name__ == '__main__':
for arr in listArrays:
print 'Size of array: %s ' % len(arr)
print 'number of iterations %s' % nb_rep
sample = random.sample(arr, nb_elem)
t1 = Timer('testIterator(arr, sample)', 'from __main__ import testIterator, arr, sample')
tt1 = t1.timeit(number=nb_rep)
t2 = Timer('testList(arr, sample)', 'from __main__ import testList, arr, sample')
tt2 = t2.timeit(number=nb_rep)
t3 = Timer('testGenerator(arr, sample)', 'from __main__ import testGenerator, arr, sample')
tt3 = t3.timeit(number=nb_rep)
norm = min(tt1, tt2, tt3)
print 'maximum runtime %.6f' % max(tt1, tt2, tt3)
print 'normalized times:\n iterator: %.6f \n list: %.6f \n generator: %.6f' % \
(tt1/norm, tt2/norm, tt3/norm)
print '===========================================
Run Code Online (Sandbox Code Playgroud)
==========="
我得到的结果请注意,编辑后的版本不是在同一台机器上运行(因此有规范化的结果很有用),并使用带有python 2.7.3的32位解释器运行:
Size of array: 100
number of iterations 1000
maximum runtime 0.125595
normalized times:
iterator: 1.000000
list: 1.260302
generator: 1.276030
======================================================
Size of array: 1000
number of iterations 1000
maximum runtime 1.740341
normalized times:
iterator: 1.466031
list: 1.010701
generator: 1.000000
======================================================
Size of array: 10000
number of iterations 1000
maximum runtime 17.033630
normalized times:
iterator: 1.441600
list: 1.000000
generator: 1.010979
======================================================
Size of array: 100000
number of iterations 1000
maximum runtime 169.677963
normalized times:
iterator: 1.455594
list: 1.000000
generator: 1.008846
======================================================
Run Code Online (Sandbox Code Playgroud)
您能否就改进提出一些建议,并评论该基准是否可以给出准确的结果?
我知道装饰师的情况可能会影响结果.我希望就此提出一些建议.
谢谢.
首先,不要试图复制所有内容timeit,而是使用它.该time函数可能没有足够的准确性来使用,并且编写几十行脚手架代码(特别是如果它必须像开启一样的hacky func.__name__)你不需要的只是无缘无故地邀请bug.
假设没有错误,它可能不会显着影响结果.你正在做一些额外的工作并收费testIterator,但每个外循环只有一次.但是,这样做没有任何好处,所以不要这样做.
def testGenerator(arr,sample):
for i in (x for x in sample if x in arr):
k = random.random()
def testIterator(arr,sample):
for i in ifilter(lambda x: x in sample, arr):
k = random.random()
def testList(arr,sample):
for i in [x for x in sample if x in arr]:
k = random.random()
tests = testIterator, testGenerator, testList
for arr in listArrays:
print 'Size of array: %s ' % len(arr)
print 'number of iterations %s' % nb_rep
sample = random.sample(arr, nb_elem)
funcs = [partial(test, arr, sample) for test in tests]
times = [timeit.timeit(func, number=nb_rep) for func in funcs]
norm = min(*times)
print 'maximum runtime %.6f' % max(*times)
print 'normalized times:\n iterator: %.6f \n list: %.6f \n generator: %.6f' % (times[0]/norm,times[1]/norm,times[2]/norm)
print '======================================================'
Run Code Online (Sandbox Code Playgroud)
接下来,你为什么要那样做k = random.random()呢?从快速测试来看,只需要执行该行N次而不需要复杂的循环,只要整个就行了0.19倍.所以,你要为每个数字增加20%,这无缘无故地削弱了它们之间的差异.
一旦你摆脱了它,for除了使用迭代器之外,循环没有任何意义,并且这也增加了额外的开销.从2.7.3和3.3.0开始,使用没有自定义C代码的迭代器的最快方法是deque(it, maxlen=0),让我们试试这个:
def discard(it):
collections.deque(it, maxlen=0)
def testGenerator(arr,sample):
discard(x for x in sample if x in arr)
def testIterator(arr,sample):
discard(ifilter(sample.__contains__, arr))
def testList(arr,sample):
discard([x for x in sample if x in arr])
Run Code Online (Sandbox Code Playgroud)
或者,只是让函数返回一个generator/ifilter/list然后discard对结果进行脚手架调用(无论哪种方式都无关紧要).
同时,对于这种testIterator情况,您是否尝试测试lambda与内联表达式的成本,或者ifilter与生成器相比的成本?如果你想测试前者,这是正确的; 如果是后者,你可能想要优化它.例如,传递sample.__contains__,而不是lambda x: x in sample似乎是快20%,在64位的Python 3.3.0和30%的在32位2.7.2更快(尽管由于某种原因,不更快都在64位2.7.2).
最后,除非您只测试一个实现/平台/版本,否则请确保尽可能多地运行它.例如,对于64位CPython的2.7.2,list并generator总是脖子和颈部,而iterator从1.0倍逐步上升到1.4倍的清单增长,但PyPy 1.9.0,iterator总是最快的,与generator和list出发2.1 x和1.9倍慢,但随着列表的增长接近1.2倍.
因此,如果您决定反对迭代器因为"它很慢",那么您可能会在PyPy上大幅减速,以便在CPython上实现更小的加速.
当然这可能是可以接受的,例如,因为即使是最慢的PyPy运行速度也非常快,或者因为没有一个用户使用PyPy,或者其他什么.但这肯定是"这个基准相关吗?"答案的一部分.