【问题标题】:Why passing a list as a parameter performs better than passing a generator?为什么将列表作为参数传递比传递生成器性能更好?
【发布时间】:2020-05-16 14:41:51
【问题描述】:

我正在为this question 提供答案,当我测试我的解决方案的时间时,我发现了一个与我认为正确的矛盾。

提出问题的人想找到一种方法来了解另一个列表中包含多少不同的列表。 (想了解更多可以check the question

我的回答基本上是这个函数:

def how_many_different_lists(lists):
    s = set(str(list_) for list_ in lists)
    return len(s)

现在,当我测量运行所需的时间并将其与基本相同的函数进行比较时,情况出现了,但将列表而不是生成器作为参数传递给 set():

def the_other_function(lists):
    s = set([str(list_) for list_ in lists])
    return len(s)

这是我用来测试功能的装饰器:

import time

def timer(func):
    def func_decorated(*args):
        start_time = time.clock()
        result = func(*args)   
        print(time.clock() - start_time, "seconds")
        return result
    return func_decorated

这是给定输入的结果:

>>> list1 = [[1,2,3],[1,2,3],[1,2,2],[1,2,2]]
>>> how_many_different_lists(list1)
6.916326725558974e-05 seconds
2
>>> the_other_function(list1)
3.882067261429256e-05 seconds
2

即使是更大的列表:

# (52 elements)
>>> list2= [[1,2,3],[1,2,3],[1,2,2],[1,2,2],[1,2,3],[1,2,3],[1,2,2],[1,2,2],[1,2,3],[1,2,3],[1,2,2],[1,2,2],[1,2,3],[1,2,3],[1,2,2],[1,2,2],[1,2,3],[1,2,3],[1,2,2],[1,2,2],[1,2,3],[1,2,3],[1,2,2],[1,2,2],[1,2,3],[1,2,3],[1,2,2],[1,2,2],[1,2,3],[1,2,3],[1,2,2],[1,2,2],[1,2,3],[1,2,3],[1,2,2],[1,2,2],[1,2,3],[1,2,3],[1,2,2],[1,2,2],[1,2,3],[1,2,3],[1,2,2],[1,2,2],[1,2,3],[1,2,3],[1,2,2],[1,2,2],[1,2,3],[1,2,3],[1,2,2],[1,2,2]]
>>> how_many_different_lists(list2)
0.00023560132331112982 seconds
2
>>> the_other_function(list2)
0.00021329059177332965 seconds
2

现在,我的问题是:为什么第二个例子比第一个更快?由于“按需”生产元素,发电机不应该更快吗?我曾经认为制作一个列表并遍历它会比较慢。

PS:我已经测试了很多次,得到了基本相同的结果。

【问题讨论】:

  • 调用the_other_function 似乎使那个运行“更快”,所以我想还有其他python 优化在起作用-example,第二次调用它们,时差是可以忽略不计
  • 为什么要滚动自己的计时功能而不是使用timeit
  • 哦,是的,很抱歉。我一直在玩timeit,但我仍然不太熟悉它。你认为它可以在这里产生巨大的变化吗? (顺便说一句,这里是您的答案的忠实拥护者;-))
  • 附注一点,但是为什么要将任何列表/生成器理解传递给set(),当 set 有它自己的时候? {str(list_) for list_ in lists} ;)
  • 不能立即确定确切的机制,但是将其反汇编后,使用生成器表达式的字节码要长一条指令。

标签: python list time generator


【解决方案1】:

我一直在对您的功能进行基准测试:

from simple_benchmark import BenchmarkBuilder
from random import choice

b = BenchmarkBuilder()
from operator import setitem


@b.add_function()
def how_many_different_lists(lists):
    s = set(str(list_) for list_ in lists)
    return len(s)


@b.add_function()
def the_other_function(lists):
    s = set([str(list_) for list_ in lists])
    return len(s)


@b.add_arguments('Number of lists in the list')
def argument_provider():
    for exp in range(2, 18):
        size = 2**exp

        yield size,  [list(range(choice(range(100)))) for _ in range(size)]


r = b.run()
r.plot()

生成器是懒惰的,因为生成器表达式将动态创建项目,而列表推导将在内存中创建整个列表。你可以在这里阅读更多:Generator Expressions vs. List Comprehension

正如您从基准测试中看到的那样,它们之间并没有那么大的差异。

【讨论】:

  • 很棒的分析。所以基本上答案是在实践中没有一个比另一个更快的事情,对吧?它们的整体表现几乎相同?
猜你喜欢
  • 2013-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-28
  • 1970-01-01
  • 2023-03-23
  • 2011-01-20
  • 1970-01-01
相关资源
最近更新 更多