【並行計算】Circular-Shift Routing in Hypercube

QAQ:經過映射的Hypercube就不是Hypercube了。。尷尬
並行計算導論課作業,折騰了一下午,基本搞定了。
但是還有幾個問題沒有解決:

  • 有時候執行會莫名崩潰,在send和receive之間加入sleep後,就很少崩潰,這是爲什麼?
  • 我的i7 4700最多隻能跑1000多個進程,進程數過多磁盤,cpu使用率直接爆炸,電腦死機,程序運行崩潰。如何才能提高進程並行數?

用的MPI實現的多進程。

MPI+VS環境的搭建可以參考這篇轉載的文章:

http://blog.csdn.net/qq379548839/article/details/52770424


Circular-Shift Routing in Hypercube實現方法:

循環移位的實現首先需要對一般的hypercube的頂點重新編號。
具體的方法是:
將一般的hypercube頂點編號作爲格雷碼,將這個格雷碼轉換爲二進制編碼,這個二進制編碼即爲這個定點的新編號。
以上就是《並行計算導論》書中說的reflected gray code(RGC),中文翻譯:反射格雷碼。
對於這個概念我只能說,呵呵。我google了很久也沒有查到這個所謂的RGC到底是個什麼東西,最後我觀察規律才發現所謂的反射是上面這個意思。
然後將移位數二進制拆分,拆分成最多logp(p爲節點數)項進行移位。

然後,這樣編號和二進制拆分有兩個極大的好處:

  • 當移位是2^0時,每個節點只需要進行1次消息發送與接收;當移位不爲2^k(k!=0)時,每個節點只需要進行2次消息發送與接收
  • 在消息傳遞時,是無擁塞的

// MPI_Test.cpp : 定義控制檯應用程序的入口點。
//

#include "stdafx.h"
#include "stdafx.h"  
#include "mpi.h"  
#include <stdio.h>  
#include <math.h>  
#include <Windows.h>  
#include <iostream>
#include <ctime>

using namespace std;

int val;
int p, q;
int srcRelation[20], dstRelation[20];

void getPQ(char* argv[]) {//讀取p,q
    p = 0;
    int i = 0;
    while (argv[1][i] != '\0') {
        p = p * 10 + argv[1][i] - '0';
        i++;
    }

    q = 0;
    i = 0;
    while (argv[2][i] != '\0') {
        q = q * 10 + argv[2][i] - '0';
        i++;
    }
}

int grayToBit(int x) {//格雷碼轉二進制編碼
    int rt = x & (1 << (p - 1));
    bool last;
    if (rt != 0) last = 1;
    else last = 0;
    for (int i = p - 2; i >= 0; i--) {
        if (x & (1 << i)) last = 1 ^ last;
        else last = last ^ 0;
        rt |= last << i;
    }
    return rt;
}

int bitToGray(int x) {//二進制編碼轉格雷碼
    return (x >> 1) ^ x;
}

