并行化std::map迭代

openMp : parallelize std::map iteration

本文关键字:迭代 map 并行化 std      更新时间:2023-10-16

有一些关于这个问题的帖子,但没有一个让我满意。我没有openMp 3.0的支持,我需要在映射上并行化迭代。我想知道这个解决方案是否有效:

auto element = myMap.begin();
#pragma omp parallel for shared(element)
for(int i = 0 ; i < myMap.size() ; ++i){
 MyKeyObject * current_first = nullptr;
 MyValueObject * current_second = nullptr;
#pragma omp critical
{
    current_first = element->first;
    current_second = element->second;
    ++element;
}
// Here I can use 'current' as in a usual loop
}

所以我使用for循环只是为了确保线程将处理相同数量的map元素。这是一个正确的猜测还是会失败?

ps:我正在visual studio 2012上工作,所以如果你有一个关于如何使我的编译器支持openMp 3.0的提示,那也会解决我的问题。

这不是对你问题的直接回答,但我会尽量为你节省一些未来糟糕的"OpenMP with Visual Studio"体验。

Microsoft C/c++ Compiler只支持OpenMP 2.0。没有办法让它支持OpenMP 3.0或更高版本,因为OpenMP是内置在编译器核心中的,而不是一个附加包(除非有人提出一个外部的源码到源码转换引擎),而且微软似乎对提供进一步的OpenMP支持不感兴趣,同时推出他们自己的解决方案(见下文)。因此,您应该使用与Visual Studio集成的Intel C/c++编译器,或者像GCC或PGI C/c++编译器这样的独立编译器。

如果你是专门为Windows开发的,那么你可能想要放弃OpenMP而使用并发运行时,特别是PPL。PPL随Visual Studio 2012及更新版本一起提供,并提供与STL中的一些算法等价的数据和任务并行。你感兴趣的是concurrency::parallel_for_each(),它是std::for_each()的平行版本。它适用于前向迭代器,尽管效率不如随机迭代器。但是你必须确保处理映射的一个元素至少需要一千条指令,否则并行化就不会有好处。

如果您的目标是跨平台兼容性,那么英特尔线程构建块(简称英特尔TBB)是PPL的替代方案。它提供了tbb::parallel_do()算法,该算法专门设计用于前向迭代器。对于每个map元素的工作量,同样的警告也适用。

您的方法将工作,因为您访问并迭代临界区中的共享对象element。这是否有利于性能,您将不得不进行测试。以下是您可能想要考虑的另一种方法。我把它叫做"快进"方法。

让我们假设你想并行执行

for(auto element = myMap.begin(); element !=myMap.end(); ++element) {
    foo(element->first, element->second);
}

您可以使用OpenMP 2.0

#pragma omp parallel
{
    size_t cnt = 0;
    int ithread = omp_get_thread_num();
    int nthreads = omp_get_num_threads();
    for(auto element = myMap.begin(); element !=myMap.end(); ++element, cnt++) {
        if(cnt%nthreads != ithread) continue;
        foo(element->first, element->second);
    }
}

每个线程都要经过myMap.size()迭代器。然而,每个线程只调用foo myMap.size()/num_threads。你的方法只运行myMap.size()/num_threads迭代器。然而,它需要在每次迭代中使用一个临界区。

只要通过nthreads迭代器"快进"的时间远远少于foo的时间,那么快进方法是有效的,即:

nthreads*time(++elements) << time(foo)

然而,如果foo的时间顺序是迭代时间,foo是读写内存,那么foo可能是内存带宽限制,无论如何都不会随着线程数量的增加而扩展。

你的方法是行不通的——因为一个概念问题和一些bug。

  1. [bug]你总是会错过第一个元素,因为你做的第一件事是增加元素迭代器。
  2. [bug]所有线程将遍历整个map,因为元素迭代器不是共享的。顺便说一句,在你的代码中不清楚共享变量"部分"是什么。
  3. 如果你让元素共享,那么访问它的代码(临界区之外)将看到它当前指向的任何东西,而不管线程是什么。你最终会处理一些元素不止一次,而一些-根本不处理。

使用迭代器并行访问map没有简单的方法,因为map迭代器不是随机访问的。您可能希望手动拆分键,然后在不同的线程上使用键集的不同部分。