Ubuntun搭建并行计算环境

Ubuntun搭建并行计算环境

实验一、Linux环境下算法的测试

1、卡布列克常数6174

原理过程

验证卡布列克运算,任意一个四位数,只要它们各个位上的数字是不全相同的,就有这样的规律:

(1)将组成该四位数的四个数字由大到小排列,形成由这四个数字构成的最大的四位数;
(2)将组成该四位数的四个数字由小到大排列,形成由这四个数字构成的最小的四位数(如果四个数中含有0,则得到的数不足四位);
(3)求两个数的差,得到一个新的四位数(高位零保留)。

重复以上过程,最后得到的结果是6174,这个数被称为卡布列克数。例如:
4321-1234=3087
8730-378=8352
8532-2358=6174
7641-1467=6174
代码实现
c 复制代码
#include <stdio.h>

int count = 0;

void vr6174(int num);
void sort(int num, int *each);
void max_min(int *each, int *max, int *min);

int main()
{
    int n;
    printf("Please input n: ");
    scanf("%d", &n);
    printf("Output:\n");
    if (n > 9999 || n == 0)
    {
        printf("Input error!\n");
        return 0;
    }
    vr6174(n);
    return 0;
}

void vr6174(int num)
{
    int each[4], max, min;
    if (num != 6174 && num)
    {
        sort(num, each);
        max_min(each, &max, &min);
        num = max - min;
        printf("[%d]: %d-%d=%d\n", ++count, max, min, num);
        vr6174(num);
    }
}

void sort(int num, int *each)
{
    int i, *j, *k, temp;
    for (i = 0; i <= 4; i++)
    {
        j = each + 3 - i;
        *j = num % 10;
        num /= 10;
    }
    for (i = 0; i < 3; i++)
    {
        for (j = each, k = each + 1; j <= each + 3 - 1; j++, k++)
            if (*j > *k)
            {
                temp = *j;
                *j = *k;
                *k = temp;
            }
    }
}

void max_min(int *each, int *max, int *min)
{
    int *i;
    *min = 0;
    for (i = each; i < each + 4; i++)
        *min = *min * 10 + *i;
    *max = 0;
    for (i = each + 3; i >= each; i--)
        *max = *max * 10 + *i;
}

2、蒙特卡洛算法

原理过程
1.首先,我们定义了一个宏NUM_POINTS,表示要生成的随机点的数量。这个值越大,估计的精度越高,但计算时间也越长。
2.我们使用rand()函数生成两个0到1之间的随机数,分别表示点的x和y坐标。
3.判断这个点是否在单位圆内。如果点到原点的距离小于等于1,那么它就在单位圆内。这里我们使用了勾股定理来计算距离:sqrt(x^2 + y^2) <= 1。
4.如果点在单位圆内,我们将points_inside_circle计数器加1。
5.最后,我们用落在单位圆内的点数除以总点数,然后乘以4,得到π的估计值。这是因为单位正方形的面积是1,而单位圆的面积是π/4,所以落在圆内的点数与总点数的比例应该接近π/4。乘以4后,我们就得到了π的估计值。
算法实验
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define NUM_POINTS 1000000

int main() {
    int points_inside_circle = 0;
    double x, y;

    srand(time(NULL)); // 初始化随机数生成器

    for (int i = 0; i < NUM_POINTS; i++) {
        x = (double)rand() / RAND_MAX; // 生成0到1之间的随机数
        y = (double)rand() / RAND_MAX; // 生成0到1之间的随机数

        if (x * x + y * y <= 1) {
            points_inside_circle++; // 如果点在单位圆内,计数器加1
        }
    }

    double pi_estimate = 4.0 * points_inside_circle / NUM_POINTS;
    printf("Estimated value of Pi: %f\n", pi_estimate);

    return 0;
}

3、冰雹猜想验证(验证范围1~100)

原理过程
代码实现
#include <stdio.h>

void collatz(int n) {
    if (n == 1) {
        return;
    } else if (n % 2 == 0) {
        collatz(n / 2);
    } else {
        collatz(3 * n + 1);
    }
}

