地图越长越慢吗
Does a map get slower the longer it is
地图越长越慢吗?我不是在谈论遍历它,而是像 .find()
.insert()
和 .at()
.
这样的操作
例如,如果我们有包含 100'000'000 个元素的 map<int, Object> mapA
和仅包含 100 个元素的 map<int, Object> mapB
。
执行 mapA.find(x)
和 mapB.find(x)
的性能会有什么不同吗?
std::map
上的查找和插入操作的复杂度是映射中元素数量的对数。因此,随着地图变大,它会变慢,但只会变慢 非常 慢(比元素编号中的任何多项式都慢)。要实现具有此类属性的容器,操作通常采用 二分搜索.
的形式
想象一下它有多慢,每次将元素数量加倍时,基本上都需要进行一次操作。因此,如果您需要对具有 4000 个元素的地图进行 k 操作,则需要对具有 8000 个元素的地图进行 k + 1 次操作,k + 2 表示 16000 个元素,依此类推。
相比之下,std::unordered_map
没有为您提供元素的排序,而在 return 中,它为您提供的复杂度平均恒定。这个容器通常被实现为一个散列table。 "On average" 意味着查找一个特定元素可能需要很长时间,但是查找许多随机选择的元素所花费的时间除以 looked-up 元素的数量,并不取决于容器大小。无序地图为您提供的功能更少,因此可能会为您提供更好的性能。
但是,在选择要使用的地图时要小心(前提是顺序无关紧要),因为渐近成本不会告诉您有关实际 wall-clock 成本的任何信息。无序映射操作中涉及的散列成本可能会贡献一个重要的常数因子,它只会使无序映射在大尺寸时比有序映射更快。此外,无序映射缺乏可预测性(以及使用选定键的潜在复杂性攻击)可能使有序映射在需要控制最坏情况而不是平均情况的情况下更可取。
C++标准只要求std::map
具有对数查找时间;并不是说它是任何特定底数或具有任何特定常数开销的对数。
因此,问"how many times slower would a 100 million map be than a 100 map"是荒谬的;很可能开销很容易支配两者,因此操作速度大致相同。对于小尺寸,时间增长甚至可能呈指数增长!按照设计,这些东西中的 none 完全可以从规范中推导出来。
此外,您询问的是时间,而不是操作。这在很大程度上取决于访问模式。 To use some diagrams from Paul Khong's (amazing) blog on Binary searches,重复搜索的运行时间(看stl
,绿松石线)几乎是完全对数的,
但是一旦您开始进行随机访问,由于在 1 级缓存之外进行内存访问,性能将变得非常明显 non-logarithmic:
请注意,goog
指的是 Google 的 dense_hash_map
,类似于 unordered_map
。在这种情况下,即使它也无法避免较大尺寸下的性能下降。
后一张图在大多数情况下可能更能说明问题,并且表明查找大小为 100 的随机索引的速度成本 map
比大小为 500 的速度成本低约 10 倍' 000 地图。 dense_hash_map
会降低 比 更糟,因为它将从 almost-free 变为 certainly-not-free,尽管总是保持 很多 比 STL 的 map
.
快
一般来说,问这些问题,理论上的方法只能给你很粗略的答案。快速查看实际基准和考虑常数因素可能会显着 fine-tune 这些粗略的答案。
现在,还要记住您说的是 map<int, Object>
,这与 set<uint32_t>
非常不同;如果 Object
很大,这将强调缓存未命中的成本和 de-emphasize 遍历的成本。
旁白。
关于散列映射的快速说明:它们的时间复杂度通常被描述为常数时间,但严格来说这并不正确。大多数哈希映射宁愿为您提供 关于查找的非常高可能性 的常数时间,以及关于插入的 非常高可能性 的摊销常数时间。
前者意味着对于大多数哈希 table 来说,有一个输入使它们的性能低于最佳值,而对于 user-input 这可能是危险的。出于这个原因,Rust 默认使用加密哈希,Java 的 HashMap
resolves collision with a binary search and CPython randomizes hashes。通常,如果您将哈希 table 暴露给不受信任的输入,则应确保您使用了此类缓解措施。
有些算法,如布谷鸟哈希,比概率算法做得更好(在受限数据类型上,给定 special kind of hash function) for the case where you're worried about attackers, and incremental resizing 消除了摊销时间成本(假设分配便宜),但两者都不常用,因为这些很少出现问题需要解决,解决方案不是免费的。
就是说,如果您正在努力思考为什么我们要经历使用无序地图的麻烦,请回顾一下图表。它们速度很快,您应该使用它们。
地图越长越慢吗?我不是在谈论遍历它,而是像 .find()
.insert()
和 .at()
.
例如,如果我们有包含 100'000'000 个元素的 map<int, Object> mapA
和仅包含 100 个元素的 map<int, Object> mapB
。
执行 mapA.find(x)
和 mapB.find(x)
的性能会有什么不同吗?
std::map
上的查找和插入操作的复杂度是映射中元素数量的对数。因此,随着地图变大,它会变慢,但只会变慢 非常 慢(比元素编号中的任何多项式都慢)。要实现具有此类属性的容器,操作通常采用 二分搜索.
想象一下它有多慢,每次将元素数量加倍时,基本上都需要进行一次操作。因此,如果您需要对具有 4000 个元素的地图进行 k 操作,则需要对具有 8000 个元素的地图进行 k + 1 次操作,k + 2 表示 16000 个元素,依此类推。
相比之下,std::unordered_map
没有为您提供元素的排序,而在 return 中,它为您提供的复杂度平均恒定。这个容器通常被实现为一个散列table。 "On average" 意味着查找一个特定元素可能需要很长时间,但是查找许多随机选择的元素所花费的时间除以 looked-up 元素的数量,并不取决于容器大小。无序地图为您提供的功能更少,因此可能会为您提供更好的性能。
但是,在选择要使用的地图时要小心(前提是顺序无关紧要),因为渐近成本不会告诉您有关实际 wall-clock 成本的任何信息。无序映射操作中涉及的散列成本可能会贡献一个重要的常数因子,它只会使无序映射在大尺寸时比有序映射更快。此外,无序映射缺乏可预测性(以及使用选定键的潜在复杂性攻击)可能使有序映射在需要控制最坏情况而不是平均情况的情况下更可取。
C++标准只要求std::map
具有对数查找时间;并不是说它是任何特定底数或具有任何特定常数开销的对数。
因此,问"how many times slower would a 100 million map be than a 100 map"是荒谬的;很可能开销很容易支配两者,因此操作速度大致相同。对于小尺寸,时间增长甚至可能呈指数增长!按照设计,这些东西中的 none 完全可以从规范中推导出来。
此外,您询问的是时间,而不是操作。这在很大程度上取决于访问模式。 To use some diagrams from Paul Khong's (amazing) blog on Binary searches,重复搜索的运行时间(看stl
,绿松石线)几乎是完全对数的,
但是一旦您开始进行随机访问,由于在 1 级缓存之外进行内存访问,性能将变得非常明显 non-logarithmic:
请注意,goog
指的是 Google 的 dense_hash_map
,类似于 unordered_map
。在这种情况下,即使它也无法避免较大尺寸下的性能下降。
后一张图在大多数情况下可能更能说明问题,并且表明查找大小为 100 的随机索引的速度成本 map
比大小为 500 的速度成本低约 10 倍' 000 地图。 dense_hash_map
会降低 比 更糟,因为它将从 almost-free 变为 certainly-not-free,尽管总是保持 很多 比 STL 的 map
.
一般来说,问这些问题,理论上的方法只能给你很粗略的答案。快速查看实际基准和考虑常数因素可能会显着 fine-tune 这些粗略的答案。
现在,还要记住您说的是 map<int, Object>
,这与 set<uint32_t>
非常不同;如果 Object
很大,这将强调缓存未命中的成本和 de-emphasize 遍历的成本。
旁白。
关于散列映射的快速说明:它们的时间复杂度通常被描述为常数时间,但严格来说这并不正确。大多数哈希映射宁愿为您提供 关于查找的非常高可能性 的常数时间,以及关于插入的 非常高可能性 的摊销常数时间。
前者意味着对于大多数哈希 table 来说,有一个输入使它们的性能低于最佳值,而对于 user-input 这可能是危险的。出于这个原因,Rust 默认使用加密哈希,Java 的 HashMap
resolves collision with a binary search and CPython randomizes hashes。通常,如果您将哈希 table 暴露给不受信任的输入,则应确保您使用了此类缓解措施。
有些算法,如布谷鸟哈希,比概率算法做得更好(在受限数据类型上,给定 special kind of hash function) for the case where you're worried about attackers, and incremental resizing 消除了摊销时间成本(假设分配便宜),但两者都不常用,因为这些很少出现问题需要解决,解决方案不是免费的。
就是说,如果您正在努力思考为什么我们要经历使用无序地图的麻烦,请回顾一下图表。它们速度很快,您应该使用它们。