"resurrect"(序列化/反序列化)std::map 的最快方法

Fastest way to "resurrect" (serialize/ and deserialize) an std::map

作为我的测试代码的一部分,我需要构建复杂的结构,其中使用了 2 std::map 个实例;它们都有大约 100 万个元素。在优化的构建中它没问题,但是,在调试未优化的构建中它需要将近一分钟。我使用相同的数据来构建该地图,基本上如果我可以保存大量 ram 并在 20 毫秒内恢复它,那么我就可以在我的应用程序中有效地获得相同的地图,而无需每次等待一分钟。我该怎么做才能加快速度?我可以尝试使用自定义分配器和 save/restore 它的分配存储,或者有没有一种方法可以从已经排序的数据中构造 std::map 以便它在时间上是线性的?

技术难点在于,对于 std::map 在调试模式下,Visual studio 编译器会插入正确性检查,并且在某些修订版中,已将元素插入到结构中以确保检查更容易。

有 2 种可能的解决方案:-

抽象

如果 std::map 提供的信息可以被接口 class 替换,那么 std::map 的内部可以被隐藏并移动到一个单独的编译单元中。这可以在调试环境之外编译并恢复性能。

替代数据结构

对于一条大致静态的信息(例如,您需要快速检索的一条静态数据,std::map 不是实现此目的的最快方法,而排序的 std::vector std::pair<key,value> 的操作性能会更好。

std::vector 的优点是它的布局有保证。如果数据是 Plain-old-data,那么它可以通过 std::vector::reserve 和 memcpy 加载。否则,填充 std::vector 中的元素仍然可以避免 Visual Studio 跟踪 std::map 的内存和结构问题所花费的大量时间。

最终在尝试了不同的方法后,我最终使用了 custom allocator

std::map 是我的结构用来保存数据的众多容器之一。分配的 ram 的总大小实际上约为 400MB,该结构包含列表、映射、不同数据的向量,其中这些容器的许多成员都是指向其他容器的指针。因此,我采取了激进的方法,并以极快的速度 'resurrect' 我的整个结构以及所有映射和内部指针。虽然最初在我的 post 中它是关于在修改代码并增加额外的复杂性后快速在调试版本中使其 'fast' 它变得同样适用于发布版本:构建时间在发布版本中变为大约 10 秒。

所以,一开始我修改了所有结构成员以使用我的自定义分配器,这样我就看到了实际分配了多少内存:

total allocated: 1970339320 bytes, total freed: 1437565512 bytes

这样我可以估计总共需要大约 600MB。然后,在我的自定义分配器中,我添加了静态全局方法 my_aloc::start_recording() 此方法将分配 600MB 的 ram 块,在调用 start_recording 之后,我的自定义分配器将简单地从该 600MB 分配 return 地址堵塞。在 start_recording 被调用后,我将复制整个结构(它实际上是一个结构向量)。制作副本时没有过度分配,每个结构成员只分配足够的 ram 用于其存储。基本上通过复制结构,它实际上只分配了大约 400MB 而不是 600MB。 我说在我的构造内部有很多指向内部成员的指针,那么如何重用我的自定义分配器记录的这 400MB "snapshot"?我可以为 "patch" 指针编写代码,但也许它甚至行不通:我有很多地图也使用指针作为自定义比较结构的键,该结构取消引用指针以比较实际指针与值。此外,一些映射包含列表中的迭代器,处理所有这些会非常混乱。另外,我的整体结构并不是一成不变的,它还在进行中,如果有什么改变,那么补丁代码也需要改变。所以,答案很明显:我只需要在相同的基地址加载整个 400MB 的快照。在 windows 中,我使用 VirtualAlloc,在 linux 中,可能需要使用诸如 mmap 之类的东西,或者可以使用 boost shared mem lib 来使其更便携。最后整体加载时间下降到 150 毫秒,而在发布时我需要超过 10 秒,而在调试版本中它现在可能已经在几分钟内了。