星图比嵌套列表理解更快吗?

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 循环更快)

更快的理解js中循环嵌套

更快的列表理解

python:reduce 可以翻译成 map、lambda 和 filter 之类的列表理解吗?

列表理解中的双重迭代