mpi第四站 - 利用MPI_Send,MPI_Recv和MPI_Probe模拟美国大片“雷神”中的彩虹桥

版权声明:博主原创文章属私人所有,未经允许 不可转发和使用 https://blog.csdn.net/a1066196847/article/details/89681871

main.cpp 

#include <iostream>
#include <vector>
#include <cstdlib>
#include <time.h>
#include <mpi.h>

using namespace std;

using namespace std;

typedef struct {
    int location; //当前在哪个位置
    int num_steps_left_in_walk; //要走多少步
} Walker;

// 函数目的:根据传进来的world_rank进程号,然后得到每个星域的起点、每个星域的长度
// 并且初始化到 subdomain_start subdomain_size 中
void decompose_domain(int domain_size, int world_rank,
                      int world_size, int* subdomain_start,
                      int* subdomain_size) {
    // domain_size是100,world_size不能大于这个
    if (world_size > domain_size) {
        MPI_Abort(MPI_COMM_WORLD, 1);
    }
    // domain_size就是整个的长度100
    // world_size 是程序启动的总进程数,相当于介绍中的5
    // domain_size / world_size是一个"星域"的长度,再乘以world_rank,就是这个星域的起点
    *subdomain_start = domain_size / world_size * world_rank;
    *subdomain_size = domain_size / world_size;
    // 当整个长度不能够平均分的话,就把多余的给最后一个"星域"
    if (world_rank == world_size - 1) {
        *subdomain_size += domain_size % world_size;
    }
}

// 函数目的:每个进程都有num_walkers_per_proc(20)个walker,在这个函数里面初始化,location都是同一个起点
// 但是num_steps_left_in_walk是不同的(随机赋一个值 0~500),因为不管是哪个world_rank都会有20个walkers,
// 所以不用传参数 world_rank
// 然后把这20个walker放到incoming_walkers中
void initialize_walkers(int num_walkers_per_proc, int max_walk_size,
                        int subdomain_start,
                        vector<Walker>* incoming_walkers) {
    Walker walker;
    for (int i = 0; i < num_walkers_per_proc; i++) {
        // 首先把当前进程的起点,也就是"星域"的起点,赋值给walker.location
        walker.location = subdomain_start;
        // 然后生成一个 0~max_walk_size 之间的一个数字,也就是 0~500间的一个数字
        // 也就是这个walker将要走的一个步长
        walker.num_steps_left_in_walk =
                (rand() / (float)RAND_MAX) * max_walk_size;
        // 将这个walker放在 存储将来进行的游走队列中
        incoming_walkers->push_back(walker);
    }
}

// 函数功能:传进来一个walker,然后让这个wakler走到尽头
// subdomain_start: 一个星域的起点
// subdomain_size: 这个星域的长度
// domain_size: 所有星域的总长度
void walk(Walker* walker, int subdomain_start, int subdomain_size,
          int domain_size, vector<Walker>* outgoing_walkers) {
    // 根据传进来的参数,num_steps_left_in_walk的大小在 0~499,代表意义是这个walker最大走的"步数",一次可以走1

    // 刚开始的时候location、subdomain_start大小是一样的,但是walker要往前走才行,走到当前星域的最后1位就可以了
    // 假设location和subdomain_start刚开始都是0,第一个星域的大小是4,等到walker走到 0+4=4,也就是当前星域的最后1位
    while (walker->num_steps_left_in_walk > 0) {
        if (walker->location == subdomain_start + subdomain_size) {
            // Take care of the case when the walker is at the end
            // of the domain by wrapping it around to the beginning
            if (walker->location == domain_size) {
                walker->location = 0;
            }
            // 只有能走到的才会放到outgoing_walkers中,也就是说可能20个wakler只有18个能走到
            outgoing_walkers->push_back(*walker);
            break;
        } else {
            // 走1步时,如果不满足 到达 -> 当前星域的最后1位
            // 先给能走的步数减1,再给location加1(也就是walker的当前位置)
            walker->num_steps_left_in_walk--;
            walker->location++;
        }
    }
}

void send_outgoing_walkers(vector<Walker>* outgoing_walkers,
                           int world_rank, int world_size) {
    // 把这些数据当做 MPI_BYTEs类型的数组,传送给下一个进程。最后一个进程传给0进程
    // (void*)outgoing_walkers->data() -> 这些数据要进行传送
    // outgoing_walkers->size() * sizeof(Walker) -> 这是数据的大小
    // MPI_BYTE -> 数据类型
    // (world_rank + 1) % world_size -> 要传给哪个进程
    // 0 -> 传送的数据tag
    // MPI_COMM_WORLD -> 环境变量
    MPI_Send((void*)outgoing_walkers->data(),
             outgoing_walkers->size() * sizeof(Walker), MPI_BYTE,
             (world_rank + 1) % world_size, 0, MPI_COMM_WORLD);

    // 释放内存
    outgoing_walkers->clear();
}


