华科计算机并行实验报告.docx
《华科计算机并行实验报告.docx》由会员分享,可在线阅读,更多相关《华科计算机并行实验报告.docx(29页珍藏版)》请在冰豆网上搜索。
华科计算机并行实验报告
课程设计报告
题目:
并行实验报告
课程名称:
并行编程原理与实践
专业班级:
学号:
姓名:
指导教师:
报告日期:
计算机科学与技术学院
目录
1,实验一1
1实验目的与要求1
1.1实验目的1
1.2实验要求1
2实验内容1
2.1.1熟悉pthread编程1
2.1.2简单的thread编程2
2.2.1熟悉openMP编程3
2.3.1熟悉MPI编程4
2,实验2~57
1实验目的与要求7
2算法描述7
3.实验方案8
4实验结果与分析8
3心得体会10
附录:
10
3蒙特·卡罗算法求π的并行优化19
1.蒙特·卡罗算法基本思想19
2.工作过程20
3.算法描述20
4设计与实现21
5结果比较与分析23
6思考与总结24
1,实验一
1实验目的与要求
1.1实验目的
1)熟悉并行开发环境,能进行简单程序的并行开发,在Linux下熟练操作。
2)熟悉一些并行工具,如pthread,OpenMP,MPI等进行并行编程
3)培养并行编程的意识
1.2实验要求
1)利用pthread、OpenMP、MPI等工具,在Linux下进行简单的并行编程,并且掌握其编译、运行的方法。
2)理解并行计算的基础,理解pthread、OpenMP、MPI等并行方法。
2实验内容
2.1.1熟悉pthread编程
Linux系统下的多线程遵循POSIX线程接口,称为pthread。
编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。
下面是pthread编程的几个常用函数:
1,intpthread_create(pthread_t*restricttidp,constpthread_attr_t*restrictattr,void*(*start_rtn)(void),void*restrictarg);
返回值:
若是成功建立线程返回0,否则返回错误的编号
形式参数:
pthread_t*restricttidp要创建的线程的线程id指针
constpthread_attr_t*restrictattr创建线程时的线程属性
void*(start_rtn)(void)返回值是void类型的指针函数
void*restrictarg start_rtn的行参
2,intpthread_join(pthread_tthread,void**retval);
thread表示线程ID,与线程中的pid概念类似;retval用于存储等待线程的返回值连接函数pthread_join()是一种在线程间完成同步的方法。
该函数阻塞调用该函数线程直到thread指定的线程终止。
2.1.2简单的thread编程
1)程序创建了两个进程,分别调用函数myThread1和myThread2.主程序等两个子进程执行完后输出语句。
#include
#include
void*myThread1(void)
{
inti;
for(i=0;i<100;i++)
{
printf("Thisisthe1stpthread,createdbyzieckey./n");
sleep
(1);
}
}
void*myThread2(void)
{
inti;
for(i=0;i<100;i++)
{
printf("Thisisthe2stpthread,createdbyzieckey./n");
sleep
(1);
}
}
intmain()
{
inti=0,ret=0;
pthread_tid1,id2;
ret=pthread_create(&id1,NULL,(void*)myThread1,NULL);
if(ret)
{
printf("Createpthreaderror!
/n");
return1;
}
ret=pthread_create(&id2,NULL,(void*)myThread2,NULL);
if(ret)
{
printf("Createpthreaderror!
/n");
return1;
}
pthread_join(id1,NULL);
pthread_join(id2,NULL);printf("thisisthemainpthread!
\n");
return0;
}
2.2.1熟悉openMP编程
OpenMp提供的这种对于并行描述的高层抽象降低了并行编程的难度和复杂度,这样程序员可以把更多的精力投入到并行算法本身,而非其具体实现细节。
对基于数据分集的多线程程序设计,OpenMP是一个很好的选择。
同时,使用OpenMP也提供了更强的灵活性,可以较容易的适应不同的并行系统配置。
线程粒度和负载平衡等是传统多线程程序设计中的难题,但在OpenMp中,OpenMp库从程序员手中接管了部分这两方面的工作。
在编写使用OpenMP的程序时,则需要先includeOpenMP的头文件:
omp.h。
而要将for循环并行化处理只要在前面加上一行#pragmaompparallelfor就够了!
如下:
#include
#include
#include
voidTest(intn){
for(inti=0;i<10000;++i){
//donothing,justwastetime
}
printf("%d,",n);
}
intmain(intargc,char*argv[]){
#pragmaompparallelfor
for(inti=0;i<10;++i)
Test(i);
Printf("ok!
\n");
}
运行上述程序,:
若串行执行上述循环:
并行后结果顺序发生了改变。
2.3.1熟悉MPI编程
MPI即消息传递接口,其本质就是MPI标准定义了一组函数,使应用程序可以将消息从一个MPI进程送到另一个MPI进程。
消息(Message)是从一个有定义的缓冲区(buffer)传送到一个有定义的接收缓冲区。
有定义的缓冲区是指这个缓冲区是有类型的,即typedbuffer。
基本API:
1、intMPI_Init(int*argc,char**argv)
MPI_Init是MPI程序的第一个调用,它完成MPI程序的所有初始化工作,启动MPI环境,标志并行代码的开始。
2、intMPI_Finalize(void)
MPI_Finalize是MPI程序的最后一个调用,它结束MPI程序的运行,标志并行代码的结束,结束除主进程外其它进程。
其之后串行代码仍可在主进程(rank=0)上继续运行。
3、intMPI_Comm_size(MPI_Commcomm,int*size);
获取进程个数p,进程个数是在编译的时候,我们自己指定的。
4、intMPI_Comm_rank(MPI_Commcomm,int*rank);
MPI获取当前进程的RANK,rank值取址范围是0~p-1,RANK值唯一的表示了进程的ID,其中Rank=0的为主进程
5、进程间的相互通信
intMPI_Send(void*buf,intcount,MPI_Datatypedatatype,intdest,inttag,MPI_Commcomm);
发送函数:
当前进程将以buf为初始地址,长度为count且元素类型为datatype的信息发动给rank值为dest的进程,这条消息的标识符为tag。
其中datatype有MPI_INT,MPI_FLOAT等常用类型;Tag的作用是用于区分一对进程之间发送的不同信息
intMPI_Recv(void*buf,intcount,MPI_Datatypedatatype,intsource,inttag,MPI_Commcomm,MPI_Status*status);
接受函数:
从rank值为source的进程接受标识符为tag的信息,存入以buf为初始地址,长度为count的存储区域中,类型为datatype.
MPI编程例子:
#include
#include
intmain(intargc,char**argv)
{
intsize=0;
intrank=0;
intA[4]={5,9,4,7};
intB[4]={10,6,13,21};
intC[4];
MPI_Statusstatus;
MPI_Init(&argc,&argv);//initmpi
MPI_Comm_rank(MPI_COMM_WORLD,&rank);
MPI_Comm_size(MPI_COMM_WORLD,&size);
if(rank==0){
inti;
for(i=1;iMPI_Send(B+(i-1),2,MPI_INT,i,0,MPI_COMM_WORLD);
}
for(i=1;iMPI_Send(A+(i-1),2,MPI_INT,i,1,MPI_COMM_WORLD);
}
for(i=1;iMPI_Recv(C+(2i-2),2,MPI_INT,i,2,MPI_COMM_WORLD,&status);
}
}
else
{
inta[2],b[2],c[2];
MPI_Recv(&b,2,MPI_INT,0,0,MPI_COMM_WORLD,&status);
MPI_Recv(&a,2,MPI_INT,0,1,MPI_COMM_WORLD,&status);
c[0]=a[0]+b[0];
c[1]=a[1]+b[1];
MPI_Send(&c,2,MPI_INT,0,2,MPI_COMM_WORLD);
}
MPI_Finalize();
return0;
}
实例是计算两个二维数组相加,每个数组的元素有4个,通过创建子进程,每个子进程得到父进程传输的两个元素,做加法后回送给主进程的结果数组。
通过MPI的通信机制保证了结果的正确性。
2,实验2~5
1实验目的与要求
1.使用pthread的方法并行的实现矩阵乘法的运算;
2.使用OpenMP的方法并行的实现矩阵乘法的运算;
3.使用MPI的方法并行的实现矩阵乘法的运算;
4.使用MPI的方法并行的实现矩阵乘法的运算;
2算法描述
本次实验我采用的是两个N*N的矩阵相乘,在非并行的情况下,核心代码如下:
for(i=0;ifor(j=0;j{
C[i][j]=0;
for(k=0;kC[i][j]+=A[i][k]*B[k][j];
}
该算法两层循环的时间复杂度为O(N3),效率并不高。
但是每一层循环之间是相互独立的,即任意一层的执行不会影响其他循环执行的结果。
所以我们可以尝试着把最外层循环for(i=0;i#pragmaompparallelfornum_threads(PRO_NUM)
{
for(i=0;ifor(j=0;j{
C[i][j]=0;
for(k=0;kC[i][j]+=A[i][k]*B[k][j];
}
}
我们应该注意到,如果PRO_NUM>>N,那么将会有很大一部分线程没有利用起来。
然而OpenMP提供的动态调整的功能很好地解决了这个问题。
#pragmaompparallelfornum_threads1(dynamic)
{
for(i=0;ifor(j=0;j{
C[i][j]=0;
for(k=0;kC[i][j]+=A[i][k]*B[k][j];
}
}
3.实验方案
实验二:
首先将矩阵的乘法运算分成10个步骤,每个步骤都分别由一个独立的线程来完成,这里我采用的是1000*1000的方阵A和B。
然后就是向矩阵A和B赋值,为了便于检验计算结果,我将矩阵A和B都赋值为1。
最后利用实验室的机群进行编辑、编译和运行,同时输出整个并行计算所用的时间。
实验三:
与前一个实验不同的是使用OpenMP不需要自己去定义每一个线程,而是会自动帮你分配计算任务,显得非常的智能与便利,同时这也使得实验的难度大大减小。
首先我定义了两个1000*1000的方阵A和B。
然后向矩阵A和B赋值,为了便于检验计算结果,我将矩阵A和B都赋值为1。
最后利用实验室的机群进行编辑、编译和运行,同时输出整个并行计算所用的时间。
实验四:
同样是将矩阵乘法运算分成10个步骤,每个从线程计算其中的一个步骤并协同完成整个运算。
但是MPI不仅需要自己划分每个线程负责的预算量,还需要手动控制线程之间的通信。
这无疑增加了实验的难度,但是好处是效率非常高,这在后面的实验结果部分会提到。
整个实验利用实验室的机群进行编辑、编译和运行,同时输出整个并行计算所用的时间。
实验五:
实验五采用CUDA编程的方法借助GPU和CPU合作来实现矩阵乘法。
首先把矩阵分成10个块,然后分配给各个线程并行完成矩阵的乘法运算。
其中矩阵A和B的定义与初始化都和前面的实验保持一致。
在机群中编译并运行后输出并行计算的时间。
4实验结果与分析
本次并行编程实验的重点是程序并行带来的实际效率的提升,而运行时间则是运行效率最直观的体现,从运行时间上可看出效率的提高。
文件内容如下:
实验二:
运行./pthread.c,用时3.40s,得到的结果正确,效率确实得到了提高。
得到的结果矩阵:
实验三:
运行./OpenMP,用时4.09s,编程简单带来的代价就是开销增大。
实验四:
运行mpirun-np10MPI,用时0.081575s。
相比于多线程和OpenMP效率提高了50多倍,真的很强大。
实验五:
运行./CUDA,用时4.32s。
原以为应该比OpenMP快得多,然而结果却不是很理想。
3心得体会
通过本次实验我熟悉了并行编程的环境,初步接触并实践了并行编程的思想,同时通过查询资料我也对pthread,OpenMP,MPI,CUDA编程的思想有了基本的认识,并利用这些并行编程语言实现了矩阵乘法的并行运算,也得到正确的结果。
这次实验让我学到了很多,打破了我以往固有的串行编程思维。
以前做课程设计的时候,想的都是怎么完成需要实现的功能,而对程序的运行速度都没怎么考虑。
通过这次试验,我体会到当我们面临大量的数据处理时,选择的方法直接影响了程序执行时间的量级,可能一个好的方法,就可以把效率提高几十上百倍,这是非常可观的。
我们在后面的编程中,如果能发掘大量的并行性,带来的收益一定很大。
附录:
实验2:
#include
#include
#include
#include
#include
#include
#defineMAX1000
intC[MAX][MAX];
pthread_ttids[MAX];
intm=1000,n=1000,k=1000;
intmain(void)
{
inti,j;
srand(time(NULL));
for(i=0;ifor(j=0;j{
A[i][j]=1;
B[i][j]=1;
}
for(i=0;i{
tmp[i]=i;
if(pthread_create(&tids[i],NULL,matrix_mul,(void*)(tmp+i)))
{
perror("pthread_create");
exit
(1);
}
}
for(i=0;ipthread_join(tids[i],NULL);
clock_tfinish=clock();
printf("并行计算用时%.2f秒\n",(long)(finish-start)/1E6);
for(i=0;i{
for(j=0;j{
printf("%d\t",C[i][j]);
}
printf("\n");
}
printf("\n");
return0;
}
void*matrix_mul(void*arg)
{
intm=*(int*)arg;
inti,j,l;
intsum=0;
for(j=0;j{
sum=0;
for(l=0;lsum+=A[m][l]*B[l][j];
C[m][j]=sum;
}
}
实验3:
#include
#include
#include
#include
#defineMAX1000
#defineRANG10
doublematrix_mul(intm,intn,intk,intnum_thread);
intA[MAX][MAX];
intB[MAX][MAX];
intC[MAX][MAX];
intmain()
{
intm,n,k,i,j;
srand(time(NULL));
for(i=0;ifor(j=0;j{
A[i][j]=1;
B[i][j]=1;
}
doublematrix_mul(m,n,k,10);
for(i=0;i{
for(j=0;jprintf("%d\t",C[i][j]);
printf("\n");
}
printf("\n");
return0;
}
doublematrix_mul(intm,intn,intk,intnum_thread)
{
inti,j,l,sum;
structtimevalstart,end;
floats=0;
gettimeofday(&start,NULL);
#pragmaompparallelshared(A,B,C)private(i,j,l,sum)num_threads(num_thread)
{
#pragmaompforschedule(dynamic)
for(i=0;ifor(j=0;j{
sum=0;
for(l=0;lsum+=A[i][l]*B[l][j];
C[i][j]=sum;
}
}
gettimeofday(&end,NULL);
s=(end.tv_sec-start.tv_sec)*1000000+(end.tv_usec-start.tv_usec);
returns/1000000;
}
实验4:
#include
#include
#include
#defineN1000
intA[N][N],B[N][N];
unsignedlonglongC[N][N];
intmain(intargc,char**argv)
{
intprocess_num;
intprocess_id;
intslave_num;
intdest;
intsource;
introws;
introw_aver;
intremainder;
intoffset;
inti,j,k;
doublestart_time,end_time;
MPI_Init(&argc,&argv);
MPI_Statusstatus;
MPI_Comm_rank(MPI_COMM_WORLD,&process_id);
MPI_Comm_size(MPI_COMM_WORLD,&process_num);
srand((unsignedint)time(NULL));
slave_num=process_num-1;
if(process_id==0)
{
for(i=0;i{
for(j=0;j{
A[i][j]=1;
B[i][j]=1;
C[i][j]=0;
}
}
row_aver=N/slave_num;
remainder=N%slave_num;
offset=0;
for(dest=1;dest<=slave_num;dest++)
{
rows=(dest<=remainder)?
row_aver+1:
row_aver;
printf("sending%drowstoprocess%d\n",rows,dest);
MPI_Send(&offset,1,MPI_INT,dest,1,MPI_COMM_WORLD);
MPI_Send(&rows,1,MPI_INT,dest,1,MPI_COMM_WORLD);
MPI_Send(&A[offset][0],rows*N,MPI_INT,dest,1,MPI_COMM_WORLD);
MPI_Send(&B,N*N,MPI_INT,dest,1,MPI_COMM_WORLD);