【问题标题】:Problem with MPI matrix-matrix multiply: Cluster slower than single computerMPI矩阵-矩阵乘法的问题:集群比单台计算机慢
【发布时间】:2023-03-11 14:45:01
【问题描述】:

我编写了一个使用 MPI 并行化矩阵-矩阵乘法的小程序。问题是:在我的电脑上运行程序时,大约需要 10 秒才能完成,但在集群上大约需要 75 秒。我想我有一些同步问题,但我无法弄清楚(还)。

这是我的源代码:

/*matrix.c
mpicc -o out matrix.c
mpirun -np 11 out
*/

#include <mpi.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define N 1000

#define DATA_TAG 10
#define B_SENT_TAG 20
#define FINISH_TAG 30

int master(int);
int worker(int, int);

int main(int argc, char **argv) {
    int myrank, p;
    double s_time, f_time;

    MPI_Init(&argc,&argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
    MPI_Comm_size(MPI_COMM_WORLD, &p);

    if (myrank == 0) {
        s_time = MPI_Wtime();
        master(p);
        f_time = MPI_Wtime();
        printf("Complete in %1.2f seconds\n", f_time - s_time);
        fflush(stdout);
    }
    else {
        worker(myrank, p);
    }
    MPI_Finalize();
    return 0;
}

int *read_matrix_row();
int *read_matrix_col();
int send_row(int *, int);
int recv_row(int *, int, MPI_Status *);
int send_tag(int, int);
int write_matrix(int *);

int master(int p) {
    MPI_Status status;
    int *a; int *b;
    int *c = (int *)malloc(N * sizeof(int));
    int i, j; int num_of_finish_row = 0;

    while (1) {
        for (i = 1; i < p; i++) {
            a = read_matrix_row();
            b = read_matrix_col();
            send_row(a, i);
            send_row(b, i);
            //printf("Master - Send data to worker %d\n", i);fflush(stdout);
        }
        wait();
        for (i = 1; i < N / (p - 1); i++) {
            for (j = 1; j < p; j++) {
                //printf("Master - Send next row to worker[%d]\n", j);fflush(stdout);
                b = read_matrix_col();
                send_row(b, j);
            }
        }
        for (i = 1; i < p; i++) {
            //printf("Master - Announce all row of B sent to worker[%d]\n", i);fflush(stdout);
            send_tag(i, B_SENT_TAG);
        }
        //MPI_Barrier(MPI_COMM_WORLD);
        for (i = 1; i < p; i++) {
            recv_row(c, MPI_ANY_SOURCE, &status);
            //printf("Master - Receive result\n");fflush(stdout);
            num_of_finish_row++;
        }
        //printf("Master - Finish %d rows\n", num_of_finish_row);fflush(stdout);
        if (num_of_finish_row >= N)
            break;
    }
    //printf("Master - Finish multiply two matrix\n");fflush(stdout);
    for (i = 1; i < p; i++) {
        send_tag(i, FINISH_TAG);
    }
    //write_matrix(c);
    return 0;
}

int worker(int myrank, int p) {
    int *a = (int *)malloc(N * sizeof(int));
    int *b = (int *)malloc(N * sizeof(int));
    int *c = (int *)malloc(N * sizeof(int));
    int i;
    for (i = 0; i < N; i++) {
        c[i] = 0;
    }
    MPI_Status status;
    int next = (myrank == (p - 1)) ? 1 : myrank + 1;
    int prev = (myrank == 1) ? p - 1 : myrank - 1;
    while (1) {
        recv_row(a, 0, &status);
        if (status.MPI_TAG == FINISH_TAG)
            break;
        recv_row(b, 0, &status);
        wait();
        //printf("Worker[%d] - Receive data from master\n", myrank);fflush(stdout);
        while (1) {
            for (i = 1; i < p; i++) {
                //printf("Worker[%d] - Start calculation\n", myrank);fflush(stdout);
                calc(c, a, b);
                //printf("Worker[%d] - Exchange data with %d, %d\n", myrank, next, prev);fflush(stdout);
                exchange(b, next, prev);
            }
            //printf("Worker %d- Request for more B's row\n", myrank);fflush(stdout);
            recv_row(b, 0, &status);
            //printf("Worker %d - Receive tag %d\n", myrank, status.MPI_TAG);fflush(stdout);
            if (status.MPI_TAG == B_SENT_TAG) {
                break;
                //printf("Worker[%d] - Finish calc one row\n", myrank);fflush(stdout);
            }
        }
        //wait();
        //printf("Worker %d - Send result\n", myrank);fflush(stdout);
        send_row(c, 0);
        for (i = 0; i < N; i++) {
            c[i] = 0;
        }
    }
    return 0;
}

int *read_matrix_row() {
    int *row = (int *)malloc(N * sizeof(int));
    int i;
    for (i = 0; i < N; i++) {
        row[i] = 1;
    }
    return row;
}
int *read_matrix_col() {
    int *col = (int *)malloc(N * sizeof(int));
    int i;
    for (i = 0; i < N; i++) {
        col[i] = 1;
    }
    return col;
}

int send_row(int *row, int dest) {
    MPI_Send(row, N, MPI_INT, dest, DATA_TAG, MPI_COMM_WORLD);
    return 0;
}

int recv_row(int *row, int src, MPI_Status *status) {
    MPI_Recv(row, N, MPI_INT, src, MPI_ANY_TAG, MPI_COMM_WORLD, status);
    return 0;
}

int wait() {
    MPI_Barrier(MPI_COMM_WORLD);
    return 0;
}
int calc(int *c_row, int *a_row, int *b_row) {
    int i;
    for (i = 0; i < N; i++) {
        c_row[i] = c_row[i] + a_row[i] * b_row[i];
        //printf("%d ", c_row[i]);
    }
    //printf("\n");fflush(stdout);
    return 0;
}

int exchange(int *row, int next, int prev) {
    MPI_Request request; MPI_Status status;
    MPI_Isend(row, N, MPI_INT, next, DATA_TAG, MPI_COMM_WORLD, &request);
    MPI_Irecv(row, N, MPI_INT, prev, MPI_ANY_TAG, MPI_COMM_WORLD, &request);
    MPI_Wait(&request, &status);
    return 0;
}

int send_tag(int worker, int tag) {
    MPI_Send(0, 0, MPI_INT, worker, tag, MPI_COMM_WORLD);
    return 0;
}

int write_matrix(int *matrix) {
    int i;
    for (i = 0; i < N; i++) {
        printf("%d ", matrix[i]);
    }
    printf("\n");
    fflush(stdout);
    return 0;
}

【问题讨论】:

  • 为什么在将行发送给工作人员之后还要等待。在等待函数中,您正在调用 MPI_Barrier。你想让master和workers同步吗?这是一个坏主意。工人不需要与主人同步就可以开始工作。

标签: c mpi parallel-processing matrix-multiplication


【解决方案1】:

嗯,你有一个相当小的矩阵 (N=1000),其次你将算法分布在行/列的基础上,而不是分块。

对于使用更好算法的更真实的版本,您可能想要获得优化的 BLAS 库(例如 GOTO 是免费的),使用该库测试单线程性能,然后获取 PBLAS 并将其与优化的 BLAS 链接,并进行比较使用 PBLAS 版本的 MPI 并行性能。

【讨论】:

  • 感谢您的回复。我将在我的计算机上尝试 BLAS 库,但我没有权限将它安装在集群上。此外,如果你能帮助我改进我的代码(和我自己:D),我真的很感激。起初,我不使用 MPI_Barrier,它在我的计算机上需要 50 秒,在集群上需要 > 300 秒,所以我认为问题是同步。你怎么看?
  • Re: 没有权限的 blas 安装,您可以使用./configure --prefix=~/local(或等效的)从源代码构建,并创建一个目录~/local,您将在其中安装自己的版本。然后,您可以使用LD_PRELOAD(例如),以便在系统blas之前优先加载自己的blas。
【解决方案2】:

我在您的程序中发现了一些错误:

  1. 首先,为什么要调用等待函数,因为它的实现只是调用MPI_BarrierMPI_Barrier 是一种原始同步,它通过调用MPI_Barrier 阻塞所有线程直到它们到达“障碍”。我的问题是:你想让主人与工人同步吗?在这种情况下,这不是最优的,因为工人不需要等待主人开始计算。

  2. 其次,有2个不必要的for循环。

    for (i = 1; i < N / (p - 1); i++) {
        for (j = 1; j < p; j++) {
            b = read_matrix_col();
            send_row(b, j);
        }
    }
    
    for (i = 1; i < p; i++) {
        send_tag(i, B_SENT_TAG);
    }
    

在第一个 i 循环中,您没有在语句中使用变量。由于 j-loop 和第二个 i-loop 是相同的,你可以这样做:

for (i = 0; i < p; i++) {
    b = read_matrix_col();
    send_row(b, j);
    send_tag(i, B_SENT_TAG);
 }

在数据传输方面,您的程序并未优化,因为您为每次数据传输发送一个包含 1000 个整数数据的数组。应该有更好的方法来优化数据传输,但我会让你看看。所以请按照我告诉你的更正,告诉我们你的新表现是什么。

正如@janneb 所说,您可以使用 BLAS 库来获得更好的矩阵乘法性能。祝你好运!

【讨论】:

  • 是的,1. 我不想同步 master 和它的 worker。我只想使工作人员彼此同步。但由于所有线程都必须达到“障碍”,我必须在 master.xml 中调用“障碍”。我考虑过创建新的工人组,但我喜欢让我的代码保持简单(因为它已经很复杂了)。你有什么想法? 2.关于这个,我只想调用for-j循环N /(p - 1)次,将B的所有剩余行发送给工人。我知道您可以为我建议一个更好的方法,所以请:p。谢谢。
  • 只有一个问题:你的程序的目的是什么,为什么你需要同步所有的工人?您能否更具体地说明您的程序在做什么?
  • (1)最初,每个进程被赋予矩阵A的1行和矩阵B的1列。 (2)每个进程使用向量乘法得到乘积矩阵C的1个元素。 ( 3)在一个进程使用了​​它的矩阵 B 的列之后,它从环中的后继者那里获取 B 的下一列。 (4)如果B的所有行都已经处理完毕,则退出,否则转至(2)
  • 我不明白(4):如果B的所有行或B的列?或者可能是所有的 A 行?无论如何,你应该知道,在集群中编写分布式程序时,必须有一个最小化通信的算法
【解决方案3】:

我没有查看您的代码,但我可以提供一些提示,说明您的结果可能不会出乎意料:

  1. 如前所述,N=1000 可能太小。您应该进行更多测试以查看程序的可扩展性(尝试设置 N=100、500、1000、5000、10000 等)并比较您的系统和集群上的结果。

  2. 比较您的系统(我假设是一个处理器)和集群上的一个处理器之间的结果。通常在服务器或集群等生产环境中,单个处理器的功能不如专为桌面使用而设计的最佳处理器,但它们提供了稳定性、可靠性和其他对每天 24 小时满负荷运行的环境有用的特性。

    李>
  3. 如果您的处理器有多个内核,则可能会同时运行多个 MPI 进程,与集群中节点之间的同步相比,它们之间的同步可以忽略不计。

  4. 集群中的节点是否静态分配给您?也许其他用户的程序可以调度在你和你同时运行的节点上。

  5. 阅读有关集群架构的文档。有些架构可能更适合特定类别的问题。

  6. 评估集群网络的延迟。从每个节点到另一个节点多次 ping 并计算平均值可能会给出粗略的估计。

  7. 最后但也许是最重要的一点,您的算法可能不是最优的。阅读一些关于矩阵乘法的书籍(我可以推荐“矩阵计算”、Golub 和 Van Loan)。

【讨论】:

    猜你喜欢
    • 2013-12-23
    • 2018-09-08
    • 2017-10-30
    • 2012-11-04
    • 2014-04-21
    • 2015-04-03
    • 1970-01-01
    • 1970-01-01
    • 2018-11-29
    相关资源
    最近更新 更多