我想初始化一个std::map
. 现在我正在使用::insert
,但我觉得我在浪费一些计算时间,因为我已经知道我想要分配的大小。有没有办法分配一个固定大小的地图然后填充地图?
5 回答
不,地图的成员在内部存储在树结构中。在您知道要存储的键和值之前,无法构建树。
简短的回答是:是的,这是可能的,但这不是微不足道的。您需要为您的地图定义一个自定义分配器。基本思想是您的自定义分配器将为地图留出一块内存。由于映射需要新节点,分配器将简单地为它们分配预分配块内的地址。像这样的东西:
std::map<KeyType, ValueType, std::less<KeyType>, MyAllocator> myMap;
myMap.get_allocator().reserve( nodeSize * numberOfNodes );
但是,您必须处理许多问题。
首先,您并不真正知道每个映射节点的大小或映射将执行多少次分配。这些是内部实现细节。您可以尝试找出答案,但不能假设结果将适用于不同的编译器(甚至是同一编译器的未来版本)。因此,您不必担心分配“固定”大小的地图。相反,您的目标应该是将所需的分配数量减少到少数。
其次,如果你想支持删除,这个策略会变得相当复杂。
第三,不要忘记内存对齐问题。分配器返回的指针必须针对内存将存储的各种类型的对象正确对齐。
话虽如此,在你尝试这个之前,确保它是必要的。内存分配可能非常昂贵,但您仍然不应该认为这对您的程序来说是个问题。测量以找出答案。您还应该考虑更自然地允许预先分配的替代策略。例如,排序列表或 std::unordered_map。
不确定这是否回答了您的问题,但Boost.Container有一个flat_map
您可以在其中保留空间的地方。基本上,您可以将其视为(键,值)对的排序向量。提示:如果您还知道您的输入已排序,则可以使用带有提示的插入来获得最大性能。
这个问题已经有几个很好的答案,但他们错过了一些要点。
直接初始化地图
如果直接使用迭代器初始化,则映射预先知道大小:
auto mymap = std::map(it_begin, it_end);
这是回避问题的最佳方法。如果您对实现不了解,则映射可以从迭代器预先知道大小,然后您将问题转移到std::
实现来担心。
或者与迭代器一起使用insert
,即:
mymap.insert(it_begin, it_end);
请参阅:https ://en.cppreference.com/w/cpp/container/map/insert
提防过早的优化
但我觉得我在浪费一些计算时间。
这听起来很像您过早地进行优化(这意味着您不知道瓶颈在哪里 - 您正在猜测或看到一个不是真正的问题)。相反,首先测量然后进行优化 - 如有必要,重复。
内存分配已经在很大程度上得到优化
为地图滚动你自己的块分配器可能几乎没有结果。在现代系统上(这里我包括操作系统/硬件和C++ 语言级别)内存分配已经针对一般情况进行了很好的优化,如果滚动您自己的块分配器,您可能会看到很少或没有改进。即使您非常小心并将地图放入一个连续的数组中 - 虽然本身是一种改进 - 您仍然可能面临这样的问题,即最终元素可能会随机放置在数组中(例如插入顺序)并且无论如何都要对缓存不那么友好(这在很大程度上取决于您的实际用例 - 我假设一个超大数据集)。
使用其他容器或第三方地图
如果您仍然面临这个问题 - 最好的方法可能是使用另一个容器(例如,排序的std::vector
-std::lower_bound
用于查找)或使用针对您使用地图的方式优化的第三方地图。一个很好的例子flat_map
来自boost - 请参阅这个答案。
结论
- 让 std::map 担心这个问题。
- 当性能是主要问题时:使用最适合您的数据使用方式的数据结构(可能是第 3 方)(随机插入或批量插入/主要是迭代或主要是查找等)。然后,您需要分析和收集性能指标以进行比较。