0

我正在尝试对一些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 '===========================================

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

我得到的结果请注意,编辑后的版本没有在同一台机器上运行(因此对标准化结果很有用),而是使用 32 位解释器和 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
======================================================

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

我知道我的装饰器中的条件可能会影响结果。我希望对此提出一些建议。

谢谢。

4

1 回答 1

6

首先,不要尝试复制所有内容timeit,而是使用它。该time函数可能没有足够的准确性来发挥作用,而编写数十行不需要的脚手架代码(尤其是如果它必须进行诸如打开之类的骇人听闻的事情时func.__name__)只是无缘无故地招致错误。

假设没有错误,它可能不会显着影响结果。您正在做一些额外的工作并将其充电到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 '======================================================'

接下来,你为什么要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])

或者,或者,只让函数返回一个生成器/ifilter/list,然后discard对结果进行脚手架调用(无论哪种方式都不重要)。


同时,对于这种testIterator情况,您是在尝试测试 lambda 与内联表达式的成本,还是ifilter与生成器的成本?如果要测试前者,这是正确的;如果是后者,您可能想要优化它。例如,在 64 位 Python 3.3.0 中通过sample.__contains__而不是lambda x: x in sample似乎快 20%,在 32 位 2.7.2 中快 30%(尽管由于某种原因在 64 位 2.7.2 中根本不快)。


最后,除非您只测试一个实现/平台/版本,否则请确保尽可能多地运行它。例如,使用 64 位 CPython 2.7.2,list并且随着列表的增长从 1.0 倍逐渐攀升到 1.4 倍,generator总是并驾齐驱,但在 PyPy 1.9.0 中,总是最快的,从2.1 开始x 和 1.9x 慢,但随着列表的增长接近 1.2x。iteratoriteratorgeneratorlist

因此,如果您因为“它很慢”而决定反对迭代器,那么您可能会用 PyPy 的大幅减速换取 CPython 的小得多的加速。

当然,这可能是可以接受的,例如,因为即使是最慢的 PyPy 运行速度也非常快,或者因为您的用户都没有使用 PyPy,等等。但这绝对是“这个基准是否相关?”答案的一部分。

于 2013-04-05T21:46:01.333 回答