int main() {
    for (int i = 1; i <= 100; i++) {
        printf("Number: %d\n", i);
        collatz(i);
        printf("\n");
    }
    return 0;
}

4、亲和数验证(验证范围1~10000)

原理过程
复制代码
代码实现
c 复制代码
#include <stdio.h>

int sum_of_divisors(int n) {
    int sum = 1;
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            sum += i;
            if (i != n / i) {
                sum += n / i;
            }
        }
    }
    return sum;
}

int main() {
    for (int a = 1; a <= 10000; a++) {
        int b = sum_of_divisors(a);
        if (b > a && sum_of_divisors(b) == a) {
            printf("Amicable numbers: %d and %d\n", a, b);
        }
    }
    return 0;
}

实验二、MPI分布式运行框架搭建

1、前置操作

1.1、静态IP配置

/etc/netplan/文件下的网络配置文件

yaml 复制代码
network:
  version: 2
  ethernets:
    ens33:  # 请将 ens33 替换为您的实际接口名称
      dhcp4: no
      addresses:
        - 192.168.60.181/24  # 静态 IP 地址和子网掩码
      routes:
        - to: default
          via: 192.168.60.2  # 默认网关地址
      nameservers:
        addresses:
          - 8.8.8.8
          - 8.8.4.4  # DNS 服务器地址

需要改正的参数

  • enp0s3:使用ip a查看网卡地址
  • addresse:改变需要配置的IP地址
  • via:更改网关

刷新配置

sudo netplan apply

我的配置

01-netcfg.yaml

yaml 复制代码
network:
  version: 2
  ethernets:
    ens33:
      dhcp4: no
      addresses:
        - 192.168.60.181/24
      routes:
        - to: default
          via: 192.168.60.2
      nameservers:
        addresses:
          - 8.8.8.8
          - 8.8.4.4

动态IP的配置

yaml 复制代码
network:
    ethernets:
        ens33:
            dhcp4: true
    version: 2
1.2、虚拟机配置静态IP

vmware虚拟机设置静态ip并且通过xshell连接参考:https://www.cnblogs.com/qq1141100952com/p/15745106.html

1.3、构建互相免密钥访问(使用rsa方式生成)

构建四台计算节点之间互相免密钥访问(使用rsa方式生成)

先确定/etc/hosts文件配置了node1、node2、node3、node4的

192.168.60.181  node01
192.168.60.182  node02
192.168.60.183  node03
192.168.60.184  node04

构建秘钥

ssh-keygen -t rsa -b 2048

赋值秘钥到其他节点上

ssh-copy-id root@node2
ssh-copy-id root@node3
ssh-copy-id root@node4

我的

ssh-copy-id peng@node02
ssh-copy-id peng@node01
ssh-copy-id peng@node03

2、安装NFS服务器

https://blog.csdn.net/ssp584731180/article/details/131113101

2.1、安装 NFS 和 NFS Utils
bash 复制代码
sudo apt update
sudo apt install -y nfs-kernel-server nfs-common

在 node01 上配置 NFS 服务

创建共享目录 /var/mpi4.0/(如果不存在):

bash 复制代码
sudo mkdir -p /var/mpi4.0/

修改 NFS 配置文件,允许其他节点挂载该目录:

打开 /etc/exports 文件并添加如下内容:

bash 复制代码
/var/mpi4.0/ node02(rw,sync,no_subtree_check) node03(rw,sync,no_subtree_check) node04(rw,sync,no_subtree_check)

这行配置表示允许 node02node03node04 挂载该目录,并授予读写权限。

启动并使 NFS 服务开机自启

bash 复制代码
sudo systemctl enable --now nfs-server

应用配置

bash 复制代码
sudo exportfs -ra

验证共享

bash 复制代码
sudo exportfs -v
2.2、在其他节点配置NFS

创建挂载目录 /var/mpi4.0/(与主节点路径一致):

bash 复制代码
sudo mkdir -p /var/mpi4.0/

需要配置权限

bash 复制代码
sudo chmod 777 /var/mpi4.0

