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批量连接节点

相关推荐
数据与人工智能律师9 分钟前
数字资产革命中的信任之锚:RWA法律架构的隐形密码
大数据·网络·人工智能·云计算·区块链
即将头秃的程序媛1 小时前
centos 7.9安装tomcat,并实现开机自启
linux·运维·centos
fangeqin1 小时前
ubuntu源码安装python3.13遇到Could not build the ssl module!解决方法
linux·python·ubuntu·openssl
爱奥尼欧3 小时前
【Linux 系统】基础IO——Linux中对文件的理解
linux·服务器·microsoft
超喜欢下雨天3 小时前
服务器安装 ros2时遇到底层库依赖冲突的问题
linux·运维·服务器·ros2
tan77º4 小时前
【Linux网络编程】网络基础
linux·服务器·网络
笑衬人心。4 小时前
Ubuntu 22.04 + MySQL 8 无密码登录问题与 root 密码重置指南
linux·mysql·ubuntu
chanalbert6 小时前
CentOS系统新手指导手册
linux·运维·centos
国际云,接待6 小时前
微软服务器安全问题
运维·服务器·云原生·云计算·azure
星宸追风6 小时前
Ubuntu更换Home目录所在硬盘的过程
linux·运维·ubuntu