与列表相比,生成器多次迭代的速度

2024-01-10

我预计在多个循环的情况下列表迭代将比使用生成器快得多,而我的代码表明这是错误的。

我的理解是(通过操作我指的是定义元素的任何表达式):

  • 一个清单需要n待初始化的操作
  • 但列表上的每个循环只是从内存中获取一个元素
  • thus, m循环列表只需要n运营
  • 生成器不需要任何初始化操作
  • 然而,循环生成器在飞行中运行操作
  • 因此,生成器上的一个循环需要n运营
  • but m生成器上的循环需要n x m运营

我使用以下代码检查了我的期望:

from timeit import timeit

def pow2_list(n):
    """Return a list with powers of 2"""

    results = []

    for i in range(n):
        results.append(2**i)

    return results

def pow2_gen(n):
    """Generator of powers of 2"""

    for i in range(n):
        yield 2**i

def loop(iterator, n=1000):
    """Loop n times over iterable object"""

    for _ in range(n):
        for _ in iterator:
            pass

l = pow2_list(1000) # point to a list
g = pow2_gen(1000)  # point to a generator


time_list = \
    timeit("loop(l)", setup="from __main__ import loop, l", number=10)

time_gen = \
    timeit("loop(g)", setup="from __main__ import loop, g", number=10)

print("Loops over list took: ", time_list)
print("Loops over generator took: ", time_gen)

结果令我惊讶......

Loops over list took:  0.20484769299946493
Loops over generator took:  0.0019217690005461918

不知怎的,即使循环超过 1000 次,使用生成器看起来也比列表快得多。在这种情况下,我们谈论的是两个数量级!为什么?

EDIT:

感谢您的回答。现在我看到了我的错误。我错误地认为生成器从新循环开始,例如范围:

>>> x = range(10)
>>> sum(x)
45
>>> sum(x)
45

但这是天真的(范围不是发电机......)。

关于可能的重复评论:我的问题涉及生成器上的多个循环,这在其他线程中没有解释。


你的生成器实际上只循环一次。一旦创建pow2_gen, g存放发电机;第一次通过loop,该发电机被消耗,并发出StopIteration。其他时间通过loop, next(g) (or g.next()在Python 2)只是继续抛出StopIteration,所以,实际上g代表一个空序列。

为了使比较更加公平,您需要在每次循环时重新创建生成器。

您处理此问题的方式的另一个困难是您正在调用append构建列表,这可能是构建列表最慢的方法。更常见的是,列表是使用列表推导式构建的。

下面的代码让我们可以更仔细地区分时间。create_list and create_gen使用列表理解和生成器表达式分别创建列表和生成器。time_loop就像你的loop方法,同时time_apply是一个版本loop每次通过循环都会重新创建可迭代对象。

def create_list(n=1000):
    return [2**i for i in range(n)]

def create_gen(n=1000):
    return (2**i for i in range(n))

def time_loop(iterator, n=1000):
    for t in range(n):
        for v in iterator:
            pass

def time_apply(create_fn, fn_arg, n=1000):
    for t in range(n):
        iterator = create_fn(fn_arg)
        time_loop(iterator, 1)

print('time_loop(create_list): %.3f' % timeit("time_loop(create_list(1000))",
                                              setup="from __main__ import *",
                                              number=10))

print('time_loop(create_gen): %.3f' % timeit("time_loop(create_gen(1000))",
                                             setup="from __main__ import *",
                                             number=10))

print('time_apply(create_list): %.3f' % timeit("time_apply(create_list, 1000)",
                                               setup="from __main__ import *",
                                               number=10))

print('time_apply(create_gen): %.3f' % timeit("time_apply(create_gen, 1000)",
                                              setup="from __main__ import *",
                                              number=10))

我的盒子上的结果表明建立一个列表(time_apply(create_list))在时间上类似于(甚至可能比)构建发电机(time_apply(create_gen)).

time_loop(create_list): 0.244
time_loop(create_gen): 0.028
time_apply(create_list): 21.190
time_apply(create_gen): 21.555

您可以看到您在问题中记录的相同效果,即time_loop(create_gen)比以下快一个数量级time_loop(create_list)。同样,这是因为创建的生成器仅被迭代一次,而不是对列表进行多次循环。

正如您假设的那样,构建一个列表一次并对其进行多次迭代(time_loop(create_list))比多次迭代生成器要快(time_apply(create_gen))在这个特定的场景中。

列表和生成器之间的权衡将很大程度上取决于您创建的迭代器有多大。对于 1000 个项目,我希望列表速度会相当快。对于 100,000 个项目,情况可能看起来有所不同。

print('create big list: %.3f' % timeit("l = create_list(100000)",
                                       setup="from __main__ import *",
                                       number=10))

print('create big gen: %.3f' % timeit("g = create_gen(100000)",
                                      setup="from __main__ import *",
                                      number=10))

我在这里得到:

create big list: 209.748
create big gen: 0.023

Python 构建大列表需要使用 700 到 800 MB 的内存;发电机几乎不使用任何东西。在 Python 中,内存分配和垃圾清理的计算成本很高,并且可以预见地会使您的代码变慢;生成器是避免占用机器 RAM 的一种非常简单的方法,并且可以对运行时间产生很大的影响。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

与列表相比,生成器多次迭代的速度 的相关文章

