Eng*_*ero 10 python optimization iterator python-3.x
我很好奇消耗迭代器的最快方法是什么,以及最恐怖的方式.
例如,假设我想创建一个带有map内置函数的迭代器,它会积累一些副作用.我实际上并不关心结果map,只是副作用,所以我希望尽可能少地使用开销或样板来完成迭代.就像是:
my_set = set()
my_map = map(lambda x, y: my_set.add((x, y)), my_x, my_y)
Run Code Online (Sandbox Code Playgroud)
在这个例子中,我只是想通过迭代器来积累内容my_set,并且my_set只是一个空集,直到我实际运行my_map.就像是:
for _ in my_map:
pass
Run Code Online (Sandbox Code Playgroud)
或赤身裸体
[_ for _ in my_map]
Run Code Online (Sandbox Code Playgroud)
工作,但他们都觉得笨重.是否有更多的Pythonic方法来确保迭代器快速迭代,以便您可以从一些副作用中受益?
我在上面测试了上面的两种方法:
my_x = np.random.randint(100, size=int(1e6))
my_y = np.random.randint(100, size=int(1e6))
Run Code Online (Sandbox Code Playgroud)
用my_set和my_map如上所定义.我用timeit获得了以下结果:
for _ in my_map:
pass
468 ms ± 20.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
[_ for _ in my_map]
476 ms ± 12.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Run Code Online (Sandbox Code Playgroud)
两者之间没有真正的区别,他们都感到笨拙.
注意,我得到了类似的表现list(my_map),这是评论中的一个建议.
use*_*ica 12
虽然您不应该仅为副作用创建地图对象,但事实上在itertools文档中有一个消耗迭代器的标准方法:
def consume(iterator, n=None):
"Advance the iterator n-steps ahead. If n is None, consume entirely."
# Use functions that consume iterators at C speed.
if n is None:
# feed the entire iterator into a zero-length deque
collections.deque(iterator, maxlen=0)
else:
# advance to the empty slice starting at position n
next(islice(iterator, n, n), None)
Run Code Online (Sandbox Code Playgroud)
对于"完全消耗"的情况,这可以简化为
def consume(iterator):
collections.deque(iterator, maxlen=0)
Run Code Online (Sandbox Code Playgroud)
使用collections.deque这种方式可以避免存储所有元素(因为maxlen=0)并以C速度迭代,而无需字节码解释开销.在deque实现中甚至还有一个专用的快速路径,用于使用maxlen=0deque来使用迭代器.
定时:
In [1]: import collections
In [2]: x = range(1000)
In [3]: %%timeit
...: i = iter(x)
...: for _ in i:
...: pass
...:
16.5 µs ± 829 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [4]: %%timeit
...: i = iter(x)
...: collections.deque(i, maxlen=0)
...:
12 µs ± 566 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Run Code Online (Sandbox Code Playgroud)
当然,这都是基于CPython的.解释器开销的整个性质在其他Python实现上非常不同,maxlen=0快速路径特定于CPython.请参阅abarnert对其他Python实现的回答.
如果只关心CPython,这deque是最快的方法,如user2357112的answer所示。1 2.7和3.2、32位和64位以及Windows和Linux等都演示了相同的内容。
但这依赖于CPython的C实现中的优化deque。其他实现可能没有这种优化,这意味着它们最终会append为每个元素调用一个。
特别是在PyPy中,源代码中没有这样的优化,2并且JIT无法优化无操作append输出。(而且很难看到它在整个循环中每次都不需要至少进行一次防护测试。)当然,与Python循环的开销相比,是吧?但是在PyPy中,Python的循环速度很快,几乎与CPython中的C循环一样快,因此这实际上产生了很大的不同。
比较时间(使用与用户答案相同的测试:3
for deque
CPython 19.7us 12.7us
PyPy 1.37us 23.3us
Run Code Online (Sandbox Code Playgroud)
没有其他主要解释器的3.x版本,而且我都没有IPython,但是对Jython进行的快速测试显示了相似的效果。
因此,最快的可移植实现是这样的:
if sys.implementation.name == 'cpython':
import collections
def consume(it):
return collections.deque(it, maxlen=0)
else:
def consume(it):
for _ in it:
pass
Run Code Online (Sandbox Code Playgroud)
当然,这在CPython中为我提供12.7us,在PyPy中为1.41us。
1.当然,您可以编写一个自定义的C扩展,但是它只会以一个很小的常数项来加快速度–您可以在跳转到快速路径之前避免进行构造函数调用和测试,但是一旦进入该循环,您便可以必须确切地做它在做什么。
2.跟踪PyPy源代码总是很有趣……但是我认为它最终出现在W_Deque该类中,该类是内置_collections模块的一部分。
3. CPython 3.6.4; PyPy 5.10.1 / 3.5.3; 均来自相应的标准64位macOS安装程序。