【问题标题】:OpenMP quicksort implementation slower than sequential after randomizationOpenMP 快速排序实现比随机化后的顺序慢
【发布时间】:2020-03-12 17:21:05
【问题描述】:

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

这是“朴素”(非随机化)分区函数的代码 sn-p:

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;
}

天真的快速排序:

void quicksort(vector<int>& v, int s, int e){
  if(s >= e) return;
  int p = partition(v,s,e);
  quicksort(v,s,p-1);
  quicksort(v,p+1,e);
}

并行化朴素快速排序:

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);
  }
}

以下是使用 Google 基准测试获得的结果:

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

如您所见,并行随机版本比顺序版本慢。 Here 是我用过的整个代码的 pastebin。

【问题讨论】:

    标签: c++ parallel-processing openmp


    【解决方案1】:

    并行版本bench_par_rand不正确:它使用了rand,这不是线程安全的。它导致竞争条件。因此,结果可能不是随机的(快速排序所需的关键点)并且代码要慢得多,因为线程将不断尝试修改 rand 函数的共享内部状态(迭代种子)。如果可能,考虑使用 C++11 随机数生成器(每个线程一个)。

    一种快速的解决方法是同时使用 thread_local 存储和 C++11 随机数生成器,并将所有 rand() 重命名为 safe_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);
    

    使用局部变量而不是全局 thread_local(并使用特定的 uniform_int_distribution 而不是模数)可以提高性能,尽管这样做有点乏味。

    以下是时间安排:

    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
    

    最后一个固定的并行版本要快得多!但是,最后一个固定顺序版本也比以前慢。我认为这是由于随机生成器速度较慢。 最后,您的代码中没有截止方法(从快速排序切换到用于小数组的更快算法)。因此,随机生成器的成本在很大程度上体现出来了。

    【讨论】:

      猜你喜欢
      • 2016-12-08
      • 2018-11-14
      • 2017-09-08
      • 2013-01-29
      • 2021-06-06
      • 2010-10-04
      • 1970-01-01
      • 1970-01-01
      • 2012-05-24
      相关资源
      最近更新 更多