void receive_incoming_walkers(vector<Walker>* incoming_walkers,
                              int world_rank, int world_size) {
    MPI_Status status;

    // Receive from the process before you. If you are process zero,
    // receive from the last process
    // incoming_rank -> 当前进程要接送哪个进程传来的数据
    // 0 -> 要传来的数据的tag
    // MPI_COMM_WORLD -> 环境变量
    int incoming_rank =
            (world_rank == 0) ? world_size - 1 : world_rank - 1;
    MPI_Probe(incoming_rank, 0, MPI_COMM_WORLD, &status);

    // Resize your incoming walker buffer based on how much data is
    // being received
    int incoming_walkers_size;
    MPI_Get_count(&status, MPI_BYTE, &incoming_walkers_size);
    incoming_walkers->resize(
            incoming_walkers_size / sizeof(Walker));
    MPI_Recv((void*)incoming_walkers->data(), incoming_walkers_size,
             MPI_BYTE, incoming_rank, 0, MPI_COMM_WORLD,
             MPI_STATUS_IGNORE);
}


int main(int argc, char** argv) {
    int domain_size;
    int max_walk_size;
    int num_walkers_per_proc;

    if (argc < 3) {
        cerr << "Usage: random_walk domain_size max_walk_size "
             << "num_walkers_per_proc" << endl;
        exit(1);
    }
    domain_size = atoi(argv[1]);    //所有星域的长度 -- 100
    max_walk_size = atoi(argv[2]);  //一个walker运行的最大步长 -- 500
    num_walkers_per_proc = atoi(argv[3]); //每一个进程需要启动多少个walker -- 20

    MPI_Init(NULL, NULL);
    int world_size;
    MPI_Comm_size(MPI_COMM_WORLD, &world_size);
    int world_rank;
    MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);

    srand(time(NULL) * world_rank);
    int subdomain_start, subdomain_size;
    vector<Walker> incoming_walkers, outgoing_walkers;

    // 得到"当前world_rank"的星域的起点,星域的长度
    decompose_domain(domain_size, world_rank, world_size,
                     &subdomain_start, &subdomain_size);

    // 函数目的:每个进程都有num_walkers_per_proc个walker,在这个函数里面初始化,把当前进程的
    // 所有walker放在incoming_walkers中,代表即将到来的walkers
    initialize_walkers(num_walkers_per_proc, max_walk_size, subdomain_start,
                       &incoming_walkers);

    // 当前是哪个进程?这个进程有多少个walkers(这都是固定的,在传参中已有)?
    // 当前进程的walkers所在区间是什么 subdomain_start ~ (subdomain_start + 星域长度)-1
    // 所在区间也就是所在星域的区间
    cout << "Process " << world_rank << " initiated " << num_walkers_per_proc
         << " walkers in subdomain " << subdomain_start << " - "
         << subdomain_start + subdomain_size - 1 << endl;

    // domain_size / world_size -> 一个星域的长度
    // 500 / 20 -> 25 +1 -> 26   可能路过的最大星域的数目
    int maximum_sends_recvs = max_walk_size / (domain_size / world_size) + 1;
    for (int m = 0; m < maximum_sends_recvs; m++) {
        // 处理所有的walkers,让他们走到当前星域的最后1位
        for (int i = 0; i < incoming_walkers.size(); i++) {
            walk(&incoming_walkers[i], subdomain_start, subdomain_size,
                 domain_size, &outgoing_walkers);
        }
        cout << "Process " << world_rank << " sending " << outgoing_walkers.size()
             << " outgoing walkers to process " << (world_rank + 1) % world_size
             << endl;

        // 如果是偶数的进程 就先发送再接受
        if (world_rank % 2 == 0) {
            // Send all outgoing walkers to the next process.
            send_outgoing_walkers(&outgoing_walkers, world_rank,
                                  world_size);
            // Receive all the new incoming walkers
            receive_incoming_walkers(&incoming_walkers, world_rank,
                                     world_size);
        } else {
            // Receive all the new incoming walkers
            receive_incoming_walkers(&incoming_walkers, world_rank,
                                     world_size);
            // Send all outgoing walkers to the next process.
            send_outgoing_walkers(&outgoing_walkers, world_rank,
                                  world_size);
        }
        cout << "Process " << world_rank << " received " << incoming_walkers.size()
             << " incoming walkers" << endl;
    }
    cout << "Process " << world_rank << " done" << endl;
    MPI_Finalize();
    return 0;
}

执行的时候,第二个参数如果设置的小于每个星域的长度,那么整个程序会很快停止下来,因为每一批wakler从一个星域到下一个星域的时候,能走的最大数目连星域长度都不够,肯定就只能传过去0个,所以很快结束

有兴趣的朋友可以试试这两种参数导致的不同结果

mpicxx main.cpp -o main.out

mpirun -np 5 main.out 100 500 20

mpirun -np 5 main.out 100 5 20

打印出来的完全不一样,也可以利用这个来了解这个程序的逻辑

猜你喜欢

转载自blog.csdn.net/a1066196847/article/details/89681871
mpi