这个基准似乎相关吗? [英] Does this benchmark seem relevant?

查看:114
本文介绍了这个基准似乎相关吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试针对生成器和列表推导对 itertools 的一些方法进行基准测试。我的想法是,我希望通过过滤基本列表中的一些条目来构建迭代器。



这是我提出的代码(在接受的答案后编辑):<来自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)]

#要滤除的元素数量
nb_elem = 100
#我们运行测试的次数
nb_rep = 1000


def discard(it):
collections.deque(it,maxlen = 0)


def testGenerator(arr,sample):
discard(如果x在arr中,x为样本中的x)


def testIterator(arr,sample):
discard(ifilter(sample .__ contains _,arr))


def testList(arr,sample):
discard(如果x在arr中,则x为样本中的x)


if __name__ =='__ main__':

for listArrays中的arr:

print'数组大小:%s'%len(arr)
print'迭代次数%s'%nb_rep
sample = random.sample(arr,nb_elem)

t1 =定时器('testIterator(arr,sample)','来自__main__ import testIterator,arr,sample')
tt1 = t1.timeit (number = nb_rep)

t2 =定时器('testList(arr,sample)','来自__main__ import testList,arr,sample')
tt2 = t2.timeit(number = nb_rep )

t3 =定时器('testGenerator(arr,sample)','来自__main__ import testGenerator,arr,sample')
tt3 = t3.timeit(number = nb_rep)

norm = min(tt1,tt2,tt3)
打印'最大运行时%.6f'%max(tt1,tt2,tt3)
打印'标准化时间:\ iterator: %.6f \列表:%。6f \ n生成器:%。6f'%\
(tt1 / norm,tt2 / norm,tt3 / norm)
print'==== =================== ====================

= =========='



我得到的结果请注意,编辑后的版本不能在同一台机器上运行(因此有用)标准化结果)并使用带有python 2.7.3的32位解释器运行:

 数组大小:100 
number迭代1000
最大运行时间0.125595
标准化时间:
iterator:1.000000
list:1.260302
generator:1.276030
======== ==============================================
数组大小:1000
迭代次数1000
最大运行时间1.740341
标准化时间:
iterator:1.466031
list:1.010701
generator:1.000000
================================================ ======
数组大小:10000
迭代次数1000
最大运行时间17.033630
标准化时间:
iterator:1.441600
list: 1.000000
发电机:1.010979
======================= ===============================
数组大小:100000
迭代次数1000
最大运行时间169.677963
标准化时间:
iterator:1.455594
list:1.000000
generator:1.008846
============= =========================================

您能否就改进提出一些建议,并评论该基准是否可以给出准确的结果?



我知道装饰师的情况可能会影响结果。我希望对此提出一些建议。



谢谢。

解决方案

首先,而不是试图复制一切 timeit ,只是使用它。 时间函数可能没有足够的准确性,并且编写了几十行脚手架代码(特别是如果它必须像开启那样的hacky您不需要的func .__ name __ )只是无缘无故地邀请错误。



假设没有错误,它可能赢了' t显着影响结果。你正在做一些额外的工作并将其收费到 testIterator ,但每个外循环只需要一次。但是,这样做没有任何好处,所以不要这样做。

  def testGenerator(arr,sample):
for i in(如果x在arr中,则x为样本中的x):
k = random.random()

def testIterator(arr,sample):$ if b中i的
( lambda x:x in sample,arr):
k = random.random()

def testList(arr,sample):
for i in [x for x in sample in if x in arr]:
k = random.random()

tests = testIterator,testGenerator,testList

for listArrays中的arr:
print'Size数组:%s'%len(arr)
print'迭代次数%s'%nb_rep
sample = random.sample(arr,nb_elem)
funcs = [partial(test,test, arr,sample)for test in tests]
times = [funcs中func的[timeit.timeit(func,number = nb_rep)]
norm = min(* times)
print'最大运行时间%。6f'%max(*次)
print'标准化时间:\ iterator:%。6f \ n list:%。6f \ n generator:%。6f'%(times [0] / norm,times [1] / norm,times [2] / norm)
print'=============== ======================================='






接下来,你为什么这样做 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在arr中,x为样本中的x)

def testIterator(arr,sample):
discard(ifilter( sample .__ contains __,arr))

