微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

itertools 与用于展平和重复的生成器表达式 - 我们如何解释这些计时结果?

如何解决itertools 与用于展平和重复的生成器表达式 - 我们如何解释这些计时结果?

受到关于 this question 的讨论的启发,我决定尝试一些性能测试。我设置的任务稍微简单一些 - 给定一个源列表 A,我们希望创建一个重复 A 的每个元素 N 次的惰性迭代:

def test(implementation):
    A,N = list('abc'),3
    assert list(implementation(A,N)) == list('aaabbbccc')

我想出了几个实现,并这样测试它们:

from itertools import chain,repeat,starmap
from timeit import timeit

flatten = chain.from_iterable

def consume(iterable):
    for _ in iterable:
        pass

# FAST approaches
def tools(original,count):
    return flatten(map(repeat,original,repeat(count)))

def tools_star(original,count):
    return flatten(starmap(repeat,zip(original,repeat(count))))

def mixed(original,count):
    return flatten(repeat(a,count) for a in original)

# SLOW approaches
def mixed2(original,count):
    return (x for a in original for x in repeat(a,count))

def explicit(original,count):
    for a in original:
        for _ in range(count):
            yield a

def generator(original,count):
    return (a for a in original for _ in range(count))

def mixed3(original,count):
    return flatten((a for _ in range(count)) for a in original)

if __name__ == '__main__':
    for impl in (tools,tools_star,mixed,mixed2,explicit,generator,mixed3):
        for consumption in (consume,list):
            to_time = lambda: consumption(impl(list(range(1000)),1000))
            elapsed = timeit(to_time,number=100)
            print(f'{consumption.__name__}({impl.__name__}): {elapsed:.2f}')

以下是我机器上计时结果的三个示例:

consume(tools): 1.10
list(tools): 2.96
consume(tools_star): 1.10
list(tools_star): 2.97
consume(mixed): 1.11
list(mixed): 2.91
consume(mixed2): 4.60
list(mixed2): 6.53
consume(explicit): 5.45
list(explicit): 8.09
consume(generator): 5.98
list(generator): 7.62
consume(mixed3): 5.75
list(mixed3): 7.67

consume(tools): 1.10
list(tools): 2.88
consume(tools_star): 1.10
list(tools_star): 2.89
consume(mixed): 1.11
list(mixed): 2.87
consume(mixed2): 4.56
list(mixed2): 6.39
consume(explicit): 5.42
list(explicit): 7.24
consume(generator): 5.91
list(generator): 7.48
consume(mixed3): 5.80
list(mixed3): 7.61

consume(tools): 1.14
list(tools): 2.98
consume(tools_star): 1.10
list(tools_star): 2.90
consume(mixed): 1.11
list(mixed): 2.92
consume(mixed2): 4.76
list(mixed2): 6.49
consume(explicit): 5.69
list(explicit): 7.38
consume(generator): 5.68
list(generator): 7.52
consume(mixed3): 5.75
list(mixed3): 7.86

由此我得出以下结论:

  • itertools 工具提供了巨大的性能提升,但前提是我们两者都使用它们来“展平”迭代器(itertools.chain.from_iterable 而不是通过嵌套 for 表达式)and生成子序列(itertools.repeat 而不是 range)。仅使用 repeat 只能提供很小的改进,而仅使用 chain.from_iterable 实际上似乎会使事情变得更糟。

  • 对于完整的 itertools 实现,我们如何迭代输入序列似乎并不重要 - 无论是使用生成器表达式、使用 map 还是使用 {{1} }. (这并不奇怪,因为这里只发生 O(len(A)) 操作而不是 O(len(A) * N)。itertools.starmap 方法很笨拙,绝对不是我推荐的,但我包括因为原始激励讨论中的代码使用了它。)

  • 从可迭代对象创建列表所增加的开销似乎在方法和计时运行之间变化很大(请注意两次运行中 starmap 结果的差异) - 尽管它们看起来使快速方法更加一致。这尤其奇怪,因为我正在总结每个测试中多个列表创建的结果。

list(explicit) 的幕后到底发生了什么?我们如何解释这些时序结果?尤其奇怪的是 itertoolschain.from_iterable 在这里没有提供增量性能优势,而是完全相互依赖。列表构建是怎么回事?每种情况下增加的开销是否相同(重复将相同的元素序列附加到空列表)?

解决方法

主要归结为花费在解释器上的时间量的大O。

  • 解释器中没有循环允许 C 函数直接通信。
    • 但是嵌套如此多的 itertools 会增加少量但可衡量的开销。
      • I 在下表中。
  • 一个循环只是几个操作码的 ×1000。
  • 嵌套循环高达 1000000 倍。
  • 直接从 repeat 产生的操作码比在产生前从 range 存储短一些操作码。
    • Y 在下表中。
  • explicitgenerator 实际上是等价的。
  • 嵌套生成器是一个嵌套函数调用——代价高昂。
    • G 在下表中。

这是我的结果:

方法 复杂性(仅限口译员) 列表 消费
工具 O(0) + I 0.21 0.09
tools_star O(0) + I 0.21 0.09
混合 O(N) 0.20 0.09
mixed2 O(N²) 0.54 0.47
显式 O(N²) + Y 0.64 0.60
发电机 O(N²) + Y 0.64 0.60
工具 O(N²) + G 0.71 0.65

看起来很有说服力。

我还对代码进行了一些修改,以提高计时方法和可读性。

PRODUCERS = (tools,tools_star,mixed,mixed2,explicit,generator,mixed3)
CONSUMERS = (list,consume)
N = 1000
SAMPLES = 50
BATCH = 10

if __name__ == '__main__':
    for consumer in CONSUMERS:
        print(f"{consumer.__name__}:")
        for producer in PRODUCERS:
            to_time = lambda: consumer(producer(list(range(N)),N))
            elapsed = timeit.repeat(to_time,repeat=SAMPLES,number=BATCH)
            emin = min(elapsed) # get rid of the random fluctuations for the best theoretical time
            print(f'{emin:02.2f} | {producer.__name__}')
        print()

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?