【问题标题】:Fastest way to "resurrect" (serialize/ and deserialize) an std::map“复活”(序列化/反序列化)std::map 的最快方法
【发布时间】:2016-06-03 20:57:41
【问题描述】:

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

【问题讨论】:

  • 我希望排序后的数据需要对地图中的树进行更多的重新平衡,因此速度会变慢。您是否考虑过将vectorlower_bound 一起使用?
  • 根据您的编译器,您可以启用某种程度的优化,同时保留调试信息。什么编译器?
  • 更多关于上下文的信息会很有帮助。为什么你首先需要摆脱地图?你也许可以颠覆它。
  • @nwp 我正在考虑使用lower_bound 和排序向量推出我自己的“地图”。在我的情况下可能是可接受的方法
  • @vu1p3n0x 我使用 Visual Studio 2015(微软编译器)。

标签: c++ stdmap


【解决方案1】:

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

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

抽象

如果std::map 提供的信息可以被接口类替换,那么可以隐藏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 的内存和结构以查找问题。

【讨论】:

  • 我猜使用包含大量条目的向量可能会导致 bad_alloc。还有unordered_map。如果它是完全静态的,则可以将 Array 放入 rom。
  • 关于 1) - 似乎不起作用,请参阅我在顶部的评论。它缩短了时间,但并不接近我在发布版本中得到的。 2) - vector+lower_bound 会更快,但我担心出于与 1) 类似的原因,无论如何我都会得到缓慢的加载时间。也许,我需要将 init 代码移到 dll 中,然后 1) 可能与发布版本中一样快
  • 我试图将代码移动到 dll - 它不起作用,std 容器的内部在发布和调试版本中变得二进制不兼容。
  • 这就是为什么你需要隐藏内部结构 - 正如我已经提到的那样。
  • @mksteve 好的,我明白你的意思。在我的情况下,我实际上需要访问std::map 附带的这些“内部”:我需要迭代元素并执行其他需要我为所有这些提供替代包装器/接口的事情。
【解决方案2】:

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

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

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

total allocated: 1970339320 bytes, total freed: 1437565512 bytes

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-11
    • 2013-08-12
    • 2014-05-15
    • 2010-10-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多