在以下简单的示例中,有两个函数可以对随机数列表进行排序。第一种方法传递sorted
生成器表达式,第二种方法首先创建一个列表:
import random
l = [int(1000*random.random()) for i in xrange(10*6)]
def sort_with_generator():
return sorted(a for a in l)
def sort_with_list():
return sorted([a for a in l])
使用line profiler进行基准测试表明第二个选项(sort_with_list
)大约是生成器表达式的两倍。
任何人都可以解释发生了什么,为什么第一种方法比第二种方法慢得多?
答案 0 :(得分:6)
您的第一个示例是迭代列表的生成器表达式。第二个示例是一个迭代列表的列表表达式。实际上,第二个例子稍快一些。
>>> import timeit
>>> timeit("sorted(a for a in l)", setup="import random;l = [int(1000*random.random()) for i in xrange(10*6)]")
5.963912010192871
>>> timeit("sorted([a for a in l])", setup="import random;l = [int(1000*random.random()) for i in xrange(10*6)]")
5.021576881408691
这样做的原因无疑是制作列表一次完成,而迭代生成器需要函数调用。
生成器不会加速这样的小列表(列表中有60个元素,非常小)。主要是在创建长列表时节省内存。
答案 1 :(得分:2)
如果您查看sorted
newlist = PySequence_List(seq);
,则您传入的任何序列都会先被复制到新列表中。
generator
list
- > list
似乎慢于list
- > >>> timeit.timeit('x = list(l)', setup = 'l = xrange(1000)')
16.656711101531982
>>> timeit.timeit('x = list(l)', setup = 'l = range(1000)')
4.525658845901489
。
{{1}}
至于为什么必须制作副本,请考虑排序的工作原理。排序不是线性算法。我们多次遍历数据,有时会在两个方向上遍历数据。生成器用于生成一个序列,通过该序列,我们迭代一次且仅一次,从开始到它之后的某个地方。列表允许随机访问。
另一方面,从生成器创建列表将仅意味着内存中的一个列表,而制作列表的副本将意味着内存中的两个列表。良好的时空权衡。
Python使用the source,它是合并排序和插入排序的混合体。
答案 2 :(得分:0)
列表表达式首先将数据加载到内存中。然后用结果列表进行任何操作。让分配时间为T2
(第二种情况)。
生成器表达式不会立即分配时间,但会更改时间t1[i]
的迭代器值。所有t1[i]
的总和将为T1
。 T1
≈T2
。
但是当你打电话给sorted()
时,在第一种情况下,时间T1
增加了每对的分配记忆时间与排序(tx1[i]
)的比较。结果,T1
添加了所有tx1[i]
的总和。
因此,T2
< T1 + sum(tx1[i])