当前位置:   article > 正文

归并的OpenMP并行程序_#pragma omp for

#pragma omp for

目录

一. 实验要求

二. 关键步骤说明

1. 划分元的定位

2. 归并子序列的起始和结束下标的确定

1) A的子序列区间

2) B的子序列区间

3) 归并

三. 实验结果和分析

1. 测试数据

2. 分析

1). 第一次的时间

2). 加速比

四. 代码


一. 实验要求

 归并有序数组ab

  1. 数组大小n和线程数p都是可输入的参数。
  2. 数组ab中的每个数都初始化为一个0到1之间的随机double型值(用rand()/double(RAND_MAX)实现),然后调用sort函数分别排序数组ab
  3. 先将数组a等分为p段,然后选前p-1段的最后一个元素作为划分元,将数组b也分为p段,最后线程i归并数组ab的第i段。
  4. 添加检测归并结果是否正确的代码。
  5. 添加计算归并时间的代码,注意不包含数组的初始化时间。

二. 关键步骤说明

1. 划分元的定位

为了确保子段合并后段间有序(归并前提),所以根据划分的等长间距,等分A的划分元,且需要根据划分元对B数组进行划分。如图1所示,为了便于理解,此处额外使用了数组partition存储A的划分元,并使用数组partitionIndexInBArray来存储A划分元对应B数组的位置。

图 1 划分点的确定

2. 归并子序列的起始和结束下标的确定

1) A的子序列区间

由于A数组的划分点是等长划分的,所以不难看出若分成p段,设每段len = n / p,则第i段的下标区间为

\LARGE startIndex_{A-i} = i \times len

\LARGE endIndex_{A-i} = (i+1) \times len - 1


​    

当然,还需要考虑边界问题以及n可能不是p的整数倍数(len会向下取整),故我们还需要做一次最右侧的特判,如果i == p -1 , 则

\LARGE endIndex_{A-i}= n-1

2) B的子序列区间

由于B数组是根据A划分点的不等长划分,所以需要根据前置的partitionIndexInBArray数组来进行起始和结束下标的确定。

\LARGE startIndex_{B-i}=partitionIndexInBArray[i-1]

\LARGE endIndex_{B-i}=partitionIndexInBArray[i-1]

同理,我们还需要考虑边界问题,

如果I == 0 ,则\LARGE startIndex_{B-i}= 0

如果i == p -1,则\LARGE endIndex_{B-i}= n-1

3) 归并

根据前置的两序列区间,以类似双指针的形式合并到另外一个大数组即可。(和串行程序的归并相同)

代码见文章底部。

三. 实验结果和分析

测试并行程序在不同线程数下的执行时间和加速比(串行执行时间/并行执行时间),并分析实验结果。其中,数组大小n固定为100000000,线程数分别取1、2、4、8、16、32、64时,为减少误差,每项实验进行5次,取平均值作为实验结果。

1. 测试数据

表1 并行程序在不同线程数下的执行时间(秒)和加速比

线程数

执行时间

1

2

4

8

16

32

64

第1次

0.702

0.445

0.403

0.379

0.378

0.371

0.361

第2次

0.450

0.342

0.343

0.338

0.333

0.335

0.334

第3次

0.411

0.344

0.336

0.338

0.335

0.334

0.333

第4次

0.400

0.353

0.340

0.341

0.337

0.333

0.332

第5次

0.407

0.355

0.343

0.347

0.341

0.342

0.332

平均值

0.4740

0.3678

0.353

0.3486

0.3448

0.343

0.3384

加速比

1.106751

1.422512

1.488952

1.541021

1.515661

1.542274

1.548463

2. 分析

1). 第一次的时间

在每一轮(5次)的测试中,每一轮的第一次程序运行时间普遍更久,而后续的时间会降低且平稳。初步估计是程序初始加载时由于是在堆上动态开辟的数组空间,程序中没有有效的访问缓存,而运行一次后,根据局部性原理可以有效地复用缓存。

2). 加速比

当线程数量大于等于8(本机CPU核数)时,加速比处于1.54左右。在多次不同环境(后台程序不同)的测试中,加速比最高可达到1.88。原因分析如下:

  • 每一个线程的任务量(主要原因)

由于B段的划分点并非是等间距的,这代表着有可能存在一种极端的情况(如下图所示),即A数组的所有元素都大于B数组(或者A数组的所有元素都小于B数组),这样A中的划分元均定位到了B数组的同一个位置,合并时每一个线程的任务量不一定相同,可能存在线程的任务量接近于n的规模。

图 2 划分点的分段的极端情况

  • 访存

由于多个线程均需要访问同一块内存空间,故可能会因为访存限制而带来程序执行速度上的影响。

