第一次并行编程作业(openmp)

5.2 openmp蒙特卡洛方法计算pi

图片 图片

代码

#include<stdlib.h>
#include<stdio.h>
#include<time.h>
#include<omp.h>

int main(int argc,char** argv){
    long long int num_in_circle = 0;
    long long int number_of_tosses=1000000;
    //int num_in_circle = 0, number_of_tosses=100000;
    double pi_estimate;
    int thread_count=strtol(argv[1],NULL,10);
    double x,y,distance_squared;
    int toss;
    srand(time(NULL));
    #pragma omp parallel for num_threads(thread_count) default(none) \
reduction(+:num_in_circle)shared(number_of_tosses)private(toss,x,y,distance_squared)
         for (toss = 0; toss < number_of_tosses; toss++) {
            x=(double)rand()/(double)RAND_MAX;
            y=(double)rand()/(double)RAND_MAX;
            distance_squared = x*x + y*y;
            if (distance_squared <= 1)
                num_in_circle++;
            }
    pi_estimate = (double)num_in_circle/number_of_tosses*4;
    printf("pi estimate as: %lf",pi_estimate);
    return 0;
}

加入计时和线程数量控制后的代码

#include<stdlib.h>
#include<stdio.h>
#include<time.h>
#include<omp.h>
#define pi 3.1415926

int main(int argc,char** argv){
    long long int number_of_tosses;
    //int num_in_circle = 0, number_of_tosses=100000;
    double pi_estimate;
    int thread_count;
    double x,y,distance_squared;
    long long int toss;
    clock_t starttime, endtime;
    srand(time(NULL));
    while(true){
        long long int num_in_circle = 0;
        scanf("%lld %d",&number_of_tosses,&thread_count);
        starttime = clock();
       #pragma omp parallel for num_threads(thread_count) default(none) \
               reduction(+:num_in_circle) shared(number_of_tosses) private(toss,x,y,distance_squared)
             for (toss = 0; toss < number_of_tosses; toss++) {
                x=(double)rand()/RAND_MAX;
                y=(double)rand()/RAND_MAX;
                distance_squared = x*x + y*y;
                if (distance_squared <= 1)
                    num_in_circle++;
                }
        pi_estimate = (double)num_in_circle/number_of_tosses*4;
        printf("pi estimate as: %lf %lf%%\n",pi_estimate,abs(pi_estimate-pi)*100/pi);
        endtime = clock();
        double time=(double)(endtime - starttime)*1000/ CLOCKS_PER_SEC;
        printf( "time:%lf ms\n" ,time );
    }
    return 0;
}

所得测试结果

image.png

结果可以看出,并行可以提高效率,但是并不是线程越多效率越高,三线程和四线程用时几乎没有区别。另外,没有证据表示线程数量和结果准确率有关系。

5.3 openmp实现计数排序(桶排序)

图片

a. 私有变量:i,j,count,共享变量:temp,n,a;

**b, **我认为不存在。因为只并行化外层循环,单层循环内count的变化与上一轮和下一轮都没有关系。因为在每次线程启动时候count被初始化为0

最外层并行化的代码以及结果(试验1000次,实验环境为四核机器,实际显示是时间总和,报告写完了才发现命名错了)

c. 能够并行化对memcpy函数的调用。两种思路:多次调用memcpy函数,将memcpy函数用代码重构。

d. 代码见下方

e. 与串行程序相比加速比为1.70,慢于qsort(具体过程见下)

代码1.0

#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
#include <ctime>
#include"malloc.h"
#include<string.h>
#include <iostream>
#include <fstream>
using namespace std;
double count_sort(int a[], int n);
int main(int argc, char* argv[]) {

    int i,datalen=0;
    double ave=0;
    int num[1010];

    ifstream file("C://Users/18019/Desktop/data.txt");

    while(! file.eof() )

    file>>num[datalen++];

    file.close();

    //int thread_count = strtol(argv[1], NULL, 10);
   // #pragma omp parallel num_threads(thread_count)
    for(int i=0;i<10;i++){
    ave+=count_sort(num,datalen);
    }
    printf( "average time:%lf s\n" ,ave );
  //  for(int i=0;i<datalen;i++){
     //   printf("%d %d\n",i,num[i]);
  //  }
    return 0;
}

