基于华为云的MPI并行程序设计
1. 利用华为云环境搭建小型集群环境
首先根据如下配置购买弹性云服务器:
计费模式 | 区域 | CPU架构 | 规格 | 镜像 | 系统盘 |
按需计费 | 华北-北京四 | 鲲鹏计算 | kcl.large.2 | 公共镜像:openEuler20.03 | 至少40GB |
购买成功后远程登录三个弹性云服务器。为了防⽌服务器的⽂件混乱,在每⼀台机器下都建⽴个⼈账户。
以⽤户名zhangsan 为例,在每台主机的root用户下建立个人的账户,命令如下:
adduser zhangsan passwd zhangsan usermod -aG wheel zhangsan |
添加用户后进行三台机器主机名和IP解析的配置。打开/etc/hosts,注释文件原先本身的信息并添加以下信息:
主机1IP 主机1名 主机2IP 主机2名 主机3IP 主机3名 |
之后登录新账户,使用ssh-keygen -t rsa -b 4096指令生成本地密钥并添加公钥至所有主机。之后通过sudo yum -y install gcc-gfortran --nogpgcheck与sudo yum -y install mpich mpich-devel mpich-doc --nogpgcheck指令安装依赖包并如图配置环境变量即可。
记得最后执行source ~/.bashrc指令使之生效哦!
2. N体问题MPI并行程序设计
N体问题是研究物体之间相互作用力产生的效果的问题,这个问题的目标是确定太空中通过引力相互作用的天体的位置和运动。根据万有引力定律与牛顿第二定律,所有物体都会由于这些力的作用移动到新的位置并获得新的速度。很可惜的是,对于超过三个物体的系统的N体问题并没有确切的近似解。但我们可以使用计算机仿真,以获得尽可能精确的数值解。
设时间间隔为△t,则对于一个质量为m的特定物体,其所受力为:
新的速度为:
其中vt+1为物体在时刻t+1的速度,vt为物体在时刻t的速度。如果物体以速度v移动了△t时间,则位置变化为:
其中xt为物体在时刻t的位置。
根据上述公式,我们就能求得N体问题在t时刻的位置与速度的近似数值解。
N体串行算法的伪代码描述如下:
for (t = 0; t < tmax; t++) { for (i = 0; i < n; i++) { F = Force_routine(i); //计算第i体的受力 v[i]new = v[i] + F *dt / m; //计算第i体的当前速度 x[i]new = x[i] + v[i]new * dt; //计算第i体的当前位置 } for (i = 0; i < n; i++) { //更新速度与位置 x[i] = x[i]new; v[i] = v[i]new; } } |
接下来,我们需要根据该串行代码进行N体问题并行MPI程序的设计。由于使用的是MPI模型,首先需要为各个进程初始化信息。代码如下:
MPI_Init( &argc, &argv ); MPI_Comm_rank( MPI_COMM_WORLD, &rank ); MPI_Comm_size( MPI_COMM_WORLD, &size ); |
核心代码思路不变,只需增加进程间的通信即可。在每个时间步中,每个进程都计算分配给本进程的粒子的受力、速度与位置。随后将这些信息发送给下一个进程,并接收来自上一个进程的信息,最后更新该时间步的所有数据。代码如下:
while (cnt--) { double max_f, max_f_seg; k = 0; //加载初始缓冲 memcpy( globalbuf, particles, npart * sizeof(Particle) ); max_f = 0.0; for (pipe=0; pipe<size - 1; pipe++) { MPI_Send( globalbuf + k, counts[(ringrank-pipe)%size], particletype, (rank+1)%size, pipe, MPI_COMM_WORLD ); k += counts[(ringrank-pipe)%size]; MPI_Recv( globalbuf + k, counts[(ringrank-pipe-1)%size], particletype, (ringrank-1)%size, pipe, MPI_COMM_WORLD, statuses ); } max_f_seg = ComputeForces( particles, pv, npart, globalbuf, allparticlescount); if (max_f_seg > max_f) max_f = max_f_seg; //一获得受力情况就计算新的位置信息 sim_t += ComputeNewPos( particles, pv, npart, max_f, MPI_COMM_WORLD ); } |
这样就成功实现了N体问题的并行MPI程序设计,详细代码见附件。
3.质数计算MPI并行程序设计
质数计算程序的功能为获取用户输入的数N,计算2-N内质数的个数。串行算法如下:
# include <stdlib.h> # include <stdio.h> # include <time.h> int prime_part ( int id, int p, int n );
int main ( int argc, char *argv[]) { int id; int n = 100000; int p; int total; int total_part; p = 4; total = 0; for ( id = 0; id < p; id++ ) { total_part = prime_part ( id, p, n ); total = total + total_part; } printf ( "\n" ); printf ( " Between 2 and %d, there are %d primes\n", n, total ); return 0; } int prime_part ( int id, int p, int n) { int i; int j; int prime; int total_part = 0;
for ( i = 2 + id; i <= n; i = i + p){ prime = 1; for ( j = 2; j < i; j++) { if ( i % j == 0) { prime = 0; break; } } if (prime) { total_part = total_part + 1; } } return total_part; } |
可以看到,串行程序中虽然将计算任务划分成四部分,但仍然是在同个进程内执行,无法满足并行执行的要求。因此我们只需在串行程序的基础上将各部分任务分派给各个线程即可。main函数改写如下:
int main(int argc, char* argv[]) MPI_Init(&argc, &argv); start_time = MPI_Wtime(); if (id == 0) { MPI_Finalize(); |
但并行化后发现程序运行效果并不好,原因在于计算质数个数的prime_part函数是以线程数为间隔划分任务的,而除了2以外的偶数都不是质数,分配到偶数的线程便做无用功,这就导致了各线程的负载不均衡,容易出现虽然划分了多个线程但是大部分任务仍然由少部分线程完成的情况。为了解决这个问题,我们需要改进计算质数个数的算法使得各线程负载均衡以实现更好的性能。改进算法如下:
int is_prime(int n) { int prime_part(int id, int p, int n) return total_part; |
改进后prime_part函数在划分任务时不会再考虑除2以外的偶数,直接减少了一半的计算规模,也使得任务划分更为均衡,各个线程的任务数基本一致。此外,在判断当前计算的数i是否为质数时没有采用从2到i-1一个个尝试是否是i的因数的方法,而是从2到√i尝试是否为i的个数,这也大大减少了计算量,提升了程序性能。具体程序见附件。
- 点赞
- 收藏
- 关注作者
评论(0)