int getMidProcessor(int srcBit, int dstBit) {
    int w[20], num = 0;
    int status = srcBit ^ dstBit;
    for (int i = 0; i < p; i++) {
        if (status & (1 << i)) {
            w[num++] = i;
        }
    }
    int xBit = srcBit ^ (1 << w[0]);
    int yBit = srcBit ^ (1 << w[1]);
    //可以隨便取一個,一定不會阻塞
    return grayToBit(xBit);

}
int main(int argc, char* argv[])
{
    getPQ(argv);//讀取p,qint rank, processNum;

    //初始化MPI
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);//獲得進程號
    MPI_Comm_size(MPI_COMM_WORLD, &processNum);//返回通信子的進程數

    int srcGray = rank;
    int dstGray = (srcGray + (1 << q)) & ((1 << p) - 1);
    int srcBit = bitToGray(srcGray);
    int dstBit = bitToGray(dstGray);

    val = srcGray;//處理器初始數據=處理器編號

    int send[2], receive[2];

    if (q == 0) {
        send[0] = val;
        send[1] = -1;//位移爲1時,僅需要一次傳輸
        fprintf(stderr, "%d ---> %d\n", srcGray, dstGray);
        MPI_Send(send, 2, MPI_INT32_T, dstGray, 0, MPI_COMM_WORLD);
    }
    else {
        send[0] = val;
        send[1] = dstGray;
        int mid = getMidProcessor(srcBit, dstBit);
        //fprintf(stderr, "%d to %d acc %d\n", srcGray, dstGray, mid);
        //fprintf(stderr, "%d to %d = %d to %d acc %d\n", srcGray, dstGray, srcBit, dstBit, mid);
        fprintf(stderr, "%d ---> %d\n", srcGray, mid);
        MPI_Send(send, 2, MPI_INT32_T, mid, 0, MPI_COMM_WORLD);
    }

    int rd = 100;
    /*如何在運行時產生隨機數?
    srand(time(0));
    rd = rand() % 100;
    */
    Sleep(rd);//如果不sleep會崩潰?
    //fprintf(stderr, "------rand %d \n", rd);

    MPI_Recv(receive, 2, MPI_INT32_T, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
    int ndst = receive[1];
    int nval = receive[0];
    if (ndst != -1) {
        fprintf(stderr, "%d ---> %d\n", srcGray, ndst);
        MPI_Send(&nval, 1, MPI_INT32_T, ndst, 0, MPI_COMM_WORLD);
        MPI_Recv(&val, 1, MPI_INT32_T, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
    }
    else {
        val = nval;
    }

    fprintf(stderr, "process %d of value %d\n", srcGray, val);//輸出最終處理器數據
    //system("pause");
    MPI_Finalize();
    return 0;
}

運行指令:
mpiexec –n [2^p] MPI_test.exe [p] [q]
2^p表示節點數
q表示移位2^q
例如:mpiexec –n 16 MPI_test.exe 4 2



更新

經過確認,hypercube不只是一種結構,還是一種固定編號的結構,即必須滿足相鄰兩個節點只有一位二進制位不同。
所以我重新寫了一個方法,對於任意移位,可以在p步完成。
原理就是對於p個二進制位中的每一位,比較當前節點和當前節點存儲數據的目標節點的這一二進制位是否相同。相同則當前節點不進行消息傳遞,不同則當前節點的這一位取反,作爲目標節點進行消息傳遞。
很顯然,一個節點不會在一個階段中同時接收來自兩個節點的數據,所以不會出現擁塞。

#include "stdafx.h"
#include "mpi.h"  
#include <stdio.h>  
#include <math.h>  
#include <Windows.h>  
#include <iostream>
#include <ctime>

using namespace std;

int val;
int p, q;
int srcRelation[20], dstRelation[20];

void getPQ(char* argv[]) {//讀取p,q
    p = 0;
    int i = 0;
    while (argv[1][i] != '\0') {
        p = p * 10 + argv[1][i] - '0';
        i++;
    }

    q = 0;
    i = 0;
    while (argv[2][i] != '\0') {
        q = q * 10 + argv[2][i] - '0';
        i++;
    }
}

int main(int argc, char* argv[])
{
    getPQ(argv);//讀取p,q值, 2^p個處理器,移位q

    int rank, processNum;

    //初始化MPI
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);//獲得進程號
    MPI_Comm_size(MPI_COMM_WORLD, &processNum);//返回通信子的進程數

    int node = rank;//節點編號
    val = rank;//處理器初始數據=處理器編號

    int send[2], receive[2];

    if (q == 0) {//移位不能爲0
        MPI_Finalize();
        return 0;
    }

    int toNode;//目標節點
    int dstNode = (node + q) % (1 << p);//最終節點
    for (int i = 0; i < p; i++) {
        if ((node & (1 << i)) == (dstNode & (1 << i))) continue;
        send[0] = val;
        send[1] = dstNode;
        toNode = node ^ (1 << i);
        fprintf(stderr, "%d ---> %d\n", node, toNode);
        MPI_Send(send, 2, MPI_INT32_T, toNode, 0, MPI_COMM_WORLD);
        MPI_Recv(receive, 2, MPI_INT32_T, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
        //MPI_Sendrecv(send, 2, MPI_INT32_T, toNode, 0, receive, 2, MPI_INT32_T, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
        val = receive[0];
        dstNode = receive[1];
        //why?sleep
        Sleep(500);
    }

    fprintf(stderr, "process %d of value %d\n", node, val);//輸出最終處理器數據
    MPI_Finalize();
    return 0;
}

運行指令:
mpiexec –n [2^p] MPI_test.exe [p] [q]
2^p表示節點數
q表示移位q
例如:mpiexec –n 16 MPI_test.exe 4 5

警告:

如果同班的你看到了這篇文章,請不要抄襲,老師會代碼查重的。

發佈了48 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章