You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MPI程序性能优化:解决MPI_Allgather同步等待瓶颈

MPI异步通信优化方案(替代同步MPI_Allgather)

一、异步收集+异步广播的正确实现

你之前尝试主节点异步收集再广播失败,大概率是没正确管理非阻塞通信的请求句柄,或未确保数据就绪后再操作。以下是可运行的核心逻辑:

核心步骤

  1. 主节点(rank=0)完成KNN计算后,异步接收所有从节点的new_cache数据;从节点计算完成后异步发送自身缓存到主节点。
  2. 主节点收齐所有缓存后,异步广播给所有从节点。
  3. 自定义Diccionario结构需拆分成员(key_count、keys、values)分别通信,或序列化为连续字节流后传输。

代码示例

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

// 自定义Diccionario结构示例
typedef struct {
    int key_count;
    int* keys;
    float* values;
} Diccionario;

int main(int argc, char** argv) {
    MPI_Init(&argc, &argv);
    int rank, size;
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    Diccionario new_cache;
    // 省略:各节点加载CSV、执行KNN计算生成new_cache的代码

    MPI_Request* reqs = NULL;
    Diccionario* all_caches = NULL;

    if (rank == 0) {
        // 主节点初始化接收请求与缓存存储数组
        reqs = (MPI_Request*)malloc(2*(size-1)*sizeof(MPI_Request));
        all_caches = (Diccionario*)malloc(size * sizeof(Diccionario));
        all_caches[0] = new_cache; // 自身缓存直接复用

        // 异步接收所有从节点的缓存
        int req_idx = 0;
        for (int i = 1; i < size; i++) {
            // 先接收key_count,再分配内存接收keys/values
            MPI_Irecv(&all_caches[i].key_count, 1, MPI_INT, i, 0, MPI_COMM_WORLD, &reqs[req_idx++]);
            MPI_Wait(&reqs[req_idx-1], MPI_STATUS_IGNORE);
            all_caches[i].keys = (int*)malloc(all_caches[i].key_count * sizeof(int));
            all_caches[i].values = (float*)malloc(all_caches[i].key_count * sizeof(float));
            
            MPI_Irecv(all_caches[i].keys, all_caches[i].key_count, MPI_INT, i, 1, MPI_COMM_WORLD, &reqs[req_idx++]);
            MPI_Irecv(all_caches[i].values, all_caches[i].key_count, MPI_FLOAT, i, 2, MPI_COMM_WORLD, &reqs[req_idx++]);
        }
        MPI_Waitall(req_idx, reqs, MPI_STATUSES_IGNORE);

        // 异步广播所有缓存到从节点
        free(reqs);
        reqs = (MPI_Request*)malloc(3*size*(size-1)*sizeof(MPI_Request));
        req_idx = 0;
        for (int dest = 1; dest < size; dest++) {
            for (int i = 0; i < size; i++) {
                MPI_Isend(&all_caches[i].key_count, 1, MPI_INT, dest, 3+i, MPI_COMM_WORLD, &reqs[req_idx++]);
                MPI_Isend(all_caches[i].keys, all_caches[i].key_count, MPI_INT, dest, 4+i, MPI_COMM_WORLD, &reqs[req_idx++]);
                MPI_Isend(all_caches[i].values, all_caches[i].key_count, MPI_FLOAT, dest, 5+i, MPI_COMM_WORLD, &reqs[req_idx++]);
            }
        }
        MPI_Waitall(req_idx, reqs, MPI_STATUSES_IGNORE);
    } else {
        // 从节点异步发送自身缓存到主节点
        reqs = (MPI_Request*)malloc(3*sizeof(MPI_Request));
        MPI_Isend(&new_cache.key_count, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &reqs[0]);
        MPI_Isend(new_cache.keys, new_cache.key_count, MPI_INT, 0, 1, MPI_COMM_WORLD, &reqs[1]);
        MPI_Isend(new_cache.values, new_cache.key_count, MPI_FLOAT, 0, 2, MPI_COMM_WORLD, &reqs[2]);
        MPI_Waitall(3, reqs, MPI_STATUSES_IGNORE);

        // 从节点异步接收主节点广播的所有缓存
        free(reqs);
        reqs = (MPI_Request*)malloc(3*size*sizeof(MPI_Request));
        all_caches = (Diccionario*)malloc(size * sizeof(Diccionario));
        int req_idx = 0;
        for (int i = 0; i < size; i++) {
            MPI_Irecv(&all_caches[i].key_count, 1, MPI_INT, 0, 3+i, MPI_COMM_WORLD, &reqs[req_idx++]);
            MPI_Wait(&reqs[req_idx-1], MPI_STATUS_IGNORE);
            all_caches[i].keys = (int*)malloc(all_caches[i].key_count * sizeof(int));
            all_caches[i].values = (float*)malloc(all_caches[i].key_count * sizeof(float));
            
            MPI_Irecv(all_caches[i].keys, all_caches[i].key_count, MPI_INT, 0, 4+i, MPI_COMM_WORLD, &reqs[req_idx++]);
            MPI_Irecv(all_caches[i].values, all_caches[i].key_count, MPI_FLOAT, 0, 5+i, MPI_COMM_WORLD, &reqs[req_idx++]);
        }
        MPI_Waitall(req_idx, reqs, MPI_STATUSES_IGNORE);
    }

    // 省略:后续业务逻辑、内存释放代码
    free(reqs);
    if (all_caches) {
        for (int i = 0; i < size; i++) {
            free(all_caches[i].keys);
            free(all_caches[i].values);
        }
        free(all_caches);
    }

    MPI_Finalize();
    return 0;
}

