簡體   English   中英

隨機化后 OpenMP 快速排序的實現比順序慢

[英]OpenMP quicksort implementation slower than sequential after randomization

我正在修改 OpenMP 並嘗試實現快速排序的並行版本。
我已經實現了一個始終將第一個元素作為主元的版本,它的一個並行版本,一個通過選擇三個隨機元素的中位數來隨機化主元的版本,以及一個並行版本。
令我困擾的是,我在第一個並行化中獲得了良好的加速,而第二個(盡管以相同的方式並行化)比順序對應物慢。
在這兩種情況下,我只並行化函數的第一次調用,我知道我可以在三個遞歸中更深入地並行化,但關鍵是我希望從兩個並行化中獲得相同的加速。

這是“naive”(無隨機化)分區函數的代碼片段:

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

正如您所看到的,並行化的隨機版本比順序版本慢。 是我使用的整個代碼的 pastebin。

並行版本bench_par_rand不正確:它使用非線程安全rand 這會導致競爭條件。 因此,結果可能不是隨機的(快速排序所需的臨界點)並且代碼慢得多,因為線程會不斷嘗試修改 rand 函數的共享內部狀態(迭代種子)。 如果可能,請考慮使用 C++11 隨機數生成器(每個線程一個)。

一個快速的解決方法可能是一起使用 thread_local 存儲和 C++11 隨機數生成器,並通過safe_rand()重命名所有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

最后一個固定的並行版本要快得多! 但是,最后一個固定的順序版本也比以前慢。 我認為這是由於較慢的隨機生成器。 最后,您的代碼中沒有截止方法(對於小數組,從快速排序切換到更快的算法)。 因此,隨機生成器的成本在很大程度上得到了體現。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM