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

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

本文关键字:方法 map 标准 resurrect 序列化 反序列化      更新时间:2023-10-16

作为测试代码的一部分,我需要构建复杂的结构,其中使用2个std::map实例;它们都有大约100万个元素。在优化的构建中,这是可以的,然而,在调试未优化的构建时,这几乎需要一分钟的时间。我使用相同的数据来构建地图,基本上,如果我能保存大块ram并在20ms内恢复它,那么我就可以在我的应用程序中有效地获得相同的地图,而无需每次等待一分钟。我能做些什么来加快速度?我可以尝试使用自定义分配器并保存/恢复其满负荷存储,或者有没有一种方法可以从已经排序的数据中构造std::map,这样它在时间上就会是线性的?

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

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

抽象

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

替代数据结构

对于一条基本上是静态的信息(例如,一条需要快速检索的静态数据),std::map不是实现这一点的最快方法,而std::pair<key,value>的排序std::vector在操作中会更具性能

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

在尝试了不同的方法后,我最终使用了自定义分配器。

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

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

total allocated: 1970339320 bytes, total freed: 1437565512 bytes

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