配置自动挂载

打开 /etc/fstab 文件,添加以下内容

bash 复制代码
192.168.1.1:/var/mpi4.0/ /var/mpi4.0/ nfs defaults 0 0

此行配置会将 node01/var/mpi4.0/ 目录挂载到本地的 /var/mpi4.0/,并设置开机自动挂载。

挂载目录

bash 复制代码
sudo mount -a

**验证挂载是否成功 **

bash 复制代码
df -h

应该能看到挂载在 /var/mpi4.0/ 的共享目录。

3、安装和测试MPI

3.1、安装MPI

下载MPI

wget https://www.mpich.org/static/downloads/4.0/mpich-4.0.tar.gz

解压

tar -xvf mpich-4.0.tar.gz

FCFLAGS=-fallow-argument-mismatch ./configure --prefix=/var/mpi4.0

./configure --prefix=/var/mpi4.0 --disable-fortran
make
sudo make install

配置环境变量

vi ~/.bashrc

# .bashrc
# User specific aliases and functions 
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
PATH="$PATH:/var/mpi4.0/bin"  # 新增路径
# Source global definitions
if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi

刷新

source ~/.bashrc

验证路径是否正确配置

which mpicc
which mpiexec

创建主机名称集合文件

/var/mpi4.0/bin 下创建一个 csmpd.hosts 文件,内容如下:

node01
node02
node03
node04

测试(我的在)

/var/mpi4.0/examples/ 目录下可以找到一些示例程序。编译并运行其中一个:

mpicc -o my_mpi_example /var/mpi4.0/examples/cpi.c

运行时指定主机文件 csmpd.hosts

mpiexec -np 4 -f /var/mpi4.0/bin/csmpd.hosts ./my_mpi_example

mpiexec -n 4 -f /var/mpi4.0/bin/csmpd.hosts /var/mpi4.0/code/hello
3.2、测试MPI
c 复制代码
/*hello.c*/
#include <stdio.h>
#include "mpi.h"

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

    MPI_Init( 0, 0 );
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    printf( "Hello world from process %d of %d\n", rank, size );
    MPI_Finalize();
    return 0;
}

编译

mpicc --o hello hello.c

运行

$mpiexec --n <processes> ./hello
$mpiexec -f csmpd.hosts --n <processes> ./hello

实验三、运行5.4、5.5、5.7、5.8

程序5.4

c 复制代码
/*文件名:who.c*/
#include "mpi.h"
#include <stdio.h>
int main(int argc,char **argv)
{
  	int myid, numprocs;
  	int namelen;
  	char processor_name[MPI_MAX_PROCESSOR_NAME];
  	MPI_Init(&argc,&argv);
  	MPI_Comm_rank(MPI_COMM_WORLD,&myid);//获得本进程ID
  	MPI_Comm_size(MPI_COMM_WORLD,&numprocs);//获得总的进程数目
  	MPI_Get_processor_name(processor_name,&namelen);//获得本进程的机器名
  	printf("Hello World! Process %d of %d on %s\n",myid, numprocs, processor_name);
  	MPI_Finalize();
}

程序5.5

c 复制代码
/*文件名:message.c*/
#include <stdio.h>
#include "mpi.h"
int main(int argc, char** argv)
{ 
     int myid, numprocs, source;
     MPI_Status status;
     char message[100];
     MPI_Init(&argc, &argv);
     MPI_Comm_rank(MPI_COMM_WORLD, &myid);
     MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
     if (myid != 0)
     {
           strcpy(message, "Hello World!");//为发送字符串赋值
           //发送字符串时长度要加1,从而包括串结束标志
           MPI_Send(message,strlen(message)+1, MPI_CHAR, 0,99,MPI_COMM_WORLD);
     }
     else
     {
           //除0进程的其他进程接收来自于0进程的字符串数据
           for (source = 1; source < numprocs; source++)
          {
                 MPI_Recv(message, 100, MPI_CHAR, source, 99,MPI_COMM_WORLD, &status);
                 printf("I am process %d. I recv string '%s' from process %d.\n", myid, message,source);
          }
     }
     MPI_Finalize();
}