随机推荐

  • 从 PHP 查询时,视图内的 Postgresql regexp_matches 始终返回 null

    我有与此类似的观点 CREATE OR REPLACE VIEW regexp test AS SELECT regexp matches decode NTB4 base64 text d x 当我从 pgAdmin 查询视图时 按预期返
  • 代码中的注释有标准格式吗? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我想知道人们在代码中是否有标准的注释格式 不是方法或类的 xml 注释 而是注释within一个方法 也可以看看 是否有注释 C 代码的标准 如
  • 从代码隐藏访问 div 标签

    我正在使用 asp net 和 c 我有一个div在我的应用程序中标记class something 我需要访问这个某物代码隐藏中的类 我该怎么做 Code div class something somecode div Note 我想要
  • JavaScript:forEach 循环中的奇怪行为

    我的代码如下所示 someArray forEach x gt do something console log calling api for x callAnHttpApiAsync sleep 10 http api 调用是异步的 但
  • 无法在 Spring4D 中使用带有比较器的默认参数值

    我不确定这是否是一些通用问题 还是因为 Spring4D 实现 但我不能使用默认参数值来创建比较器 type TMyClass class class function MyComparer AParam Boolean False ICo
  • 在 Laravel Eloquent 模型中创建动态命名的变体

    我有一个日期字段列表 所有这些字段的变异器都有相同的逻辑 我想将此功能提取到一个特征中 以便将来我需要的只是在模型中创建一个日期字段数组并使用该特征 像这样的事情 foreach dates as date dateCamelCase th
  • 从 Maven 运行 Gradle

    我正在寻找一些 Maven 的 Gradle 执行器插件 类似于 Maven ant run 插件 谷歌没有提供帮助 难道这样的插件不存在吗 我应该尝试这个 https github com if6was9 gradle maven plu
  • 实现docker容器按需启动

    情况 大量重型 docker 容器会在一段时间内定期受到攻击 然后在较长时间内保持未使用状态 希望 按需启动容器 就像 systemd 通过套接字激活启动容器一样 并在空闲一段时间后停止它们 不visible最终用户的停机时间 Option
  • 忘记是行不通的

    如果我尝试从此集合中删除某个项目 examples Example where example data example gt get 通过做 examples gt forget 20 它不会从集合中删除该项目 我仍然取回原来存在的所有项
  • 如何将自定义 Java 类转换为 Spark 数据集

    我无法找到将测试对象列表转换为 Spark 中的数据集的方法 这是我的课 public class Test public String a public String b public Test String a String b thi
  • Brave/Chrome 浏览器中图像周围出现不需要的边框半径角

    我尝试在容器内显示一个简单的图像border radius 5px 但角落处似乎有一个细边框的轮廓 您需要仔细查看下图 如何避免这些角边框 cover margin 1em padding 1em image wrapper height
  • 如何从 Android 删除 Firestore 集合

    Issue 我正在寻找一个临时解决方案来从客户端删除集合以进行概念证明 我最终将按照建议将其重构到服务器上 我添加了删除所有特定 Firestore 用户帐户信息的功能 包括他们在应用程序中保存的内容集合 根据Firestore 文档 ht
  • 标识符 int 不是 struct SOCKET_LOG_DATA 的直接成员

    当我编译以下结构时 typedef PACKED struct PACKED SUFFIX SOCKET LOG DATA typedef PACKED union PACKED SUFFIX PACKED struct PACKED SU
  • Swift 中的条件删除集合的最后一个元素

    我正在尝试删除 从字符串数组的后面直到最后一项包含一些文本 但我的实现没有实现 到目前为止我的实现 var array A B C D while true if array last array last array removeLast
  • Django i18n 不起作用

    我正在尝试为我的项目激活不同的语言 现在有英语和西班牙语 我将描述我遵循的所有步骤 首先 我将自己置于要翻译的目录中 或者更好地说 所有 trans 标签都是 cd media templates landing mkdir locale
  • Javascript e.preventDefault();不适用于提交()

    我在使用 javascript 提交表单时遇到问题submit 现场录制 https jsfiddle net 98sm3f3t https jsfiddle net 98sm3f3t HTML
  • 从 JPanel 中完全删除 JLabel...而不是 setVisible(False)

    我有一个相当简单的问题 我在 JFrame 上有一个 JPanel 我在 JPanel 上有一个 JLabel 我想知道如何在运行时从 JPanel 中完全删除 JLabel ImageIcon image7 new ImageIcon a
  • C++,DLL的多个实例,单例

    我有一个 DLL 其中定义了单例 我有一个可以加载此 DLL 的多个实例的应用程序 DLL 需要每个 DLL 实例有一个单例实例 否则会崩溃 我发现多个 DLL 实例只有一个单例实例 为什么 我怎样才能解决它 如果可能的话 不将单例重构为其
  • Linq orderby 文化(丹麦语、æøå)

    我的 orderby linq 表达式有问题 它以错误的顺序生成输出 我来自丹麦 正在创建一个丹麦网站 因此订单必须准确无误 这是我的查询 var model from w in db News orderby w Title select
  • 与列表相比,生成器多次迭代的速度

    我预计在多个循环的情况下列表迭代将比使用生成器快得多 而我的代码表明这是错误的 我的理解是 通过操作我指的是定义元素的任何表达式 一个清单需要n待初始化的操作 但列表上的每个循环只是从内存中获取一个元素 thus m循环列表只需要n运营 生