【问题标题】:Why is max slower than sort?为什么 max 比 sort 慢?
【发布时间】:2023-03-31 16:30:01
【问题描述】:

我发现max 比 Python 2 和 3 中的 sort 函数慢。

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

为什么 max (O(n)) 比 sort 函数 (O(nlogn)) 慢?

【问题讨论】:

  • a.sort() 就地工作。试试sorted(a)
  • @AndreaCorbellini 但 sorted(a) 需要 O(n) 内存,而 max(a) 只需要一个
  • @WeizhongTu 但sort 排序,然后a 永远排序
  • 另外值得注意的是:python 使用 Timsort。该算法在已经排序的列表上进行n-1 比较,这与max 必须做的数字相同。事实上,即使输入是“部分排序的”,Timsort 也会进行 O(n) 比较。即使在排序的情况下,其他算法也可能需要 O(nlogn) 时间。

标签: python sorting max python-internals


【解决方案1】:

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

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 的 answer 解释了我未解释的部分:a.sort() 知道它正在处理列表,因此可以直接访问元素。 max(a) 适用于任意迭代,因此必须使用泛型迭代。

【讨论】:

  • 好收获。我从来没有意识到解释器状态会在代码运行中保留。现在我想知道我过去制作了多少错误的基准。 :-}
  • 这对我来说是显而易见的。但请注意,即使您对已排序的数组进行排序,您也必须检查所有元素。这与获得最大值一样多。对我来说,这看起来像是一个半答案。
  • @KarolyHorvath,你是对的。我认为@skyking 得到了答案的另一半:a.sort() 知道它正在处理一个列表,因此可以直接访问元素。 max(a) 在任意序列上工作,不能使用泛型迭代。
  • @KarolyHorvath 也许分支预测可以解释为什么重复排序排序数组更快:stackoverflow.com/a/11227902/4600
  • @JuniorCompressor listsort.txt 解释说“它在多种偏序数组上具有超自然的性能(少于所需的 lg(N!) 比较,并且少至 N-1)”然后继续解释各种血腥的优化。我想它可以做出很多max 做不到的假设,即排序不是渐近更快的。
【解决方案2】:

首先,请注意max() uses the iterator protocol,而list.sort() uses ad-hoc code。显然,使用迭代器是一项重要的开销,这就是为什么您会观察到时间上的差异。

但是,除此之外,您的测试并不公平。您不止一次在同一个列表上运行a.sort()algorithm used by 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) 时间或更短的时间内运行.

【讨论】:

    【解决方案3】:

    这可能是因为l.sortlist 的成员,而max 是一个通用函数。这意味着l.sort 可以依赖list 的内部表示,而max 必须通过通用迭代器协议。

    这使得l.sort 的每个元素提取速度都比max 的每个元素提取速度快。

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

    【讨论】:

    • 这个假设只是一个更具体的时间安排。不质疑你的知识,只是这样的添加对于那些不知道它的人来说是微不足道的。
    • 你说得对,sorted(a)max(a) 慢。毫不奇怪,它的速度与a.sort() 大致相同,但您对原因的猜想不是——这是因为 OP 在他们的测试中犯了错误,正如接受的答案中所指出的那样。
    • 重点是通用迭代器协议有可能有足够的开销来抵消复杂性中的log(n) 因素。也就是说,O(n) 算法仅保证在足够大的n 的情况下比O(nlogn) 算法快(例如,因为算法之间每个操作的时间可能不同 - nlogn 快速步骤可能比 @ 快987654339@ 慢步)。在这种情况下,不考虑收支平衡的确切位置(但应该注意,log n 因素对于较小的n 来说并不是一个很大的因素)。
    猜你喜欢
    • 2014-07-29
    • 2012-10-07
    • 2019-08-25
    • 1970-01-01
    • 1970-01-01
    • 2011-03-23
    • 2014-04-18
    • 2019-10-23
    • 2019-12-18
    相关资源
    最近更新 更多