程序5.7

c 复制代码
/*文件名 inte.c*/
#define N 100000000
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "mpi.h"
int main(int argc, char** argv)
{
  int myid,numprocs;
  int i;
  double local=0.0;
  double inte,tmp=0.0,x;
  MPI_Init(&argc, &argv);
  MPI_Comm_rank(MPI_COMM_WORLD, &myid);
  MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
  srand((int)time(0));//设置随机数种子
/*各节点分别计算一部分积分值*/
/*以下代码在不同节点运行的结果不同*/
  for(i=myid;i<N;i=i+numprocs)
  {
    x=10.0*rand()/(RAND_MAX+1.0);//求函数值
    tmp=x*x/N;
    local=tmp+local;//各节点计算面积和
  }	
//计算总的面积和,得到积分值
  MPI_Reduce(&local,&inte,1,MPI_DOUBLE,MPI_SUM,0,MPI_COMM_WORLD);
  if(myid==0)
  {
    printf("The integal of x*x=%16.15f\n",inte);
  }
  MPI_Finalize();
}

程序5.8

c 复制代码
/*文件名 myreduce.c*/
#define N 100000000
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "mpi.h"
void Myreduce(double *sendbuf, double *recvbuf,int count,int root);//定义自己的reduce函数
int main(int argc, char** argv)
{
  int myid,numprocs;
  int i;
  double local=0.0;
  double inte,tmp=0.0,x;
  MPI_Init(&argc, &argv);
  MPI_Comm_rank(MPI_COMM_WORLD, &myid);
  MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
/*采用归约对y=x*x在[1,10]区间求积分*/
  srand((int)time(0));
  for(i=myid;i<N;i=i+numprocs)
  {
    x=10.0*rand()/(RAND_MAX+1.0);
    tmp=x*x/N;
    local=tmp+local;
  }
  Myreduce(&local,&inte,1,0);//调用自定义的规约函数
  if(myid==0)
  {
    printf("The integal of x*x=%16.15f\n",inte);
  }
  MPI_Finalize();
}
/*自定义的归约函数,sendbuf为发送缓冲区,recvbuf为接收缓冲区,count为数据个数,root为指定根节点*/
/*该函数实现归约求和的功能*/
void Myreduce(double *sendbuf,double *recvbuf,int count,int root)
{
  MPI_Status status;	
  int i;
  int myid,numprocs;
  *recvbuf=0.0;
  MPI_Comm_rank(MPI_COMM_WORLD, &myid);
  MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
  double *tmp;
  //非root节点向root节点发送数据
  if(myid!=root)
  {
     MPI_Send(sendbuf,count,MPI_DOUBLE,root,99,MPI_COMM_WORLD);
  }
  //root节点接收数据并对数据求和,完成规约操作
  if(myid==root)
  {
     *recvbuf=*sendbuf;
     for(i=0;i<numprocs;i++)
     {
        if(i!=root)
        {
          MPI_Recv(tmp,count,MPI_DOUBLE,i,99,MPI_COMM_WORLD,&status); 
          *recvbuf=*recvbuf+*tmp;
        }
     }
  }
}

实验四、卡布列克常数的MPI测试

思路

初始化 MPI 环境 :使用 MPI_Init 初始化 MPI 环境,并获取每个节点的 rank 和总节点数 size

分配任务 :根据 ranksize 将四位数区间分割给不同的节点。比如,如果是 4 个节点,可以让每个节点负责一个子区间。

卡布列克运算

  • 将当前数字分解成 4 位数的数组。
  • 排序并生成最大数和最小数。
  • 计算两者的差值,如果差值不为 6174,重复操作,直到达到 6174 或步骤达到上限。

输出结果:当一个数达到了 6174,打印步骤,结果,以及这个数所需的步骤数。

汇总与退出:所有节点完成计算后,MPI 程序结束,释放资源。

代码

kaprekar_mpi.c

c 复制代码
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TARGET 6174

