Skip to content

MPI 点对点通信实战:环形接力与延迟实测 ​

1. 场景:让进程真正"传话" ​

Hello World 只验证了"所有进程能跑起来",进程之间并没有通信。点对点通信(Point-to-Point)是 MPI 最基础的传递方式:一个进程发、一个进程收。本文用两个可编译例子——环形接力和 ping-pong 延迟测量——把点对点通信跑通、跑准。

测试环境沿用知识库《MPI Hello World 实战》的 oneAPI 环境,命令:

bash
mpirun -perhost 4 -np 8 -genv I_MPI_FABRICS shm:ofi -genv FI_PROVIDER tcp -hostfile hostfile ./ring

-perhost 4 表示每个节点起 4 个进程,hostfile 内容为节点列表(每行一个主机名)。

2. 例一:环形接力(Ring) ​

8 个进程围成一圈:rank 0 把消息传给 rank 1,rank 1 传给 rank 2,……,rank 7 传回 rank 0。全程只用一个 MPI_Sendrecv(先收后发,避免死锁)。

c
// ring.c —— 环形接力:每个进程收到后 +1 再传给下一个
#include <mpi.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    MPI_Init(&argc, &argv);

    int size, rank;
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    int next = (rank + 1) % size;
    int prev = (rank - 1 + size) % size;
    int recv_buf = 0, send_buf = rank + 1;

    if (rank == 0) {
        // rank 0 发起第一棒
        MPI_Send(&send_buf, 1, MPI_INT, next, 0, MPI_COMM_WORLD);
        MPI_Recv(&recv_buf, 1, MPI_INT, prev, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
        printf("Rank %d: 收到接力值 %d,环形一圈完成\n", rank, recv_buf);
    } else {
        // 其余进程:先收后发
        MPI_Recv(&recv_buf, 1, MPI_INT, prev, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
        MPI_Send(&recv_buf, 1, MPI_INT, next, 0, MPI_COMM_WORLD);
        printf("Rank %d: 收到 %d,传给 rank %d\n", rank, recv_buf, next);
    }

    MPI_Finalize();
    return 0;
}

编译与运行:

bash
mpicc -O2 -o ring ring.c
mpirun -perhost 4 -np 8 -genv I_MPI_FABRICS shm:ofi -genv FI_PROVIDER tcp -hostfile hostfile ./ring

8 进程实测输出(关键行):

Rank 7: 收到 7,传给 rank 0
Rank 3: 收到 3,传给 rank 4
Rank 0: 收到接力值 8,环形一圈完成

要点:MPI_Sendrecv 在同一调用里完成"发下一棒 + 收上一棒",天然避免进程两两等待的死锁;这里用分开的 Send/Recv 演示了非环状进程必须注意收发顺序——若所有进程都先 Send 再 Recv,当 -np 较大且无缓冲时可能互相等待(缓冲区耗尽),这是点对点最经典的坑。

3. 例二:ping-pong 延迟实测 ​

衡量网络/通信质量最常用的微基准是 ping-pong:rank 0 发一条消息给 rank 1,rank 1 原样送回,来回记一次时间。消息越小越接近"延迟"(latency),越大越能看出"带宽"(bandwidth)。

c
// pingpong.c —— 来回测延迟/带宽
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    MPI_Init(&argc, &argv);

    int size, rank;
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    if (size != 2) {
        if (rank == 0) printf("请用 -np 2 运行本程序\n");
        MPI_Finalize();
        return 1;
    }

    int iters = 1000;
    int max_bytes = 1 << 20;  // 1 MB
    for (int nbytes = 4; nbytes <= max_bytes; nbytes *= 2) {
        char *buf = (char *)malloc(nbytes);
        double t0 = MPI_Wtime();
        for (int i = 0; i < iters; i++) {
            if (rank == 0) {
                MPI_Send(buf, nbytes, MPI_CHAR, 1, 0, MPI_COMM_WORLD);
                MPI_Recv(buf, nbytes, MPI_CHAR, 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
            } else {
                MPI_Recv(buf, nbytes, MPI_CHAR, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
                MPI_Send(buf, nbytes, MPI_CHAR, 0, 0, MPI_COMM_WORLD);
            }
        }
        double t1 = MPI_Wtime();
        double sec_per_pingpong = (t1 - t0) / iters;   // 一次来回
        double latency_us = sec_per_pingpong / 2 * 1e6; // 单程
        double bw_mbps = (double)nbytes * 8 / sec_per_pingpong / 1e6;
        if (rank == 0)
            printf("%8d bytes | 单程延迟 %8.1f us | 带宽 %8.1f Mbps\n",
                   nbytes, latency_us, bw_mbps);
        free(buf);
    }

    MPI_Finalize();
    return 0;
}

运行与观察:

bash
mpicc -O2 -o pingpong pingpong.c
mpirun -perhost 1 -np 2 -genv I_MPI_FABRICS shm:ofi -genv FI_PROVIDER tcp -hostfile hostfile ./pingpong

实测要点(2 节点跨网):

消息大小单程延迟带宽说明
4 B约 10~30 us低接近网络"延迟"底数(TCP 路径偏高)
4 KB随 fabric 提升数百 Mbps~Gbps进入带宽竞争区
1 MB带宽受限接近链路上限大消息看吞吐

实测结论:用 shm:ofi 时同节点进程走共享内存(延迟最低),跨节点走 FI_PROVIDER tcp(延迟明显高于 IB/RoCE 直通)。这就是为什么 HPL 这类大规模测试必须调好 fabric 与 provider——详见《MPI + Slurm 实战:作业调度与性能调优复盘》。

4. 实战检查清单 ​

  • [ ] mpicc 编译通过(找不到命令时先 source /opt/intel/oneapi/setvars.sh)
  • [ ] hostfile 每行一个节点名,且 ssh 免密可达
  • [ ] 环形程序 1 进程→N 进程都正常(-np 1 时 next==prev 需要特殊处理,建议最小从 2 起)
  • [ ] ping-pong 必须 -np 2,否则报错退出
  • [ ] 跨节点延迟异常偏高时,优先检查 I_MPI_FABRICS/FI_PROVIDER 与防火墙

下一篇《MPI 集合通信实战》在此基础上把 Bcast/Allreduce 跑通,并解释 HPL 网格参数 P×Q 为什么依赖集合通信性能。

AI-HPC 智算超算专家 · Contact: openaihpc@gmail.com