为什么在OpenMP并行块中使用OpenMP Single?

时间:2018-03-07 06:05:30

标签: c parallel-processing openmp

例如:

#pragma omp parallel num_threads(2)
{
    #pragma omp single
    QuickSort(arr, 0, arr.length, cuttoff);
}

据我所知#pragma omp single使代码串行化。只有一个进程将执行QuickSort()。上面的代码有问题吗?

1 个答案:

答案 0 :(得分:2)

此方案通常由任务并行使用,首先,您需要创建固定数量的线程,其次,您定义一些任务,然后将其分发到OpenMP运行时的这些线程。任务的定义(通过#pragma omp task#pragma omp section)必须在并行部分内完成。但是,有些情况,例如在快速排序中,从多个线程定义任务没有意义;要仅从单个线程定义它们,请使用#pragma omp single

快速排序的并行化并非易事。除了任务,您还需要在递归的顶层并行执行 partitioning 。这是通过嵌套并行性实现的OpenMP。

考虑一下,例如,quicksort和8个核心:

  1. 您创建了8个主题(#pragma omp parallel)。
  2. 您只能通过一个线程调用quicksort#pragma omp single)。
  3. quicksort内,您调用partition,它通过嵌套并行性将单个线程拆分为8个线程,并且并行执行分区。
  4. 最后,创建了2个分区。单个线程现在创建2个任务(#pragma omp task)并通过调用quicksort以递归方式调用它们。
  5. OpenMP运行时将这两个任务分配给8个可用线程中的任何一个,因为现在所有线程都处于空闲状态。
  6. 现在,2个线程正在执行quicksort函数。
  7. 此案例仅描述最高级别的递归。在较低级别,您需要关心负载平衡,因为两个分区可能具有不同的大小。 (在第二级,有两个线程同时执行partition。但是,由于两个分区的大小通常不同,例如,您将第一个线程拆分为3个线程,将第二个线程拆分为5个线程以利用所有8核。基本上你希望尽可能多地利用所有这些核心,这对于多线程快速排序开发者来说是一个相当大的挑战。)

    高效实施还将采用尾调用优化,其中只创建1个任务而不是2个;它节省了大量的堆栈空间并避免了许多call指令。低于某个阈值,然后切换到顺序快速排序(或者更改为快速排序,插入排序和堆栈的某种组合)。

    为了说明,这是使用OpenMP并行快速排序的简单实现,用于对整数数组进行排序:

    void par_qs_rec(int* a, long lo, long hi) {
       if (lo < hi) {
          long p = partition(a, lo, hi);
          #pragma omp task
          par_qs_rec(a, lo, p - 1);
          #pragma omp task
          par_qs_rec(a, p + 1, hi);
       }
    }
    
    void par_qs(int* a, long lo, long hi) {
       #pragma omp parallel 
       {
          #pragma omp single
          par_qs_rec(a, lo, hi); // (1)
       }
    }
    

    如果没有#pragma omp single会发生什么情况,即par_qs_rec的所有主题是否会调用初始(1)