// 对数字分解成千位、百位、十位和个位
void split_digits(int num, int digits[4]) {
    digits[0] = num / 1000;
    digits[1] = (num / 100) % 10;
    digits[2] = (num / 10) % 10;
    digits[3] = num % 10;
}

// 排序数字数组
void sort_digits(int digits[4], int ascending) {
    for (int i = 0; i < 3; i++) {
        for (int j = i + 1; j < 4; j++) {
            if ((ascending && digits[i] > digits[j]) || (!ascending && digits[i] < digits[j])) {
                int temp = digits[i];
                digits[i] = digits[j];
                digits[j] = temp;
            }
        }
    }
}

// 生成最大和最小数字
int form_number(int digits[4]) {
    return digits[0] * 1000 + digits[1] * 100 + digits[2] * 10 + digits[3];
}

// 卡布列克猜想的核心算法
int kaprekar_routine(int num) {
    int steps = 0;
    while (num != TARGET && steps < 7) {
        int digits[4];
        split_digits(num, digits);
        
        sort_digits(digits, 0); // 降序排列
        int large = form_number(digits);
        
        sort_digits(digits, 1); // 升序排列
        int small = form_number(digits);
        
        num = large - small;
        steps++;
    }
    return steps;
}

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

    int rank, size;
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    int start = 1000 + (9000 / size) * rank;
    int end = (rank == size - 1) ? 9999 : start + (9000 / size) - 1;

    printf("Node %d processing range %d to %d\n", rank, start, end);

    for (int i = start; i <= end; i++) {
        if (i % 1111 == 0) continue;  // 排除全相同的数

        int steps = kaprekar_routine(i);
        if (steps <= 7) {
            printf("Node %d: %d reaches 6174 in %d steps\n", rank, i, steps);
        }
    }

    MPI_Finalize();
    return 0;
}
  • split_digits:将数字分解为千、百、十、个位。
  • sort_digits:对数字数组进行排序,用于生成最大和最小数字。
  • form_number:将分解后的数字数组重新组合成整数。
  • kaprekar_routine:核心算法,计算每个四位数到达 6174 所需的步骤数。
  • MPI_Comm_rankMPI_Comm_size:获取当前节点的编号和节点总数,以分配任务。

运行

  1. 先安装 MPI(例如使用 mpichopenmpi)。

  2. 编译代码:

    mpicc -o kaprekar_mpi kaprekar_mpi.c
    
  3. 运行代码(假设使用 4 个节点):

    mpirun -np 4 ./kaprekar_mpi
    

实验五、基于蒙特卡洛算法求π值的MPI程序设计

思路

代码

以下代码实现了基于蒙特卡洛方法估算 π 值的 MPI 并行程序。程序将随机点生成和圆内点的计数分配给多个进程,每个进程独立计算局部结果,并将其汇总到主进程以得到最终 π 值。

c 复制代码
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define TOTAL_POINTS 1000000  // 每个进程要生成的总点数,可以根据需要调整

int main(int argc, char **argv) { 
    int myid, numprocs;
    long count = TOTAL_POINTS;
    long local_m = 0, global_m = 0;  // 局部和全局的圆内点数
    double x, y;
    double local_pi, pi_estimate;

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &myid);  // 获取当前进程的进程号
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);  // 获取通信域中的总进程数

    srand(time(NULL) + myid);  // 设置随机种子,确保每个进程生成不同随机序列

    // 每个进程生成随机点并统计在单位圆内的点数
    for (long i = 0; i < count; i++) {
        x = (double)rand() / RAND_MAX;
        y = (double)rand() / RAND_MAX;
        if ((x * x + y * y) <= 1) {
            local_m++;
        }
    }

    // 计算局部 π 值
    local_pi = 4.0 * local_m / count;
    printf("Process %d of %d on pi = %f\n", myid, numprocs, local_pi);

    // 使用 MPI_Reduce 汇总所有进程的圆内点数
    MPI_Reduce(&local_m, &global_m, 1, MPI_LONG, MPI_SUM, 0, MPI_COMM_WORLD);

    // 主进程计算总 π 值
    if (myid == 0) {
        pi_estimate = 4.0 * global_m / (count * numprocs);
        printf("Estimated value of π: %f\n", pi_estimate);
    }

    MPI_Finalize();
    return 0;
}
  1. 设置随机数 :每个进程通过 srand 设置不同的随机数种子,以确保生成的随机点不同。
  2. 蒙特卡洛方法计算 π 值
    • 每个进程独立生成 TOTAL_POINTS 个随机点 (x, y)
    • 判断点是否在单位圆内,并统计圆内点数 local_m
  3. MPI 汇总
    • 使用 MPI_Reduce 将各进程的 local_m 汇总到 global_m
  4. 结果计算
    • 主进程 (myid == 0) 使用汇总的圆内点数计算 π 的近似值,并打印结果。