double count_sort(int a[], int n){
    clock_t starttime, endtime;
    double time=0;
    starttime = clock();
    int i, j, count;
    int*  temp =(int*) malloc(n*sizeof(int));
    #pragma omp parallel for private(i, j, count) shared(n, a, temp)
    for (i = 0; i < n; i++){
        count = 0;
        for (j = 0; j < n; j++){
            if (a[j] < a[i])
                count++;
            else if (a[j] == a[i] && j < i)
                    count++;
         }
           temp[count] = a[i];
    }
    memcpy(a, temp, n*sizeof(int));
    free(temp);
    endtime = clock();
    time=(double)(endtime - starttime) / CLOCKS_PER_SEC;
	printf( "Total time:%lf s\n" ,time );
	return time;
}

并行结果:

图片

串行结果

图片

更改一下计时区域,只看循环部分:

代码1.5

#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
#include <ctime>
#include"malloc.h"
#include<string.h>
#include <iostream>
#include <fstream>
using namespace std;

double count_sort(int a[], int n);

int main(int argc, char* argv[]) {

    int i,datalen=0;
    double ave=0;
    int num[1010];

    ifstream file("C://Users/18019/Desktop/data.txt");

    while(! file.eof() )

    file>>num[datalen++];

    file.close();

    //int thread_count = strtol(argv[1], NULL, 10);
   // #pragma omp parallel num_threads(thread_count)
    for(int i=0;i<1000;i++){
    ave+=count_sort(num,datalen);
    }
    printf( "sum time:%lf s\n" ,ave );
  //  for(int i=0;i<datalen;i++){
     //   printf("%d %d\n",i,num[i]);
  //  }
    return 0;
}

double count_sort(int a[], int n){
    clock_t starttime, endtime;
    double time=0;
    int i, j, count;
    int*  temp =(int*) malloc(n*sizeof(int));
     starttime = clock();
  //  #pragma omp parallel for private(i, j, count) shared(n, a, temp)
    for (i = 0; i < n; i++){
        count = 0;
        for (j = 0; j < n; j++){
            if (a[j] < a[i])
                count++;
            else if (a[j] == a[i] && j < i)
                    count++;
         }
           temp[count] = a[i];
    }
    endtime = clock();
    time=(double)(endtime - starttime) / CLOCKS_PER_SEC;
    memcpy(a, temp, n*sizeof(int));
    free(temp);
//	printf( "Total time:%lf s\n" ,time );
	return time;
}

并行结果

图片

串行结果

图片

感到很奇怪,即使只计算循环区域依旧加速不到两倍,尝试加大了测试次数,结果依旧达不到两倍。我也尝试了设置线程调度方法,也没有上面用处。我用了四个线程加速比却上不了2,好悲伤我真的好悲伤。 另外,我尝试修改了使用的线程数量,发现无论是二/三/四所得结果都相近,认为在这个问题上线程数量的增加对结果影响不大。

对memcpy部分进行并行,第一个思路,不使用函数,将memcpy改为for循环

实验(因为该部分较短,所以我设置了计算一万次将差异放大):

代码2.0:

double count_sort(int a[], int n){
    clock_t starttime, endtime;
    double time=0;
    int i, j, count;
    int*  temp =(int*) malloc(n*sizeof(int));
    // starttime = clock();
    #pragma omp parallel for private(i, j, count) shared(n, a, temp)
    for (i = 0; i < n; i++){
        count = 0;
        for (j = 0; j < n; j++){
            if (a[j] < a[i])
                count++;
            else if (a[j] == a[i] && j < i)
                    count++;
         }
           temp[count] = a[i];
    }
    starttime = clock();
  //  #pragma omp parallel for //private(i) shared(n, a, temp)
	for (i = 0; i < n; ++i)
        a[i] = temp[i];
    endtime = clock();
    time=(double)(endtime - starttime) / CLOCKS_PER_SEC;
  //  memcpy(a, temp, n*sizeof(int));
    free(temp);
//	printf( "Total time:%lf s\n" ,time );
	return time;
}

