有效地重用 std::unordered_map
reusing std::unordered_map efficiently
我在我的程序中管理相对较小的瞬态字典。我的问题:重用它们(使用后使用 mymap.clear()
)而不是 delete
旧的并创建 new
的效率要高得多吗?
此外,这些词典目前实现为 std::unordered_map<std::string, int>
。这行得通,但如果(根据上述使用模式)另一个容器(stl 或不是)更可取,我会毫不犹豫地切换这个实现。
你写过吗?因为现在只是很多猜测。
考虑 new
和 std::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.
好的开始选择。如果你想尝试别的:
- 考虑prefix tree
- 考虑来自其他库的其他哈希映射,例如 abseil 库
使用真实数据衡量真实场景的性能,看看替代方案是否值得使用。
不幸的是,.clear()
没有任何性能优势,重用只是获得一个新的基于节点的容器,它的工作量几乎相同。
如果您知道字典的最大大小,并且它相当小,请考虑为节点使用自定义分配器。
这样,您可能会使事情变得更紧凑并节省分配开销。
除此之外,避免在标准库之外分配数千个单独节点的其他容器也是可能的。
至少对于 GCC,std::unordered_map<std::string, int>
在任何时间点都有如下动态分配:
- 1 个存储桶数组的分配,每个存储桶将迭代器(可能实现为指针)保存到单链节点列表(通常是峰值元素数的 1 倍到 2 倍之间),或者在没有元素时的哨兵迭代器状态散列到那个桶
- #elements 分配:有一个带有下一个指针的节点、一个散列值(是的,它保存了它!),以及
std::string
和 int
数据
- #keys 长于 15:任何
std::string
对于短字符串优化而言太长(文本内容直接存储在 std::string
对象中),将有一个指向动态分配文本的指针缓冲区
当您执行 .clear()
时,后两类分配将被释放。当容器本身被销毁时,只会进行一次额外的释放。
所以,我不希望保持 unordered_map
的性能有多大提升。
如果您关心性能,请更仔细地查看您的数据。字符串长度有上限吗?如果有并且它不大(例如 8 或 16 字节),您可以使用开放寻址 a.k.a 获取哈希 table。闭散列,其中键和值直接存储在桶中,因此只有一个动态分配正在进行。预计这会给你带来很大的性能提升(但总是衡量)。
我在我的程序中管理相对较小的瞬态字典。我的问题:重用它们(使用后使用 mymap.clear()
)而不是 delete
旧的并创建 new
的效率要高得多吗?
此外,这些词典目前实现为 std::unordered_map<std::string, int>
。这行得通,但如果(根据上述使用模式)另一个容器(stl 或不是)更可取,我会毫不犹豫地切换这个实现。
你写过吗?因为现在只是很多猜测。
考虑 new
和 std::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.
好的开始选择。如果你想尝试别的:
- 考虑prefix tree
- 考虑来自其他库的其他哈希映射,例如 abseil 库
使用真实数据衡量真实场景的性能,看看替代方案是否值得使用。
不幸的是,.clear()
没有任何性能优势,重用只是获得一个新的基于节点的容器,它的工作量几乎相同。
如果您知道字典的最大大小,并且它相当小,请考虑为节点使用自定义分配器。
这样,您可能会使事情变得更紧凑并节省分配开销。
除此之外,避免在标准库之外分配数千个单独节点的其他容器也是可能的。
至少对于 GCC,std::unordered_map<std::string, int>
在任何时间点都有如下动态分配:
- 1 个存储桶数组的分配,每个存储桶将迭代器(可能实现为指针)保存到单链节点列表(通常是峰值元素数的 1 倍到 2 倍之间),或者在没有元素时的哨兵迭代器状态散列到那个桶
- #elements 分配:有一个带有下一个指针的节点、一个散列值(是的,它保存了它!),以及
std::string
和int
数据 - #keys 长于 15:任何
std::string
对于短字符串优化而言太长(文本内容直接存储在std::string
对象中),将有一个指向动态分配文本的指针缓冲区
当您执行 .clear()
时,后两类分配将被释放。当容器本身被销毁时,只会进行一次额外的释放。
所以,我不希望保持 unordered_map
的性能有多大提升。
如果您关心性能,请更仔细地查看您的数据。字符串长度有上限吗?如果有并且它不大(例如 8 或 16 字节),您可以使用开放寻址 a.k.a 获取哈希 table。闭散列,其中键和值直接存储在桶中,因此只有一个动态分配正在进行。预计这会给你带来很大的性能提升(但总是衡量)。