【问题标题】:python function call with/without list comprehension [duplicate]带/不带列表理解的python函数调用[重复]
【发布时间】:2018-09-21 08:01:12
【问题描述】:

我有以下两个功能:

def foo(n=50000):
    return sum(i*i for i in range(n))  # just called sum() directly without 

def bar(n=50000):
    return sum([i*i for i in range(n)])  # passed constructed list to sum()

我希望foo 会比bar 运行得更快,但我已经用%%timeit 在ipython 中检查了foobar 花费的时间稍长

In [2]: %%timeit
   ...: foo(50000)
   ...: 
100 loops, best of 3: 4.22 ms per loop

In [3]: %%timeit
   ...: bar(50000)
   ...: 
100 loops, best of 3: 3.45 ms per loop
In [4]: %%timeit
   ...: foo(10000000)
   ...: 
1 loops, best of 3: 1.02 s per loop

In [5]: %%timeit
   ...: bar(10000000)
   ...: 
1 loops, best of 3: 869 ms per loop

当我增加 n 的值时差异会增加,因此我尝试使用 dis.dis(foo)dis.dis(bar) 检查功能,但它是相同的。

那么这两种方法之间出现这种时间差的原因是什么?

【问题讨论】:

  • 抛开问题,记住在这些情况下首选使用生成器表达式的主要原因是为了提高内存使用率而不是速度优化
  • 我可以发誓我们已经对这种现象有几个问题,但我找不到任何问题......
  • @IljaEverilä 不错的发现!

标签: python python-3.x


【解决方案1】:

关于生成器有很多很好的答案,所以我不会详细说明。

生成器保持状态。如果您执行非常快速 操作(例如使用sum,但如果您使用 I/O 命令不会有太大差异),它们会稍微慢一些。 生成器的好处是它们不会提前将所有项目加载到内存中,而列表会这样做。

这是迭代列表时发生的情况(非常高级):

  • 您将列表中的所有项目加载到内存中
  • 请求下一个元素只会为您提供指向该对象的指针

将其与生成器进行比较:

  • 您的内存中没有所有项目。一次只一个项。
  • 请求下一个元素会恢复生成器对象,运行代码直到到达 yield 语句。
  • 然后它会在内存中生成对象的地址,以便您可以访问它。

中间的这个额外步骤是测试中的差异。

因此,生成器通常用于处理大量需要加载到内存中的数据。 (当然,生成器还有更多用例,比如协程)

使用大文件和 for 循环打印行进行过期。在某些时候,您会在使用列表时出现内存不足异常。然后尝试使用生成器,它们不会耗尽内存..

【讨论】:

  • 这是矛盾的,因为列表理解:[...] 在文档中被声明为在生成器上调用的list() 的基本语法糖。因此,使用 list-comp 仍然必须遍历生成器以获取下一个元素,运行代码直到产生等。它只是在声明时执行此操作并将结果存储在内存中,而不是动态存储。所以我个人仍然很困惑,为什么一开始这样做比在sum() 通话期间更快,imo 你的回答并没有解决你专注于记忆的问题。
  • list() 首先构建数据结构,并将其加载到内存中。然后将列表数据结构转发到sum该列表已构建并可用。生成器一次只产生一个项目,检索下一个元素需要更长的时间,因为它执行更多的代码(直到下一个产生),而不是仅仅返回一个指针。
  • 是的,我同意它的工作原理,但不明白这意味着列表更快。创建列表和加载到内存所增加的时间必须抵消使用指针访问条目所节省的时间?
  • 不完全正确。创建超出范围的列表很快,您只需执行一次并将其加载到内存中。然后当iteration 开始时它访问list[index]。所有项目都在 O(1) 中访问,而使用生成器,访问项目是另一回事 - 您实际上执行代码,并产生 one 对象。然后在下一次迭代中再次出现......所以产生一个元素(在 OP 场景中)在时间方面比仅仅访问一个 ready 列表更昂贵。另外,我很确定在生成列表时会有一些优化。
  • @joe-iddon 你可能指的是this吗?请注意,它指出 semantics 已更改,因此它们更接近list(<gen expr>)(例如,它们不再泄漏循环控制变量的生成器表达式),但实现仍然不同:@987654322 @。简而言之,您关于 list-comp 仍然必须迭代生成器的说法是错误的。
猜你喜欢
  • 2015-03-31
  • 2020-05-29
  • 1970-01-01
  • 1970-01-01
  • 2018-04-12
  • 2013-05-28
  • 2021-04-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多