运行

将代码保存为 mtpi.c,然后在终端中进行编译和运行:

bash 复制代码
# 编译代码
mpicc -o mtpi mtpi.c

# 使用 4 个进程运行
mpirun -np 4 ./mtpi

实验六、使用 MPI 技术验证角谷猜想

思路

角谷猜想(Collatz conjecture)是一个数学猜想,对任何一个正整数 n,若 n 为偶数,则把它除以 2,若 n 为奇数,则把它乘以 3 再加 1,得到一个新的数。对新的数重复上述步骤,最终总会得到 1。

代码

c 复制代码
#include <mpi.h>
#include <stdio.h>

void verify_collatz(long n) {
    while (n != 1) {
        if (n % 2 == 0) {
            n = n / 2;
        } else {
            n = 3 * n + 1;
        }
    }
}

int main(int argc, char **argv) {
    int myid, numprocs;
    long start, end, range;
    long i;

    // 设置验证数据的范围
    long min_range = 1;
    long max_range = 100000;  // 可根据需求增大此数值,验证更大范围的数

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &myid);    // 获取当前进程号
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs); // 获取进程总数

    // 将范围划分给各个进程
    range = (max_range - min_range + 1) / numprocs;
    start = min_range + myid * range;
    end = (myid == numprocs - 1) ? max_range : start + range - 1;

    printf("Process %d is verifying numbers from %ld to %ld\n", myid, start, end);

    // 遍历每个数并验证角谷猜想
    for (i = start; i <= end; i++) {
        verify_collatz(i);
    }

    // 所有进程完成计算后输出结果
    if (myid == 0) {
        printf("Verification of the Collatz conjecture from %ld to %ld completed.\n", min_range, max_range);
    }

    MPI_Finalize();
    return 0;
}

verify_collatz 函数执行角谷猜想的验证过程。

主程序中,各个 MPI 进程会分配到一个范围的数字,分别验证这些数字是否满足角谷猜想。

各进程完成计算后,主进程(myid == 0)输出验证完成的消息。

运行

编译代码

bash 复制代码
mpicc -o collatz collatz.c

运行代码

bash 复制代码
mpirun -np 4 ./collatz

实验七、使用 MPI 技术验证亲和数组合的个数

思路

亲和数组合指的是满足一定条件的数对或数集合。这里的实验将并行计算所有可能的亲和数组合数量,通过划分数据范围来分配到不同的计算节点,并验证这些组合满足的特定亲和条件。

代码

c 复制代码
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

// 判断是否为亲和数组合
int is_amicable(int a, int b) {
    int sum_a = 0, sum_b = 0;
    for (int i = 1; i <= a / 2; i++) {
        if (a % i == 0) sum_a += i;
    }
    for (int i = 1; i <= b / 2; i++) {
        if (b % i == 0) sum_b += i;
    }
    return (sum_a == b && sum_b == a);
}

