高性能计算:使用MPI多进程并行求解N皇后,并按进程顺序输出摆放方案
实验环境 linux 这里我使用了超算习堂的云主机https://easyhpc.net/personal-computer
0. N皇后问题并行算法说明:
在N×N格的国际象棋上摆放N个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法?
要求:要求N>8,进(线)程数P取等于N,小于N,大于N等不同情况。所有进(线)程依次将满足条件的排法个数输出。进(线)程0先输出,进(线)程1再输出,进(线)程P-1最后输出。
- 更新软件包列表
sudo apt-get update - 配置基础编译环境
sudo apt-get install -y build-essential
下载与安装完成后,可键入下面的命令观察gcc版本信息。
gcc -v
- MPICH的安装
sudo apt-get install -y mpich
mpicc -v
- 环境搭建完成,编写代码如下:
N皇后问题的并行算法求解,算法思路参考了> 八皇后问题mpi求解方案> 建议对Nqueen不了解的同学去看看这篇。
,为了满足多个进程顺序输出其搜索到的方案的要求,其前面的连接基础上,我的代码加入了阻塞通讯,构造了一个ostringstram oss保存单个进程搜索到的方案数, 可以使得多个进程之间进行通讯。进程1将oss输出后,向下一进程send消息,下一进程会堵塞,知道接收到上一进程的消息后(也就是上一进程完成了输出),输出自己的方案并且自己发送消息到自己的下一个进程。
- C++实现
点击查看代码
#include <chrono>
#include <cstring>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>
#ifndef MPICH_SKIP_MPICXX
#define MPICH_SKIP_MPICXX
#endif
#include <mpi.h>
using std::cout;
using std::endl;
using std::clog;
using std::ofstream;
using std::ifstream;
using std::ostringstream;
using std::string;
int N = 8;
double start, end;
void outPut(const int &size, int *array, ostringstream &file) //输出到文件
{
int myid;
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
file << "myId: " << myid << ":";
for (int i = 0; i < size; i++)
{
file << array[i];
}
file << "\n";
}
bool isContact(const int &deep, const int *const &array) //判断对角线上是否有冲突
{
int temp;
for (int i = 1; i < deep + 1; i++)
{
temp = array[deep - i];
if (array[deep] - i == temp || array[deep] + i == temp) //这条语句完成判断
return true;
}
return false;
}
void range(const int &size, const int &deep, int *const &flags, int *&array, int &count, ostringstream &file) //进行递归推导
{
for (int i = 0; i < size; i++) //从第到第个,判断是否还有没用过并且没有冲突的数据
{
if (!flags[i]) //判断是否被用过,这里使用的是按内容寻址的方式
{
array[deep] = i; //如果i没有被使用过,那么现在使用i
if (deep != 0) //不是第一行的元素要判断对角线上是否有冲突
{
if (isContact(deep, array)) //判断对角线是否有冲突,主要deep是层次
continue; //如果有冲突,继续循环,寻找下一个试探点
}
flags[i] = 1; //目前第i个点可用,这里进行标记,第i个点已经被占用了
if (deep == size - 1) //当深度为,就是找到了一个序列,满足八皇后要求了
{
outPut(size, array, file); //将结果输出到文件
count++; //次数加一
}
else //没有找全所有的棋子
{
range(size, deep + 1, flags, array, count, file); //进一步递归调用,完成没完成的棋子
array[deep] = -1; //递归回来,要恢复原状,以备下次继续递归到新的序列
}
flags[i] = 0; //恢复标志
}
}
}
void mpi_range(const int &size, int *&flags, int *&array, const int &myId) //mpi开启递归调用
{
flags = new int[N]; //两个数据结构,flags用来存储位置是否被占用信息
array = new int[N]; //存储第i行的棋子放的位置
memset(flags, 0, sizeof(int) * N); //赋初值
memset(array, -1, sizeof(int) * N);
flags[myId] = 1; //第i个进程执行第一行为i的计算
array[0] = myId;
int count = 0; //计数为
int totalCount = 0; //总计数为
ostringstream file;
range(N, 1, flags, array, count, file); //开始递归
MPI_Reduce(&count, &totalCount, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); //规约所有递归结构
if (myId == 0) //主进程输出计算结果
{
MPI_Recv(&deley, 1, MPI_INT, size - 1, 0, MPI_COMM_WORLD, &status2);
end = MPI_Wtime();
cout << "totalCount: " << totalCount << endl;
cout << "Runtime =:" << end - start << endl;
}
MPI_Status status;
int deley = 0;
if (myId == 0)
{
cout << file.str();
// cout<<myId;
MPI_Send(&deley, 1, MPI_INT, myId + 1, 0, MPI_COMM_WORLD);
}
if (myId >= 1)
{
MPI_Recv(&deley, 1, MPI_INT, myId - 1, 0, MPI_COMM_WORLD, &status);
cout << file.str();
// cout<<myId;
if (myId + 1 <= size)
{
MPI_Send(&deley, 1, MPI_INT, (myId + 1) % size, 0, MPI_COMM_WORLD);
}
}
}
int main(int argc, char *argv[])
{
int size;
int myId;
MPI_Init(&argc, &argv); //初始化mpi
start = MPI_Wtime();
MPI_Comm_size(MPI_COMM_WORLD, &size); //获取开启的进程数量
N = size;
MPI_Comm_rank(MPI_COMM_WORLD, &myId); //获取当前进程的id号
int *flags; //标记
int *array; //存放棋盘次序
mpi_range(N, flags, array, myId); //开始递归计算
MPI_Finalize(); //计算终止
return 0;
}
- 编译
mpixx ./Nqueen.cpp -o ./Nqueen
- 运行
mpirun -np 8 ./Nqueen
- 运行结果