四. 代码

  1. #include <omp.h>
  2. #include <stdlib.h>
  3. #include <time.h>
  4. #include <iostream>
  5. #include <algorithm>
  6. #include <math.h>
  7. using namespace std;
  8. const int TESTTIMES = 5;//测试次数
  9. void checkArr(double a[], double ans[], int n) {
  10.     //check
  11.     bool flag = true;
  12.     for (int i = 0; i < n; i++) {
  13.          if (a[i] != ans[i]) {
  14.              cout << "结果错误" << endl;
  15.              flag = false;
  16.              break;
  17.          }
  18.     }
  19.     if (flag) cout << "结果正确" << endl;
  20. }
  21. // 输出数组内元素
  22. void showArr(double a[], int n) {
  23.     for (int i = 0; i < n; i++) {
  24.          printf("%f ", a[i]);
  25.     }
  26.     printf("\n");
  27. }
  28. void showArr(int a[], int n) {
  29.     for (int i = 0; i < n; i++) {
  30.          printf("%d ", a[i]);
  31.     }
  32.     printf("\n");
  33. }
  34. //借助临时数组b来合并两段有序的数组a
  35. void merge(double a[], double b[], int startIndex, int midIndex, int endIndex)
  36. {
  37.     int i = startIndex, j = midIndex + 1, k = startIndex;
  38.     while (i <= midIndex && j <= endIndex) {
  39.          if (a[i] > a[j]) {
  40.              b[k++] = a[j++];
  41.          }
  42.          else {
  43.              b[k++] = a[i++];
  44.          }
  45.     }
  46.     //合并剩下多余的
  47.     while (i <= midIndex) b[k++] = a[i++];
  48.     while (j <= endIndex) b[k++] = a[j++];
  49.     //重新赋值
  50.     for (i = startIndex; i <= endIndex; i++) a[i] = b[i];
  51. }
  52. int binarySearch(double nums[],int n, double target) {
  53.     if (n == 0) return 0;
  54.     /*如果目标值大于 nums 里的最大值,则将其插入到数组的末尾*/
  55.     if (target > nums[n - 1]) return n ;
  56.     /*将在区间 [0, n - 1] 内查找目标索引*/
  57.     int left = 0, right = n - 1;
  58.     while (left < right) {
  59.          int mid = left + (right - left) / 2;
  60.          if (target > nums[mid]) { //严格小于 target 的元素一定不是解
  61.              //下一轮搜索区间是 [mid + 1, right]
  62.              left = mid + 1;
  63.          }
  64.          else { //严格大于等于 target 的元素有可能是解
  65.               //下一轮搜索区间是 [left, mid]
  66.              right = mid;
  67.          }
  68.     }
  69.     return left;
  70. }
  71. void main()
  72. {
  73.     srand((unsigned)time(NULL));//设置随机数种子
  74.     int n, p;//数组大小n,线程数p
  75.     cout << "输入数组大小以及线程数:" << endl;
  76.     cin >> n >> p;
  77.     double* a = new double[n];//初始数据
  78.     double* b = new double[n];//temp数组
  79.     double* ans = new double[2*n + 1]; // answer
  80.     double* parallel_ans = new double[2 * n ];// 并行合并结果
  81.     double* partition = new double[p];//存储数组a的划分元,实际p-1个
  82.     int* partitionIndexInBArray = new int[p];//划分元对应B数组中的下标,实际p-1个
  83.     int* mergeIndex = new int[p+1];// 存储每一段合并后数组的起始位置,初始为0
  84.     int tid;//线程ID
  85.     int t = TESTTIMES;//测试次数
  86.     clock_t start, end;//记录起始和结束时间
  87.     double all_parallel = 0, all_non_parallel = 0;//统计全部时间,最后记录平均
  88.     double time;//记录单次时间
  89.     int i, j, k;//循环变量
  90.     while (t--) {
  91.          omp_set_num_threads(p);//设置线程数量
  92.          cout << endl << " -------------------------  mergeTurn --------------------------------- " << endl;
  93.          //并行: 随机初始化
  94.          #pragma omp parallel shared(a,b,n) private(i)
  95.          {
  96.              #pragma omp for schedule(static,4)
  97.              for (i = 0; i < n; i++) {
  98.                   a[i] = rand() / double(RAND_MAX);
  99.                   b[i] = rand() / double(RAND_MAX);
  100.              }
  101.          }
  102.          //排序a,b数组
  103.          sort(a, a + n);
  104.          sort(b, b + n);
  105.          //初始的合并段长度
  106.          int len = n / p;
  107.          i = 0;
  108.          j = 0;
  109.          k = 0;
  110.          //cout <<endl<< "A: " ;
  111.          //showArr(a, n);
  112.          //cout << endl;
  113.          //cout << "B: " ;
  114.          //showArr(b, n);
  115.          //cout << endl;
  116.          cout << "计算串行程序..." << endl;
  117.          //开始计时(串行程序)
  118.          start = clock();
  119.          while (i + j <= 2 * n) {
  120.              if (i == n) {//如果b[j]合并完了
  121.                   ans[k] = b[j++];
  122.              }
  123.              else if(j == n){//如果a[i]合并完了
  124.                   ans[k] = a[i++];
  125.              }
  126.              else if (a[i] < b[j]) {//如果a数组更小
  127.                   ans[k] = a[i++];
  128.              }
  129.              else {//如果b数组更小
  130.                   ans[k] = b[j++];
  131.              }
  132.              k++;
  133.          }
  134.          end = clock();
  135.          time = (end - start) / 1000.0;
  136.          printf("串行时间:%f s\n", time);
  137.          all_non_parallel += time;
  138.          //cout << "Ans:" << endl;
  139.          //showArr(ans, 2 * n);
  140.          //cout << endl;
  141.         
  142.          //处理划分点
  143.          #pragma omp parallel shared(a,partition,len,p) private(i)
  144.          {
  145.              //找到A划分点( p - 1 )
  146.              #pragma omp for  schedule(static,1)
  147.              for (int i = 0; i < p - 1; i++) {
  148.                   partition[i] = a[(i + 1) * len - 1];
  149.              }
  150.              //确保所有的划分点都计算完毕
  151.              #pragma omp barrier
  152.             
  153.              // 利用A划分点,二分定位来划分B数组 ( p - 1 )
  154.              #pragma omp for  schedule(static,1)
  155.              for (i = 0; i < p - 1; i++) {
  156.                   partitionIndexInBArray[i] = binarySearch(b, n, partition[i]);
  157.              }
  158.              //确保所有A划分点定位完B数组中的对应下标
  159.              //#pragma omp barrier
  160.          }
  161.          cout << "计算并行程序..." << endl;
  162.          start = clock();
  163.          //合并两段到新数组中
  164.          #pragma omp parallel shared(a,partition,len,p) private(i)
  165.          {
  166.              int startIndexA, endIndexA, startIndexB, endIndexB, startMergeIndex;
  167.              #pragma omp for  schedule(static,1)
  168.              for (i = 0; i < p; i++) {
  169.                   if (i == 0) {
  170.                       startIndexB = 0;
  171.                       startIndexA = 0;
  172.                   }
  173.                   else {
  174.                       startIndexB = partitionIndexInBArray[i - 1];
  175.                       startIndexA = i * len;
  176.                   }
  177.                   if (i == p - 1) {
  178.                       endIndexA = n - 1;
  179.                       endIndexB = n - 1;
  180.                   }
  181.                   else {
  182.                       endIndexB = partitionIndexInBArray[i] - 1;
  183.                       endIndexA = (i + 1) * len - 1;
  184.                  }
  185.                   //startMergeIndex = mergeIndex[i];
  186.                   startMergeIndex = startIndexA + startIndexB;
  187.                   //cout << startIndexA << " - " << endIndexA << " - " << startIndexB << " - " << endIndexB << " - " << startMergeIndex << endl;
  188.                   // 没有 哨兵
  189.                   while (startIndexA <= endIndexA && startIndexB <= endIndexB) {
  190.                       if (a[startIndexA] < b[startIndexB]) {
  191.                           parallel_ans[startMergeIndex++] = a[startIndexA++];
  192.                       }
  193.                       else {
  194.                           parallel_ans[startMergeIndex++] = b[startIndexB++];
  195.                       }
  196.                   }
  197.                   while (startIndexA <= endIndexA) {
  198.                       parallel_ans[startMergeIndex++] = a[startIndexA++];
  199.                   }
  200.                   while (startIndexB <= endIndexB) {
  201.                       parallel_ans[startMergeIndex++] = b[startIndexB++];
  202.                   }
  203.              }
  204.          }
  205.          end = clock();
  206.          time = (end - start) / 1000.0;
  207.         printf("并行时间:%f s\n", time);
  208.          all_parallel += time;
  209.          //检查答案
  210.          checkArr(parallel_ans, ans, 2*n);
  211.     }
  212.     printf("——————————————————————————————————\n");
  213.     printf("串行平均时间:%f\n", all_non_parallel / TESTTIMES);
  214.     printf("并行平均时间:%f\n", all_parallel / TESTTIMES);
  215.     printf("加速比: %f\n", all_non_parallel / all_parallel);
  216.     system("pause");
  217. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/275951
推荐阅读
相关标签
  

闽ICP备14008679号