int main(int argc, char **argv) {
    int myid, numprocs;
    long range_start, range_end, total_pairs = 0, local_pairs = 0;
    long range = 100000;  // 大范围以延长运行时间

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &myid);    // 获取当前进程号
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs); // 获取进程总数

    // 划分数据范围
    long range_per_proc = range / numprocs;
    range_start = myid * range_per_proc + 1;
    range_end = (myid == numprocs - 1) ? range : range_start + range_per_proc - 1;

    printf("Process %d is calculating from %ld to %ld\n", myid, range_start, range_end);

    // 计算亲和数组合数量
    for (long a = range_start; a <= range_end; a++) {
        for (long b = a + 1; b <= range; b++) {
            if (is_amicable(a, b)) {
                local_pairs++;
            }
        }
    }

    // 汇总所有进程的结果
    MPI_Reduce(&local_pairs, &total_pairs, 1, MPI_LONG, MPI_SUM, 0, MPI_COMM_WORLD);

    // 主进程输出结果
    if (myid == 0) {
        printf("Total number of amicable pairs in the range: %ld\n", total_pairs);
    }

    MPI_Finalize();
    return 0;
}

is_amicable 函数用于判断两个数是否为亲和数组合。若数 a 的因子和等于 b,且数 b 的因子和等于 a,则这两个数为亲和数组合。

主程序中,每个 MPI 进程被分配到一段范围来检查亲和数组合。

最终,通过 MPI_Reduce 汇总各进程的亲和数组合总数。

为了确保运行时间超过 20 分钟,可以适当调大 range 的值(例如,增加至 500000 或更大),从而增加计算复杂度和运行时间。

运行

编译代码

mpicc -o amicable_pairs amicable_pairs.c

运行代码

mpirun -np 4 ./amicable_pairs

使用Docker搭建并行计算环境

1、拉取镜像

docker pull ubuntu:22.04

2、启动镜像

docker run -d --name node1 -p 30001:22 ubuntu:22.04 sleep infinity
docker run -d --name node2 -p 30001:22 ubuntu:22.04 sleep infinity
docker run -d --name node3 -p 30001:22 ubuntu:22.04 sleep infinity
docker run -d --name node4 -p 30001:22 ubuntu:22.04 sleep infinity

查看运行中的容器

docker ps

进入节点

docker exec -it node1 /bin/bash

3、安装相关软件

安装ssh服务

apt install -y openssh-server
service ssh start

确保主机上的防火墙没有阻止访问映射的端口(如 2222)。你可以使用以下命令查看防火墙状态:

  • 对于 iptables

    iptables -L
    
  • 对于 ufw(如果使用):

    ufw status
    

如果端口被阻止,可以添加规则以允许访问:

ufw allow 2222

确保 SSH 配置文件 /etc/ssh/sshd_config 中允许使用密码进行身份验证:

PermitRootLogin yes
PasswordAuthentication yes

修改后,重启 SSH 服务以使更改生效:

service ssh restart

修改ubuntu的密码,修改密码后然后就可以使用xshell登录了。

passwd root

安装vim【可选】

apt install vim

安装net-tools【可选】

sudo apt install net-tools

安装iproute2【可选】

apt install -y iproute2

安装curl和wget【可选】

apt install -y curl
apt install -y wget

4、使用XShell连接

使用XShell批量连接节点

相关推荐
hjjdebug40 分钟前
linux 下 signal() 函数的用法,信号类型在哪里定义的?
linux·signal
其乐无涯41 分钟前
服务器技术(一)--Linux基础入门
linux·运维·服务器
Diamond技术流42 分钟前
从0开始学习Linux——网络配置
linux·运维·网络·学习·安全·centos
斑布斑布1 小时前
【linux学习2】linux基本命令行操作总结
linux·运维·服务器·学习
Spring_java_gg1 小时前
如何抵御 Linux 服务器黑客威胁和攻击
linux·服务器·网络·安全·web安全
✿ ༺ ོIT技术༻1 小时前
Linux:认识文件系统
linux·运维·服务器
会掉头发1 小时前
Linux进程通信之共享内存
linux·运维·共享内存·进程通信
我言秋日胜春朝★1 小时前
【Linux】冯诺依曼体系、再谈操作系统
linux·运维·服务器
饮啦冰美式2 小时前
22.04Ubuntu---ROS2使用rclcpp编写节点
linux·运维·ubuntu