星图比嵌套列表理解更快吗?
Posted
技术标签:
【中文标题】星图比嵌套列表理解更快吗?【英文标题】:Is starmap faster than a nested list comprehension? 【发布时间】:2018-11-19 17:51:16 【问题描述】:这是解决here任务的代码:
def maximizingXor(l, r):
return max([i^j for i in range(l, r+1) for j in range(i, r+1)])
这是我丑陋的解决方案:
from itertools import combinations, starmap
from operator import xor
# Complete the maximizingXor function below.
def maximizingXor(l, r):
return max(starmap(xor, combinations(range(l,r+1),2)))
它不像那个那么漂亮,但在 l=10, r=15 上确实更快: 对于我的解决方案,%timeit 显示 3.81 µs ± 156 ns,对于没有调用函数的解决方案,每个循环显示 8.67 µs ± 1.1 µs。 那么问题来了——为什么更快? 更一般地说: 在什么情况下,像 itertools 这样的函数调用比直接循环更快? 谢谢。
【问题讨论】:
列表推导必须为 O((r-l)**2)) 值分配内存,然后它可以遍历它们并选择最大的。你只需要恒定的内存,在生成时保留或丢弃每个值。 初始代码中不需要使用列表推导。生成器表达式更快吗?请注意,对于某些表达式,map
等辅助函数确实比推导式更快。
除了创建列表之外,itertools
构造与手写 python 版本相比可以相当快。它们在 C 中实现。
@VasylKolomiets 不会改变。如果你编写一个列表理解,Python 应该创建一个列表。原则上你不能优化它,因为没有什么能阻止你做max = some_other_function
你可以使用生成器表达式,但除非列表变得很大,否则可能不会更快,因为生成器很慢,而 Python 非常擅长创建事物列表。
理解变体创建、使用和销毁大约r-l
额外的range
对象及其迭代器。 itertools 变体可以直接从一个range
对象计算所有对。只需创建一个包含 5 个range
对象的列表,就需要在我的机器上实现大约 90% 的初始时间差异。
【参考方案1】:
第一个注释max
适用于任何可迭代对象。这可以是一个列表或一个生成器。哪个更有效取决于输入的大小和硬件限制。列表需要大量内存,但生成器表达式会因next
调用而产生更大的开销。
以下时序是针对同一逻辑的 4 种变体进行 2 次不同的运行。如您所见,对于非常大的l, r
,生成器表达式比列表理解更有效,反之亦然,对于较小的l, r
。
starmap
,同样懒惰但避免了生成器表达式,比两者都更有效。用外行的话来说,starmap
具有两全其美的优点,因为它是懒惰的和使用优化的 C 代码进行迭代。
# run 1 inputs
l, r = 10000, 15000
# run 2 inputs
l, r = 1000, 1500
%timeit maximizingXor_lc(l, r) # 2.83 s per loop, 18.2 ms per loop
%timeit maximizingXor_ge(l, r) # 2.48 s per loop, 21.5 ms per loop
%timeit maximizingXor(l, r) # 1.53 s per loop, 15.2 ms per loop
%timeit maximizingXor_zip(l, r) # 6.52 s per loop, 51.7 ms per loop
基准代码
from itertools import combinations, starmap
from operator import xor
def maximizingXor_lc(l, r):
return max([i^j for i in range(l, r+1) for j in range(i, r+1)])
def maximizingXor_ge(l, r):
return max(i^j for i in range(l, r+1) for j in range(i, r+1))
def maximizingXor(l, r):
return max(starmap(xor, combinations(range(l,r+1), 2)))
def maximizingXor_zip(l, r):
return max(map(xor, *zip(*combinations(range(l,r+1), 2))))
assert maximizingXor_lc(l, r) == maximizingXor(l, r)
assert maximizingXor_lc(l, r) == maximizingXor_ge(l, r)
assert maximizingXor_lc(l, r) == maximizingXor_zip(l, r)
【讨论】:
【参考方案2】:早些时候,我用不正确的代码发布了这个。查找列表的最大值比查找生成器的最大值要快。如果范围足够小以适合内存,则创建列表并找到最大值会更快。
def maximizingXor_lst(l, r):
return max(list(starmap(xor, combinations(range(l, r+1), 2))))
【讨论】:
以上是关于星图比嵌套列表理解更快吗?的主要内容,如果未能解决你的问题,请参考以下文章
比较列表理解和显式循环(3 个数组生成器比 1 个 for 循环更快)