def testList(arr,sample):
discard([x for x in sample in x in arr])

或者,只是让函数返回一个generator / ifilter / list然后进行脚手架调用 discard 结果(无论哪种方式都无关紧要)。






同时,对于 testIterator case,您是否正在尝试测试lambda与内联表达式的成本,或 ifilter vs.的成本发电机?如果你想测试前者,这是正确的;如果是后者,你可能想要优化它。例如,传递 sample .__包含__ 而不是示例中的lambda x:x似乎比64-快20%比特Python 3.3.0和32比2.7.2快30%(虽然由于某些原因在64位2.7.2中根本不快)。






最后,除非您只测试一个实现/平台/版本,否则请确保尽可能多地运行它。例如,对于64位CPython 2.7.2,列表生成器总是一气呵成,而<随着列表的增长,code> iterator 逐渐从1.0x爬升到1.4x,但在PyPy 1.9.0中, iterator 总是最快的,生成器列表从2.1x和1.9x开始减慢,但随着列表的增长接近1.2x。 / p>

所以,如果你决定反对迭代器,因为它很慢,你可能会在PyPy上大幅减速,因为CPython的速度要小得多。



当然这可能是可以接受的,例如,因为即使是最慢的PyPy运行也非常快,或者因为没有一个用户使用PyPy,或者其他什么。但它肯定是这个基准相关的答案的答案的一部分吗?


I am trying to benchmark a few method of itertools against generators and list comprehensions. The idea is that I want to build an iterator by filtering some entries from a base list.

Here is the code I came up with(Edited after accepted answer):

   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 '===========================================

==========='

And the results that I get Please note that the edited version was not run on the same machine ( thus useful to have normalized results) and was ran with a 32bits interpreter with python 2.7.3 :

   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
======================================================

Could you give some suggestions on improvement and comment on whether or not this benchmark can give accurate results?

I know that the condition in my decorator might bias the results. I am hoping for some suggestions regarding that.

Thanks.

解决方案

First, instead of trying to duplicate everything timeit does, just use it. The time function may not have enough accuracy to be useful, and writing dozens of lines of scaffolding code (especially if it has to hacky things like switching on func.__name__) that you don't need is just inviting bugs for no reason.

Assuming there are no bugs, it probably won't affect the results significantly. You're doing a tiny bit of extra work and charging it to testIterator, but that's only once per outer loop. But still, there's no benefit to doing it, so let's not.

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 '======================================================'


Next, why are you doing that k = random.random() in there? From a quick test, just executing that line N times without the complex loop is 0.19x as long as the whole thing. So, you're adding 20% to each of the numbers, which dilutes the difference between them for no reason.


Once you get rid of that, the for loop is serving no purpose except to consume the iterator, and that's adding extra overhead as well. As of 2.7.3 and 3.3.0, the fastest way to consume an iterator without custom C code is deque(it, maxlen=0), so, let's try this:

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])

Or, alternatively, just have the functions return a generator/ifilter/list and then make the scaffolding call discard on the result (it shouldn't matter either way).


Meanwhile, for the testIterator case, are you trying to test the cost of the lambda vs. an inline expression, or the cost of ifilter vs. a generator? If you want to test the former, this is correct; if the latter, you probably want to optimize that. For example, passing sample.__contains__ instead of lambda x: x in sample seems to be 20% faster in 64-bit Python 3.3.0 and 30% faster in 32-bit 2.7.2 (although for some reason not faster at all in 64-bit 2.7.2).


Finally, unless you're just testing for exactly one implementation/platform/version, make sure to run it on as many as you can. For example, with 64-bit CPython 2.7.2, list and generator are always neck-and-neck while iterator gradually climbs from 1.0x to 1.4x as the lists grow, but in PyPy 1.9.0, iterator is always fastest, with generator and list starting off 2.1x and 1.9x slower but closing to 1.2x as the lists grow.

So, if you decided against iterator because "it's slow", you might be trading a large slowdown on PyPy for a much smaller speedup on CPython.

Of course that might be acceptable, e.g., because even the slowest PyPy run is blazingly fast, or because none of your users use PyPy, or whatever. But it's definitely part of the answer to "is this benchmark relevant?"

这篇关于这个基准似乎相关吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