为什么max比排序慢?


92

我发现它maxsortPython 2和3中的函数慢。

Python 2

$ python -m timeit -s 'import random;a=range(10000);random.shuffle(a)' 'a.sort();a[-1]'
1000 loops, best of 3: 239 usec per loop
$ python -m timeit -s 'import random;a=range(10000);random.shuffle(a)' 'max(a)'        
1000 loops, best of 3: 342 usec per loop

Python 3

$ python3 -m timeit -s 'import random;a=list(range(10000));random.shuffle(a)' 'a.sort();a[-1]'
1000 loops, best of 3: 252 usec per loop
$ python3 -m timeit -s 'import random;a=list(range(10000));random.shuffle(a)' 'max(a)'
1000 loops, best of 3: 371 usec per loop

为什么 maxO(n)比慢)sort功能(O(nlogn))?


3
您只需运行一次Python 2分析,并且Python 3代码完全相同。
erip

9
a.sort()在原地工作。试试sorted(a)
Andrea Corbellini,2016年

如果您修复了该问题,请回发您对其进行修复的操作。
椒盐脆饼

4
@Pretzel OP表示帖子已被编辑,而不是问题已得到解决。
erip '16

2
@WeizhongTu但是sort排序,然后a永远排序
njzk2

Answers:


125

使用时,您必须非常小心 timeit在Python中模块。

python -m timeit -s 'import random;a=range(10000);random.shuffle(a)' 'a.sort();a[-1]'

初始化代码在这里运行一次,以产生一个随机数组a。然后,其余代码将运行几次。第一次对数组进行排序,但是每隔一次您对已排序的数组调用sort方法。仅返回最快的时间,因此您实际上是在计时Python对已经排序的数组进行排序所花费的时间。

Python排序算法的一部分是检测何时已对数组进行部分或完全排序。完全排序后,只需在阵列中扫描一次即可检测到此情况,然后停止。

相反,如果您尝试:

python -m timeit -s 'import random;a=range(100000);random.shuffle(a)' 'sorted(a)[-1]'

那么排序就会在每个定时循环上发生,您可以看到,对数组进行排序的时间确实比仅仅查找最大值要长得多。

编辑: @skyking的答案解释了我无法解释的部分:a.sort()知道它正在处理列表,因此可以直接访问元素。max(a)可以处理任意可迭代项,因此必须使用通用迭代。


10
接得好。我从来没有意识到解释器状态在整个代码运行中都会保留。现在,我想知道过去产生了多少错误的基准测试。:-}
Frerich Raabe

1
这对我来说很明显。但是请注意,即使您对已经排序的数组进行排序,也必须检查所有元素。这与获得最大成就一样多。...对我来说,这似乎是一个半答案。
Karoly Horvath

2
@KarolyHorvath,你是对的。我认为@skyking可以得到答案的另一半:a.sort()知道它正在处理列表,因此可以直接访问元素。max(a)在任意序列上工作,以使其可以使用泛型迭代。
邓肯

1
@KarolyHorvath也许分支预测可以解释为什么重复排序排序的数组更快的原因:stackoverflow.com/a/11227902/4600
marcospereira

1
@JuniorCompressor listsort.txt解释说:“它在许多部分有序的数组上具有超自然的性能(少于所需的lg(N!)比较,少至N-1)”,然后继续解释所有种类的gory优化。我想它可以做出很多无法做到的假设max,即排序不是渐近地进行。
Frerich Raabe

87

首先,请注意,它max()使用迭代器协议,而list.sort()使用临时代码。显然,使用迭代器是一项重要的开销,这就是为什么您观察到时序差异的原因。

但是,除此之外,您的测试是不公平的。您a.sort()在同一列表上运行了不止一次。Python使用算法经过专门设计,可快速处理已(部分)排序的数据。您的测试表明该算法运行良好。

这些是公平的测试:

$ python3 -m timeit -s 'import random;a=list(range(10000));random.shuffle(a)' 'max(a[:])'
1000 loops, best of 3: 227 usec per loop
$ python3 -m timeit -s 'import random;a=list(range(10000));random.shuffle(a)' 'a[:].sort()'
100 loops, best of 3: 2.28 msec per loop

在这里,我每次都创建列表的副本。如您所见,结果的数量级是不同的:正如我们所期望的,微秒与毫秒。

请记住:big-Oh指定一个上限!Python的排序算法的下限是Ω(n)。设为O(n log n)并不自动意味着每次运行都花费与n log n成比例的时间。它甚至不暗示它需要比O(n)算法慢,但这是另一回事了。重要的是要理解,在某些有利的情况下,O(n log n)算法可以在O(n)时间或更短的时间内运行。


31

这可能是因为while l.sort的成员是泛型函数。这意味着可以依靠while 的内部表示listmaxl.sortlistmax将不得不通过通用的迭代协议。

这使得获取每个元素的l.sort速度快于获取每个元素的速度max

我假设如果您改用,sorted(a)您得到的结果将比慢max(a)


5
这种假设只是一线希望,即将变得更加具体。不用怀疑您的知识,只是这样的添加对于那些不了解它的人来说是微不足道的。
Reti43 '16

您是正确的,sorted(a)它比慢max(a)。毫不奇怪,它的速度与大约相同a.sort(),但是您对不这样的原因有所猜想-这是因为OP在接受的答案中指出了测试错误。
martineau '16

关键是通用迭代器协议有可能有足够的开销来抵消log(n)复杂性的因素。也就是说,O(n)仅保证O(nlogn)算法比足够大的算法快n(例如,由于每个操作的时间在算法之间可能不同- nlogn快步可能比n慢步快)。在这种情况下,没有考虑到收支平衡的确切位置(但请注意,该log n因素对于小而言并不是很大的因素n)。
凌晨
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.