关键注意事项

  • 所有非阻塞通信必须用MPI_Wait/MPI_Waitall等待完成,避免访问未就绪数据。
  • 通信标签(tag)需全局唯一,避免不同通信步骤冲突。
  • 自定义结构无法直接通过MPI传输,必须拆分成员或序列化。

二、重叠计算与通信(进一步降低延迟)

由于KNN计算耗时约60秒,可将通信与计算重叠,减少等待时间:

  • 在KNN计算接近完成时(比如完成90%迭代),提前初始化异步发送请求,待计算完全结束后直接触发传输。
  • 示例代码片段:
int total_iterations = 1000; // 假设KNN总迭代数
for (int i = 0; i < total_iterations; i++) {
    // 执行KNN迭代计算
    if (i == total_iterations * 0.9) {
        // 提前初始化发送请求,此时缓存已完成大部分填充
        MPI_Isend(&new_cache.key_count, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &send_req);
    }
}
// 计算完成后,确保缓存数据填充完毕,等待发送完成
MPI_Wait(&send_req, MPI_STATUS_IGNORE);

三、CSV加载优化建议

你提到广播CSV加载失败,可尝试以下方案:

  • 主节点加载整个CSV文件为字节流或字符串数组,通过MPI_Bcast广播给所有节点,各节点本地解析生成数据结构,避免重复磁盘IO。
  • 大文件可拆分为多个块,用非阻塞广播提升传输效率。

四、替代方案:点对点异步通信(无需主节点中转)

若不想通过主节点中转,可让节点间直接异步交换缓存:

  • 每个节点启动多个非阻塞发送/接收请求,与其他节点直接交换数据,快节点可先完成与其他快节点的通信,无需等待最慢节点。
  • 核心逻辑片段:
MPI_Request* reqs = (MPI_Request*)malloc(2*(size-1)*sizeof(MPI_Request));
int req_idx = 0;
all_caches = (Diccionario*)malloc(size * sizeof(Diccionario));
all_caches[rank] = new_cache;

for (int peer = 0; peer < size; peer++) {
    if (peer != rank) {
        // 异步发送自身缓存给peer
        MPI_Isend(&new_cache.key_count, 1, MPI_INT, peer, 10+rank, MPI_COMM_WORLD, &reqs[req_idx++]);
        MPI_Isend(new_cache.keys, new_cache.key_count, MPI_INT, peer, 20+rank, MPI_COMM_WORLD, &reqs[req_idx++]);
        // 异步接收peer的缓存
        MPI_Irecv(&all_caches[peer].key_count, 1, MPI_INT, peer, 10+peer, MPI_COMM_WORLD, &reqs[req_idx++]);
        MPI_Wait(&reqs[req_idx-1], MPI_STATUS_IGNORE);
        all_caches[peer].keys = (int*)malloc(all_caches[peer].key_count * sizeof(int));
        all_caches[peer].values = (float*)malloc(all_caches[peer].key_count * sizeof(float));
        MPI_Irecv(all_caches[peer].keys, all_caches[peer].key_count, MPI_INT, peer, 20+peer, MPI_COMM_WORLD, &reqs[req_idx++]);
    }
}
MPI_Waitall(req_idx, reqs, MPI_STATUSES_IGNORE);

内容的提问来源于stack exchange,提问作者Sebastian Piceda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 07:32:18