C++ 随机化后,OpenMP快速排序实现比顺序排序慢

C++ 随机化后,OpenMP快速排序实现比顺序排序慢,c++,parallel-processing,openmp,C++,Parallel Processing,Openmp,我正在修补OpenMP并尝试实现快速排序的并行版本。 我已经实现了一个版本,它始终以第一个元素为轴心,一个并行版本,一个通过选择三个随机元素的中间值来随机化轴心的版本,以及一个并行版本。 困扰我的是,第一次并行化的速度很快,而第二次(尽管以同样的方式进行了并行化)比顺序并行化的速度慢。 在这两种情况下,我只对函数的第一次调用进行并行化,我知道我可以对三种递归进行更深入的并行化,但关键是我希望从两种并行化中获得相同的加速 下面是“朴素”(无随机)分区函数的代码片段: int partition(v

我正在修补OpenMP并尝试实现快速排序的并行版本。
我已经实现了一个版本,它始终以第一个元素为轴心,一个并行版本,一个通过选择三个随机元素的中间值来随机化轴心的版本,以及一个并行版本。
困扰我的是,第一次并行化的速度很快,而第二次(尽管以同样的方式进行了并行化)比顺序并行化的速度慢。
在这两种情况下,我只对函数的第一次调用进行并行化,我知道我可以对三种递归进行更深入的并行化,但关键是我希望从两种并行化中获得相同的加速

下面是“朴素”(无随机)分区函数的代码片段:

int partition(vector<int>& v, int p, int q){
  int x = v[p];
  int i = p;
  for(int j = p+1; j <= q; j++){
    if(v[j] <= x){
      i++;
      swap(v[i], v[j]);
    }
  }
  swap(v[i], v[p]);
  return i;
}
int rand_median(const vector<int>& v, int p, int q){
  int n1 = (rand() % (p - q)) + p;
  int n2 = (rand() % (p - q)) + p;
  int n3 = (rand() % (p - q)) + p;
  if((v[n1] <= v[n2] && v[n1] >= v[n3]) || (v[n1] <= v[n3] && v[n1] >= v[n2])) return n1;
  else if ((v[n2] <= v[n1] && v[n2] >= v[n3]) || (v[n2] <= v[n3] && v[n2] >= v[n1])) return n2;
  else return n3;
}

int rand_partition(vector<int>& v, int p, int q){
  int pivot = rand_median(v,p,q);
  swap(v[p], v[pivot]);
  int x = v[p];
  int i = p;
  for(int j = p+1; j <= q; j++){
    if(v[j] <= x){
      i++;
      swap(v[i], v[j]);
    }
  }
  swap(v[i], v[p]);
  return i;
}
正如您所看到的,并行随机版本比顺序版本慢。
这是我使用的全部代码的粘贴箱。

并行版本
bench\u par\u rand
不正确:它使用的
rand
不是线程安全的。它会导致一种竞赛状态。因此,结果可能不是随机的(快速排序所需的一个临界点),代码也可能慢得多,因为线程将不断尝试修改rand函数的共享内部状态(迭代种子)。如果可能的话,考虑使用C++ 11随机数生成器(每个线程一个)。 一个快速的解决方法是将线程本地存储和C++11随机数生成器一起使用,并通过
safe\u rand()
重命名所有
rand()
。以下是一个例子:

thread_local std::uniform_int_distribution<int> distrib(0, RAND_MAX);
thread_local std::mt19937 rdGen;
thread_local auto safe_rand = std::bind(distrib, rdGen);
最后一个固定并行版本要快得多!但是,最后一个固定顺序版本也比以前慢。我想这是因为随机发生器速度较慢。 最后,代码中没有截止方法(对于小数组,从快速排序切换到更快的算法)。因此,随机发生器的成本在很大程度上得到了体现

void quick_par(vector<int>& v, int s, int e){
  if(s >= e) return;
  int p = partition(v,s,e);
  omp_set_num_threads(2);
#pragma omp parallel sections
  {
    #pragma omp section
    quicksort(v,s,p-1);
    #pragma omp section
    quicksort(v,p+1,e);
  }
}
void quick_rand(vector<int>& v, int s, int e){
  if(s >= e) return;
  int p = rand_partition(v,s,e);
  quick_rand(v,s,p-1);
  quick_rand(v,p+1,e);
}
void quick_par_rand(vector<int>& v, int s, int e){
  if(s >= e) return;
  int p = rand_partition(v,s,e);
  omp_set_num_threads(2);

#pragma omp parallel sections
  {
    #pragma omp section
    quick_rand(v,s,p-1);
    #pragma omp section
    quick_rand(v,p+1,e);
  }
}
bench_ser       887282457 ns    887038659 ns           10 //naive quicksort
bench_par        10738723 ns     10734826 ns           70 //parallelized naive
bench_rand         613904 ns       613686 ns         1039 //randomized quicksort
bench_par_rand    3249751 ns      3248460 ns          213 //parallelized randomized
bench_sort         106110 ns       106074 ns         5952 //std::sort
thread_local std::uniform_int_distribution<int> distrib(0, RAND_MAX);
thread_local std::mt19937 rdGen;
thread_local auto safe_rand = std::bind(distrib, rdGen);
Base version:
 - bench_rand: 582499 ns
 - bench_par_rand: 2765300 ns

Fixed version with thread_local:
 - bench_rand: 1109800 ns
 - bench_par_rand: 737799 ns

Fixed version with local variables:
 - bench_rand: 798699 ns
 - bench_par_rand: 572300 ns