使用迭代器的最快(最Pythonic)方式

使用迭代器的最快(最Pythonic)方式,python,python-3.x,optimization,iterator,Python,Python 3.x,Optimization,Iterator,我很好奇,使用迭代器的最快方式是什么,以及最具Python风格的方式 例如,假设我想创建一个具有map内置项的迭代器,该迭代器会将某些内容累积为副作用。实际上,我并不关心映射的结果,只关心副作用,所以我希望以尽可能少的开销或模板完成迭代。比如: my_set = set() my_map = map(lambda x, y: my_set.add((x, y)), my_x, my_y) for _ in my_map: pass if sys.implementation.name

我很好奇,使用迭代器的最快方式是什么,以及最具Python风格的方式

例如,假设我想创建一个具有
map
内置项的迭代器,该迭代器会将某些内容累积为副作用。实际上,我并不关心
映射的结果,只关心副作用,所以我希望以尽可能少的开销或模板完成迭代。比如:

my_set = set()
my_map = map(lambda x, y: my_set.add((x, y)), my_x, my_y)
for _ in my_map:
    pass
if sys.implementation.name == 'cpython':
    import collections
    def consume(it):
        return collections.deque(it, maxlen=0)
else:
    def consume(it):
        for _ in it:
            pass
在本例中,我只想通过迭代器来累加
my_set
中的内容,而
my_set
只是一个空集,直到我实际运行
my_map
。比如:

my_set = set()
my_map = map(lambda x, y: my_set.add((x, y)), my_x, my_y)
for _ in my_map:
    pass
if sys.implementation.name == 'cpython':
    import collections
    def consume(it):
        return collections.deque(it, maxlen=0)
else:
    def consume(it):
        for _ in it:
            pass
还是裸体

[_ for _ in my_map]
工作,但他们都觉得笨重。是否有一种更具python风格的方法来确保迭代器快速迭代,从而从一些副作用中获益


基准 我在以下方面测试了上述两种方法:

my_x = np.random.randint(100, size=int(1e6))
my_y = np.random.randint(100, size=int(1e6))
使用上面定义的
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)
两者之间没有真正的区别,他们都觉得笨重


注意,我在
列表(my_map)
中获得了类似的性能,这是评论中的一个建议。

虽然你不应该仅仅为了副作用而创建一个map对象,但事实上,在

对于“完全消费”的情况,这可以简化为

def consume(iterator):
    collections.deque(iterator, maxlen=0)
以这种方式使用
collections.deque
可以避免存储所有元素(因为
maxlen=0
)并以C速度迭代,而不会产生字节码解释开销。deque实现中甚至有一个使用
maxlen=0
deque使用迭代器的函数

时间:

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)

当然,这都是基于CPython的。解释器开销的整个性质在其他Python实现上是非常不同的,并且
maxlen=0
fast路径特定于CPython。有关其他Python实现,请参阅。

如果您只关心CPython,
deque
是最快的方法,如.1所示,同样的事情也在2.7和3.2、32位与64位、Windows与Linux等中进行了演示

但这取决于CPython的C实现中对
deque
的优化。其他实现可能没有这样的优化,这意味着它们最终会为每个元素调用
append

特别是在PyPy中,源代码中没有这样的优化,2并且JIT无法优化没有op
append
out的优化。(很难看出每次循环都不需要至少进行一次保护测试。)当然,与Python中循环的成本相比……对吗?但是PyPy中Python中的循环速度非常快,几乎和CPython中的C循环一样快,所以这实际上是一个巨大的区别

比较时间(使用与用户答案中相同的测试:3

其他主要解释器没有3.x版本,我也没有IPython,但使用Jython进行的快速测试显示了类似的效果

因此,最快的可移植实现类似于:

my_set = set()
my_map = map(lambda x, y: my_set.add((x, y)), my_x, my_y)
for _ in my_map:
    pass
if sys.implementation.name == 'cpython':
    import collections
    def consume(it):
        return collections.deque(it, maxlen=0)
else:
    def consume(it):
        for _ in it:
            pass
这当然给了我12.7us的CPython和1.41us的PyPy


1.当然,你可以编写一个自定义的C扩展,但是它只会更快一点,只需一个很小的常数项,你就可以在跳转到快速路径之前避免构造函数调用和测试,但是一旦你进入这个循环,你就必须完全按照它所做的去做

2.通过PyPy源代码进行跟踪总是很有趣的…但我认为它最终会出现在类中,这是内置的
\u collections
模块的一部分

<P>3。CPython 3.4.4;pypy5.5.1/3.5.3;两个标准的64位MACOS安装程序。

< P>提供了一个<代码>附件>(代码)>方法。但是在我的PC(Python 3.5)上,它与Deq解决方案是一致的。你可以检查它是否对你的特定解释器有优势。

>>>timeit.timeit(lambda: collections.deque(range(1,10000000),maxlen=0),number=10)
1.0916123000000084
>>>timeit.timeit(lambda: more_itertools.consume(range(1,10000000)),number=10)
1.092838400000005

太好了,谢谢!我应该加上一条警告,一般来说,这是个坏主意,但信不信由你,我找到了一个我需要做的案例!@zvone:它更快。增加了计时。@engineer这样的案例并不常见,但它们确实会出现,否则,
itertools
在文档中就不会有它的配方,而且
deque
在C源代码中不会对其进行优化。值得注意的是,在PyPy中,
deque
没有针对maxlen=0进行优化,因此它实际上比
for
循环慢一个数量级以上(在我的笔记本电脑上是23.3us,而不是1.17us)。你也可以只做
列表(我的地图)而不是
[/code>
并放弃结果我很确定这是一个重复的老问题,我也很确定我在自己的答案中提到了那个老问题……但我找不到它。(我可以找到一些答案,比如,但它们都只是简单地说,
deque
最快,没有链接…。你也可以使用
[0表示我的地图中的u如果为False]
而不是
[[uu表示我的地图中的u]
如果你想减少内存分配,它也应该更快。这太棒了,谢谢!我还没有真正考虑到不同解释器在实现上的差异。我现在被困在Jupyter笔记本中。我将不得不进一步研究这方面…@Engineero如果你还不知道的话:Jupyter笔记本可以运行PyPy内核。(上次我试过,在CPython笔记本上使用PyPy内核时出现了一些小问题,而另一种方法效果很好,但无法安装本地的Qt东西。但是如果不需要同时运行两个内核,就可以并行安装两个Jupyter。)