为什么 set() 构造函数比 list() 慢

Ch3*_*teR 1 python performance list set

我计时set()list()构造函数。set()明显慢于list(). 我使用不存在重复项的值对它们进行了基准测试。我知道 set use hashtables 是它变慢的原因吗?

在撰写本文时(3 月8),我正在使用 Python 3.7.5 [MSC v.1916 64 位 (AMD64)]、Windows 10

#No significant changed observed.
timeit set(range(10))
517 ns ± 4.91 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
timeit list(range(10))
404 ns ± 4.71 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Run Code Online (Sandbox Code Playgroud)

当大小增加set()变得非常慢时list()

# When size is 100
timeit set(range(100))
2.13 µs ± 12.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
timeit list(range(100))
934 ns ± 10.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

# when size is ten thousand.
timeit set(range(10000))
325 µs ± 2.37 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
timeit list(range(10000))
240 µs ± 2.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

# When size is one million.
timeit set(range(1000000))
86.9 ms ± 1.78 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
timeit list(range(1000000))
37.7 ms ± 396 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Run Code Online (Sandbox Code Playgroud)

他们都采取O(n)渐近。当没有重复项时,不应set(...)近似等于list(...)

令我惊讶的是,集合理解列表理解并没有像set()list()显示的那样显示出那些巨大的偏差。

# When size is 100. 
timeit {i for i in range(100)}
3.96 µs ± 858 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
timeit [i for i in range(100)]
3.01 µs ± 265 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

# When size is ten thousand.
timeit {i for i in range(10000)}
434 µs ± 5.11 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
timeit [i for i in range(10000)]
395 µs ± 13.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

# When size is one million.
timeit {i for i in range(1000000)}
95.1 ms ± 2.03 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
timeit [i for i in range(1000000)]
87.3 ms ± 760 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 8

为什么它们应该相同?是的,它们都是 O(n),但set()需要对每个元素进行散列,并且需要考虑元素不是唯一的。这意味着每个元素的固定成本更高。

Big O 没有说绝对时间,只是随着输入大小的增加花费的时间会如何增长。两种 O(n) 算法,在给定相同输入的情况下,完成所花费的时间可能大不相同。您只能说,当输入的大小加倍时,这两个函数所花费的时间(大致)会加倍。

如果您想更好地了解 Big O,我强烈推荐Ned Batchelder 对该主题的介绍

当没有重复时不应该 set(...) 大约等于 list(...)。

不,它们不相等,因为list()不散列。没有重复是没有意义的。

令我惊讶的是,集合理解和列表理解并没有像set()list()显示的那样显示出那些巨大的偏差。

由 Python 解释器循环执行的附加循环增加了支配所用时间的开销。较高的固定成本set()则不那么突出。

还有其他差异可能会产生影响:

  • 给定一个已知长度的序列list()可以预先分配足够的内存来容纳这些元素。集合不能预先分配,因为它们不知道会有多少重复。预分配避免了必须动态增长列表的(摊销)成本。
  • 列表和集合推导式一次添加一个元素,因此列表对象无法预分配,稍微增加了固定的每项成本。

  • @Ch3steR Luciano Ramalho 的 *Fluent Python* 不会出错,由 O'Reilly 出版。(免责声明:我与出版商没有任何联系,但很了解卢西亚诺,他在致谢中感谢我)。 (2认同)
  • @Ch3steR 我的前同事兼朋友 Matt Wilkes 有一本全新的书 *Advanced Python Development* (由 Apress 出版),可能也值得您花时间,但我还没有读过。预计今年 9 月发布。 (2认同)
  • @Ch3steR:https://www.apress.com/gp/book/9781484257920。也在亚马逊上列出供预订。 (2认同)