PEP 424在"理由"中提及的是:
能够根据
__length_hint__
估计的预期大小预先分配列表,这可能是一项重要的优化。已经观察到CPython比PyPy更快地运行一些代码,纯粹是因为存在这种优化。
所以我问自己我现在问的问题:是否有可能加快一些可迭代类处理迭代器(当它可以正确预测它时&#) #39; s"长度")基于这些知识?
答案 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
)的结果:
这表示长期迭代的重要优化(最多50%)。
所以__length_hint__
的那个有时会更快但不是我称之为重要的。