如何同时填充 std::unordered_map ?

Posted

技术标签:

【中文标题】如何同时填充 std::unordered_map ?【英文标题】:How can I fill a std::unorderd_map concurrently? 【发布时间】:2016-10-09 09:34:12 【问题描述】:

我需要用大约 100 个条目填写 std::unordered_map<int,T>。这些构建起来很昂贵,我想同时使用 OpenMP 来做到这一点:

unordered_map<int, T> mapWithTs;

#pragma omp parallel for schedule(dynamic) // dynamic because T constructs in some unpredictable time.
for(int i=0; i<100; ++i)

  mapWithTs.emplace(i, i) // calls the constructor T(i)

我读到地图将重新散列,然后迭代器将不再有效。我必须怎么做才能完成这项工作?

此外,标准库的并发解决方案是什么样的?

【问题讨论】:

建造成本高,但搬家便宜吗?每个线程是否可以创建自己的完整矢量,然后一个线程将这些矢量对象移动到地图中? 您将创建多个线程,每个线程填充自己的地图,然后合并地图(在一个线程中)。 您只需要同步(互斥)对地图的访问。我不知道如何与 OpenMP 进行同步,但大概你会。如果没有,请查阅文档。 @Galik,你提到了我首先计划的内容,但后来想知道是否有办法直接做到这一点。例如抑制重新散列或类似的东西。现在我认为最好的方法是默认构造一个“空” T 然后覆盖它或制作一些 T::doTheExpensivePart() 方法。 为什么不使用已经为并行设计的数据结构,例如 TBB 的并发无序映射 software.intel.com/en-us/node/506171 ?您可以将它与 OpenMP 一起使用,但您可能也希望查看 TBB 来满足您的所有并行性需求。 (FWIW 我为英特尔工作,有点不在 TBB 上,而且无论如何 TBB 是 BSD 风格的许可 :-))。 【参考方案1】:

如果这些昂贵的构造实例是通过引用来帮助的,即通过 shared_ptr、原始指针等,我建议让每个线程创建自己的堆栈本地映射,在一个规范地也称为“映射”的步骤中,并且然后在一个称为“reduce”的步骤中将它们全部组合到一个线程中。

这称为“map-reduce”算法。

“map”是将函数应用于集合的所有元素的函数的常用名称

“reduce”是一个函数的常用名称,它通过调用具有当前中间结果和每个元素的函数来将集合中的所有元素组合为一个值

因此得名:)

【讨论】:

需要注意的一点是通过newmalloc 或类似方法隐式使用堆内存的“映射”操作。大多数实现为所有线程提供单个堆,因此使用堆内存可能会导致争用和锁定,类似于显式使用带锁的共享映射。 你的意思是当它耗尽时?或者当涉及到并发分配时,JVM、GNU libc++、BSD libc++ 的堆是否效率非常低? 你的意思是当它耗尽时?或者当涉及到并发分配时,JVM、GNU libc++、BSD libc++ 的堆是否效率非常低?其中任何一个。任何一个。这取决于堆实现和内存使用模式。以我的经验,多线程感知堆往往工作得很好——大多数时候。但是我已经看到了减少这种堆实现以有效地单线程应用程序的使用模式。这只是需要注意的一点,如果您对运行 8 个线程的预期加速 6 倍进行基准测试,但根本看不到加速。 非常感谢您指出这一点,这可能会成为非常有价值的知识,就像监控 unix 或 linux 机器的系统负载一样,我最近才发现这是一件大事:D【参考方案2】:

正如 Galik 和 yeoman 所指出的,必须使移动对象成为一项廉价的操作。如果它已经是(建筑很重,但搬家很便宜),那么你很好。否则,您应该将对象放入uniq_ptr's。在此重新散列之后也将是一个便宜的操作(是的,重新散列需要线性时间,但它是 0(1) 摊销复杂度)。所以你不必担心 rehash。

接下来是填充地图。您正在从多个线程使用它,因此您必须确保不超过一个线程同时使用它。你需要像 #pragma omp critical 或 std::mutex 这样的东西。这里是重要的部分:如果你像现在一样使用emplace,那么重的 T 构造函数将在关键部分下执行,这会扼杀整个并行化的想法。因此,在这种特殊情况下,您更愿意事先创建对象 T,然后进入临界区并将对象移动到哈希图中。

如果 T 的构造真的是一个繁重的操作(它需要更长的时间,然后将一个值插入到 unordered_map 中),那么就可以了。通过制作每个线程列表并将它们插入到地图中,您不会获得性能提升。否则,yeoman 的答案可以通过增加代码复杂性的成本为您带来额外的好处。

【讨论】:

以上是关于如何同时填充 std::unordered_map ?的主要内容,如果未能解决你的问题,请参考以下文章

如何以线程安全的方式使用`std::unordered_map`?

std::unordered_map 如何表现? [C++]

如何在删除元素时防止重新散列 std::unordered_map?

CDT 索引器找不到 std::unordered_map

如何将 std::unordered_map 部分专门化为我的模板类的成员?

如何使用变体作为 unordered_map 中的键?