当我事先知道它的长度时,我可以加速一个可迭代的课程吗?

时间:2017-01-11 03:06:55

标签: python performance python-internals

PEP 424在"理由"中提及的是:

  

能够根据__length_hint__估计的预期大小预先分配列表,这可能是一项重要的优化。已经观察到CPython比PyPy更快地运行一些代码,纯粹是因为存在这种优化。

所以我问自己我现在问的问题:是否有可能加快一些可迭代类处理迭代器(当它可以正确预测它时&#) #39; s"长度")基于这些知识?

2 个答案:

答案 0 :(得分:2)

抛开生成器/迭代器术语混淆,__length_hint__方法是一个非常小的优化,我只会在特殊情况下使用。我写了自己的简单小测试:

class Range:

    def __init__(self, n):
        self._n = n
        self._i = 0

    def __iter__(self):
        return self

    def __next__(self):
        i = self._i
        if i >= self._n:
            raise StopIteration
        self._i += 1
        return i

class RangeWithHint(Range):

    def __length_hint__(self):
        return self._n

如果这用于生成值列表,那么预分配列表的优势只能通过大约一百万个元素的大型列表来衡量,即使这样也非常小:

Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> timeit("xs = list(Range(1000000))", "from __main__ import Range", number=10)
5.068971888250076
>>> timeit("xs = list(RangeWithHint(1000000))", "from __main__ import RangeWithHint", number=10)
4.7962311912107225

外卖:Python在增长时重新分配列表已经非常非常快。不要以为__length_hint__会大大提高速度。

答案 1 :(得分:1)

关于做两个实验的结论(一个在收到@TerryJanReedy的反馈后):

在具有长迭代次数的简单情况下,可以存在显着(高达50%)优化但是在绝对性能中,只要使用或执行某些更复杂的操作,它就可以忽略不计该项目或可迭代项目非常短。

设置

我实现了一个只迭代一些迭代器的类,还有一个map - 就像将一个函数应用于每个项目一样。这两个类有两个变体,一个没有实现__length_hint__,另一个没有实现。

我选择Cython删除尽可能多的Python开销:

from operator import length_hint

cdef class MyIter(object):
    cdef object it

    def __init__(self, iterable):
        self.it = iter(iterable)

    def __iter__(self):
        return self

    def __next__(self):
        return next(self.it)

cdef class MyIter2(object):
    cdef object it

    def __init__(self, iterable):
        self.it = iter(iterable)

    def __iter__(self):
        return self

    def __next__(self):
        return next(self.it)

    # --- This method is new ---
    def __length_hint__(self):
        return length_hint(self.it)

# Map-like classes

cdef class MyMap(object):
    cdef object func
    cdef object it

    def __init__(self, func, iterable):
        self.it = iter(iterable)
        self.func = func

    def __iter__(self):
        return self

    def __next__(self):
        return self.func(next(self.it))

cdef class MyMap2(object):
    cdef object func
    cdef object it

    def __init__(self, func, iterable):
        self.it = iter(iterable)
        self.func = func

    def __iter__(self):
        return self

    def __next__(self):
        return self.func(next(self.it))

    # --- This method is new ---
    def __length_hint__(self):
        return length_hint(self.it)

计时

我使用Python 3.5和Ipythons %timeit命令进行了计时:

import random

lengths1 = []
timing1 = []
timing2 = []

lengths2 = []
timing3 = []
timing4 = []

for _ in range(30):
    i = random.randint(1, 1000000)
    lengths1.append(i)
    lst = list(range(i))

    res1 = %timeit -o list(MyIter(lst))
    timing1.append(res1)
    res2 = %timeit -o list(MyIter2(lst))
    timing2.append(res2)

    i = random.randint(1, 100000)  # factor 10 less items
    lengths2.append(i)
    lst = list(range(i))

    res3 = %timeit -o list(MyMap(float, lst))
    timing3.append(res3)
    res4 = %timeit -o list(MyMap2(float, lst))
    timing4.append(res4)

时差(timing1 - timing2)和相对时差(100 * (timing1 - timing2) / timing1)的结果:

MyIter

enter image description here

这表示长期迭代的重要优化(最多50%)。

MyMap中

enter image description here

所以__length_hint__的那个有时会更快但不是我称之为重要的