c – 有效地收集/分散任务

我正在使用的MPI实现本身不支持完整的多线程操作(出于复杂的原因,最高级别是MPI_THREAD_SERIALIZED),所以我试图将来自多个线程的请求汇集到一个工作线程中,然后将结果分散回来到多个线程.

通过使用并发队列,我可以轻松地处理收集本地请求任务,并且MPI本身支持排队异步任务.然而,问题是让双方互相交谈:

为了将响应分散回各个线程,我需要在当前的正在进行的请求中调用类似MPI_Waitany的东西,但在此期间MPI工作者被有效阻止,因此它无法从中收集和提交任何新任务当地工人.

// mpi worker thread
std::vector<MPI_Request> requests; // in-flight requests
while(keep_running)
{
    if(queue.has_tasks_available())
    {
        MPI_Request r;
        // task only performs asynchronous MPI calls, puts result in r
        queue.pop_and_run(task, &r);
        requests.push_back(r);
    }
    int idx;
    MPI_Waitany(requests.size(), requests.data(), &idx,
                MPI_STATUS_IGNORE); // problems here! can't get any new tasks
    dispatch_mpi_result(idx); // notifies other task that it's response is ready
    // ... erase the freed MPI_Request from requests
}

类似地,如果我只是让mpi worker等待新任务从并发队列中可用,然后使用类似MPI_Testany之类的东西轮询MPI响应,那么最好的响应可能需要花费很长时间才能真正实现本地工作人员,最糟糕的是,mpi工作人员会因等待本地任务而陷入僵局,但所有任务都在等待mpi响应.

// mpi worker thread
std::vector<MPI_Request> requests; // in-flight requests
while(keep_running)
{
    queue.wait_for_available_task(); // problem here! might deadlock here if no new tasks happen to be submitted
    MPI_Request r;
    queue.pop_and_run(task, &r);
    requests.push_back(r);
    int idx;
    MPI_Testany(requests.size(), requests.data(), &idx, MPI_STATUS_IGNORE);
    dispatch_mpi_result(idx); // notifies other task that its response is ready
    // ... erase the freed MPI_Request from requests
}

我能看到解决这两个问题的唯一解决方案是让mpi工作者只对双方进行轮询,但这意味着我有一个永久挂钩的线程来处理请求:

// mpi worker thread
std::vector<MPI_Request> requests; // in-flight requests
while(keep_running)
{
    if(queue.has_tasks_available())
    {
        MPI_Request r;
        // task only performs asynchronous MPI calls, puts result in r
        queue.pop_and_run(task, &r);
        requests.push_back(r);
    }
    int idx;
    MPI_Testany(requests.size(), requests.data(), &idx, MPI_STATUS_IGNORE);
    dispatch_mpi_result(idx); // notifies other task that its response is ready
    // ... erase the freed MPI_Request from requests
}

我可以介绍一些睡眠功能,但这似乎是一个黑客,会降低我的吞吐量.这个饥饿/无效率问题是否有其他解决方案?

最佳答案 我担心你最接近你可以做的最好的解决方案是循环检查来自本地线程和MPI_Testany(或更好的MPI_Testsome)的新任务.

你可以做的一件事就是为此专注于整个核心.优点是,这很简单,具有低延迟并提供可预测的性能.在现代HPC系统上,这通常是> 20个核心,所以< 5%的开销.如果您的应用程序受内存限制,则开销甚至可以忽略不计.不幸的是,这浪费了CPU周期和能量.一个小修改就是在循环中引入usleep.您必须调整睡眠时间以平衡利用率和延迟. 如果要将所有内核用于应用程序,则必须小心,以免MPI线程从计算线程中窃取CPU时间.我假设你的队列实现是阻塞的,即不忙等待.这导致这样的情况:计算线程可以在等待时为MPI线程提供CPU时间.不幸的是,发送这可能不是真的,因为工作人员可以在将任务放入队列后立即继续. 您可以做的是增加MPI线程的良好级别(降低优先级),以便它主要在计算线程等待结果时运行.您还可以在循环中使用sched_yield为调度程序提供一些提示.虽然两者都是在POSIX中定义的,但它们的语义非常周,并且在很大程度上依赖于actual scheduler implementation.使用sched_yield实现繁忙的等待循环通常不是一个好主意,但是你没有真正的替代方案.在某些情况下,OpenMPI和MPICH实现类似的循环.

额外MPI线程的影响取决于计算线程的紧密耦合程度.例如.如果它们经常处于障碍状态,则会严重降低性能,因为只需延迟单个线程就会延迟所有线程.

最后,如果您希望实施有效,则必须测量并调整到某个系统.

点赞