基于这个线程,OpenMP and STL vector,哪个数据结构是并行for循环中共享 std :: vector的好选择?主要方面是速度,矢量可能需要在循环期间调整大小。
答案 0 :(得分:35)
我认为你可以在大多数情况下使用std::vector
和OpenMP,但仍然有很好的表现。例如,以下代码并行填充std::vectors
,然后将它们组合在一起。只要您的主循环/填充功能是瓶颈,这通常可以很好地工作并且是线程安全的。
std::vector<int> vec;
#pragma omp parallel
{
std::vector<int> vec_private;
#pragma omp for nowait //fill vec_private in parallel
for(int i=0; i<100; i++) {
vec_private.push_back(i);
}
#pragma omp critical
vec.insert(vec.end(), vec_private.begin(), vec_private.end());
}
编辑:
OpenMP 4.0允许使用#pragma omp declare reduction
进行用户定义的缩减。上面的代码可以简化为
#pragma omp declare reduction (merge : std::vector<int> : omp_out.insert(omp_out.end(), omp_in.begin(), omp_in.end()))
std::vector<int> vec;
#pragma omp parallel for reduction(merge: vec)
for(int i=0; i<100; i++) vec.push_back(i);
编辑: 到目前为止我所展示的并没有按顺序填充向量。如果订单很重要,那么就可以这样做
std::vector<int> vec;
#pragma omp parallel
{
std::vector<int> vec_private;
#pragma omp for nowait schedule(static)
for(int i=0; i<N; i++) {
vec_private.push_back(i);
}
#pragma omp for schedule(static) ordered
for(int i=0; i<omp_get_num_threads(); i++) {
#pragma omp ordered
vec.insert(vec.end(), vec_private.begin(), vec_private.end());
}
}
这避免了为每个线程保存std :: vector,然后在并行区域之外的串行中合并它们。我了解了这个“技巧”here。 我不确定如何(或者如果可能的话)用户定义的缩减。。用户定义的缩减不可能做到这一点。
我刚刚意识到我从这个问题parallel-cumulative-prefix-sums-in-openmp-communicating-values-between-thread中找到的关键部分是不必要的。此方法也可以使订单正确
std::vector<int> vec;
size_t *prefix;
#pragma omp parallel
{
int ithread = omp_get_thread_num();
int nthreads = omp_get_num_threads();
#pragma omp single
{
prefix = new size_t[nthreads+1];
prefix[0] = 0;
}
std::vector<int> vec_private;
#pragma omp for schedule(static) nowait
for(int i=0; i<100; i++) {
vec_private.push_back(i);
}
prefix[ithread+1] = vec_private.size();
#pragma omp barrier
#pragma omp single
{
for(int i=1; i<(nthreads+1); i++) prefix[i] += prefix[i-1];
vec.resize(vec.size() + prefix[nthreads]);
}
std::copy(vec_private.begin(), vec_private.end(), vec.begin() + prefix[ithread]);
}
delete[] prefix;
答案 1 :(得分:13)
您链接的问题是在“多个线程写入单个容器的情况下,STL向量容器不是线程安全的”这一事实。如果您调用可能导致std::vector
保留的基础数组重新分配的方法,那么这只是正确的。 push_back()
,pop_back()
和insert()
就是这些危险方法的例子。
如果您需要线程安全重新分配,那么库intel thread building block会为您提供concurrent vector containers。您不应该在单线程程序中使用tbb :: concurrent_vector,因为访问随机元素所花费的时间高于std :: vector执行相同操作所需的时间(即O(1))。但是,并发向量以线程安全的方式调用push_back()
,pop_back()
,insert()
,即使在重新分配时也是如此。
编辑1:the following Intel presentation的幻灯片46和47给出了使用tbb :: concurrent_vector
并发重新分配的说明性示例编辑2:顺便说一下,如果你开始使用英特尔Tread Building Block(它是开源的,它适用于大多数编译器,它与C ++ / C ++ 11功能集成得比openmp好得多),那么你就不要不需要使用openmp来创建parallel_for,Here是使用tbb的parallel_for的一个很好的例子。