有效地重用 std::unordered_map

reusing std::unordered_map efficiently

我在我的程序中管理相对较小的瞬态字典。我的问题:重用它们(使用后使用 mymap.clear())而不是 delete 旧的并创建 new 的效率要高得多吗?

此外,这些词典目前实现为 std::unordered_map<std::string, int>。这行得通,但如果(根据上述使用模式)另一个容器(stl 或不是)更可取,我会毫不犹豫地切换这个实现。

你写过吗?因为现在只是很多猜测。

考虑 newstd::unordered_map 上的 delete 只是增加了实例化/拆除容器本身的开销。 std::unordered_map::clear 内部仍然会在它持有的每个对象上调用 delete,以便调用它的析构函数。可能涉及一个奇特的分配器,它为容器元素实现了一个大小相同的插槽池,以节省内存管理开销。

根据所包含对象的复杂性,使用普通 std::vector

可能更明智,也可能不更明智

您必须分析您的开销在哪里。 但更重要的是,如果这是您程序中导致统计显着减速的部分,则只完成工作。您应该选择微优化之上的易读性和实现清晰度。

This works, but if (in light of the above usage pattern) another container (stl or not) is preferrable, I won't hesitate to switch this implementation.

好的开始选择。如果你想尝试别的:

使用真实数据衡量真实场景的性能,看看替代方案是否值得使用。

不幸的是,.clear() 没有任何性能优势,重用只是获得一个新的基于节点的容器,它的工作量几乎相同。

如果您知道字典的最大大小,并且它相当小,请考虑为节点使用自定义分配器。

这样,您可能会使事情变得更紧凑并节省分配开销。

除此之外,避免在标准库之外分配数千个单独节点的其他容器也是可能的。

至少对于 GCC,std::unordered_map<std::string, int> 在任何时间点都有如下动态分配:

  • 1 个存储桶数组的分配,每个存储桶将迭代器(可能实现为指针)保存到单链节点列表(通常是峰值元素数的 1 倍到 2 倍之间),或者在没有元素时的哨兵迭代器状态散列到那个桶
  • #elements 分配:有一个带有下一个指针的节点、一个散列值(是的,它保存了它!),以及 std::stringint 数据
  • #keys 长于 15:任何 std::string 对于短字符串优化而言太长(文本内容直接存储在 std::string 对象中),将有一个指向动态分配文本的指针缓冲区

当您执行 .clear() 时,后两类分配将被释放。当容器本身被销毁时,只会进行一次额外的释放。

所以,我不希望保持 unordered_map 的性能有多大提升。

如果您关心性能,请更仔细地查看您的数据。字符串长度有上限吗?如果有并且它不大(例如 8 或 16 字节),您可以使用开放寻址 a.k.a 获取哈希 table。闭散列,其中键和值直接存储在桶中,因此只有一个动态分配正在进行。预计这会给你带来很大的性能提升(但总是衡量)。