串行部分:

图片

并行部分:

图片

翻车了,更慢了。启动线程花费时间太多。比较一下memcpy和改动后的串行部分

图片

得到的思考:写的什么鬼东西,老老实实调用memcpy不香吗????

代码2.5

试一试并行调用memcpy函数的思路:

 #pragma omp parallel  num_threads(4)//sections
    {
        memcpy(a, temp, n*sizeof(int));
    }
    endtime = clock();
    time=(double)(endtime - starttime) / CLOCKS_PEC;         
图片

我认为以我目前的相关知识和硬件配置最好不要并行memcpy。我查了一些资料,但是我没搞懂这中间的原理。

最终对比

qsort的结果:

图片

并行结果:

图片

串行结果:

图片

总结来说,qsort函数在这个问题上的表现最好,超过了我写的辣鸡openmp并行代码。下次能用qsort解决问题,绝对不自己写并行。

  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
1. 设计目的、意义(功能描述) 蒙特·卡罗方法(Monte Carlo method),也称统计模拟方法,是二十世纪四十年代中期由于科学技术的发展和电子计算机的发明,而被提出的一种以概率统计理论为指导的一类非常重要的数值计算方法。本次大作业主要是对蒙特·卡罗方法进行并行处理,通过OpenMP、MPI、.NET、Java、Win32API等一系列并行技术和并行机制对该算法进行并行处理,从而也进一步熟悉了蒙特·卡罗方法的串行算法和并行算法,实现了用蒙特·卡罗方法计算出半径为1单位的球体的体积,体会到了并行技术在实际生活中的应用。 2. 方案分析(解决方案) 蒙特·卡罗方法(Monte Carlo method)是指使用随机数(或更常见的伪随机数)来解决很多计算问题的方法。球的体积可以估算为:位于点模型内随机点个数与全体随机点个数的比值乘以包围盒的体积算的。 3. 设计分析 3.1 串行算法设计 假定球体用B表示,半径r=1单位,B1是包含B的参考立方体(在本例中是边长为2的正方体),在B1中产生N个均匀分布的伪随机点。对每个随机点检测其是否在B内,假设位于B内的随机点个数为N(in)(<=N),应用蒙特卡洛算法,则B的体积为 V=V1(N(in)/N) 其中V1是B1的体积。如果产生足够多的随机点,理论上可以获得任意逼近精度。 算法描述如下: BEGIN N=_MAX; FOR I=0;I<_MAX;I++ X=RANDOM(); Y=RANDOM(); Z=RANDOM(); IF (X*X+Y*Y+Z*Z)<=1 COUNT++; END IF; END FOR; BULK=V1*(COUNT/_MAX); END; 本算法主要是在参考立方体的选取上和定义的_MAX的值对结果影响较大,所以应该选择合适的数。 3.2 并行算法设计 对FOR循环进行划分使用两个处理器完成计算。例如对一个长为n的序列,首先划分得到两个长为n/2的序列,将其交给两个处理器分别处理;而后进一步划分得到四个长为n/4的序列,再分别交给四个处理器处理;如此递归下去最终得到结果。当然这是理想的划分情况,如果划分步骤不能达到平均分配的目的,那么结果的效率会相对较差。 伪代码如下: BEGIN N=_MAX; FOR1 I=0;I<_MAX/2;I++ X1=RANDOM(); Y1=RANDOM(); Z1=RANDOM(); IF (X1*X1+Y1*Y1+Z1*Z1)<=1 COUNT1++; END IF; END FOR1; FOR2 I=_MAX/2+1;I<_MAX;I++ X2=RANDOM(); Y2=RANDOM(); Z2=RANDOM(); IF (X2*X2+Y2*Y2+Z2*Z2)<=1 COUNT2++; END IF; END FOR2; BULK=V1*((COUNT1+ COUNT2)/_MAX); END; 3.3 理论加速比分析 实验中大量数据所产生的加速比比小量数据所产生的加速比要体现得更明显,并且数据生成的并行加速比随着处理器核的增加而增加。设处理器个数为p,数据量为n,由于正常情况下该快速排序算法的复杂度为O(nlogn),并行处理的时间复杂度为O(klogk),其中k=n/p,所以并行算法的时间复杂度为O((n/p)log(n/p)),理论加速比为nlogn/((n/p)log(n/p))=p+logp. 4. 功能模块实现与最终结果分析 4.1 基于OpenMP并行算法实现 4.1.1 主要功能模块与实现方法 利用了OpenMP里面的#omp parallel sections将对两个for循环用两个线程并行化执行,以多线程方式并行运行程序,并行的算法步骤如下: (1)初始化_max = 10000000; (2)创建两个线程; (3)由OpenMP编译指导语句控制产生并行执行代码区段; (4)将数据存放到tianqing_count; (5)各线程调用算法得出结果; 并行算法的部分代码如下: #pragma omp parallel for private(tianqing_x,tianqing_y,tianqing_z) reduction(+:tianqing_count2) for (tianqing_i = 0; tianqing_i<tianqing_max; tianqing_i++) { tianqing_x = rand(); tianqing_x = tianqing_x / 32767; tianqing_y = rand(); tianqing_y = tianqing_y / 32767; tianqing_z = rand(); tianqing_z = tianqing_z / 32767; if ((tianqing_x*tianqing_x + tianqing_y*tianqing_y + tianqing_z*tianqing_z) work1.pSumto(b, 0, MAXN - 1)); Thread newthread1 = new Thread(thread1); 创建Work类的对象work2; ThreadStart thread2 = new ThreadStart(() => work2.pSumto(c, 0, MAXN - 1)); Thread newthread2 = new Thread(thread2); stopwatch.Start(); 启动线程1和线程2; 等待进程结束; stopwatch.Stop(); 得到结果; 4.5.2 实验加速比分析 实验中创建了两个线程,通过多次测试,得出实验结果:由上面的理论加速比分析可知,当线程数为2时,理论加速比为2+log2=3.但由于实际操作中硬件设备以及内存分配的影响,实验加速比达不到理论值3.实验加速比在2.6~2.7左右。 4.6 并行计算技术在实际系统中的应用 4.6.1 主要功能模块与实现方法 该飞机订票系统主要实现了对机票的一些基本信息进行存储和管理的功能。在系统中实现了对机票信息的增删改查,考虑到查询的方便性,对机票按照航班号进行排序,而此排序方法用并行快速排序运用进来。利用OpenMP并行技术,对机票信息按顺序排列好,并分析了实验过程中的加速比。 4.6.2 实验加速比分析 实验中创建了两个线程,通过多次测试,得出实验结果:当数据量比较大时,加速比理论在1.9左右。数据量较大时体现出来的加速比更准确。由上面的理论加速比分析可知,当线程数为2时,理论加速比为2+log2=3.但由于实际操作中硬件设备以及内存分配的影响,实验加速比达不到理论值3.实验加速比在2.2~2.4左右。 5. 设计体会 虽然没有按时完成作业,但这份报告花了我好几天的时间,从开始的搭建并行计算平台到最后的程序运行成功可以说是对我的一个锻炼。每一次的遇到问题与每一次的解决问题都是一个成长。每一次遇到问题和解决问题都是一种锻炼,一种尝试,从我们上并行计算课我懂得了很多电脑硬件和软件的知识,这些可能对于我们这个专业以后都是没有机会接触的,所以我觉得选择了并行计算与多核多线程技术这门课是非常正确的。对OpenMP、MPI、WIN32API、Java、.NET的并行技术有了一定的了解。在搭建MPI并行程序这块,学习的知识尤为增加,这些都是在不断的摸索、学习中学会的。 这次的大作业虽然是对以前实验的整合,但它加深了我对并行计算的印象,也使我对并行计算知识的理解更加深刻,也使我认识到了自己很多不足之处。学习并行计算的历程不会因为完成本次大作业而停止,我们是为了用知识武装大脑而学习,通过学习充实自己的生活,要努力学习,争取以后能够完成规模更大的程序。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值