core
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
Loading...
Searching...
No Matches
nbx.hpp
Go to the documentation of this file.
1// core — NBX nonblocking-consensus sparse data exchange.
2//
3// A clean reimplementation of the async engine in block_decomposer/src/MPISync.hpp
4// (pbs::MPISync::communicateParticleData) using the canonical NBX protocol of Hoefler et al.,
5// "Scalable Communication Protocols for Dynamic Sparse Data Exchange":
6// 1. Post every outgoing message as a nonblocking synchronous send (MPI_Issend).
7// 2. Loop probing/receiving incoming messages; once all local Issends are matched, enter
8// MPI_Ibarrier; keep draining until the barrier completes — then every message has been
9// received globally.
10// MPI_Issend completes only when the matching receive has been posted, which is what makes the
11// barrier a correct global-termination signal without any prior agreement on message counts.
12//
13// This is the right engine for DYNAMIC / SPARSE patterns where neither the neighbour set nor the
14// message sizes are known ahead of time (particle migration, halo-topology setup). For STATIC grid
15// halos use peclet::core::halo::GridHaloTopology's persistent-neighbour path instead.
16#ifndef PECLET_CORE_HALO_NBX_HPP
17#define PECLET_CORE_HALO_NBX_HPP
18
19#include <memory>
20#include <vector>
21
23
24namespace peclet::core::halo {
25
26class NbxEngine {
27 public:
28 explicit NbxEngine(MPI_Comm comm = MPI_COMM_WORLD) : comm_(comm) {}
29
30 MPI_Comm comm() const { return comm_; }
31 int rank() const {
32 int r;
33 MPI_Comm_rank(comm_, &r);
34 return r;
35 }
36 int size() const {
37 int s;
38 MPI_Comm_size(comm_, &s);
39 return s;
40 }
41
42 // Drive one full sparse exchange.
43 // packNext(std::vector<char>& out) -> int : fill `out` with the next outgoing message and
44 // return its destination rank; return < 0 when this rank has no more messages to send.
45 // onRecv(int src, std::vector<char>& msg) : handle a received message.
46 template <typename PackNext, typename OnRecv>
47 void exchange(PackNext&& packNext, OnRecv&& onRecv, int tag = 0) {
48 // 1) Post all sends as synchronous nonblocking.
49 std::vector<std::unique_ptr<std::vector<char>>> sendBufs;
50 std::vector<MPI_Request> sendReqs;
51 for (;;) {
52 auto buf = std::make_unique<std::vector<char>>();
53 int dest = packNext(*buf);
54 if (dest < 0)
55 break;
57 MPI_Issend(buf->data(), static_cast<int>(buf->size()), MPI_BYTE, dest, tag, comm_, &req);
58 sendBufs.push_back(std::move(buf));
59 sendReqs.push_back(req);
60 }
61
62 // 2) NBX consensus loop.
63 bool barrierStarted = false;
64 bool done = false;
66 std::vector<char> recvBuf;
67 while (!done) {
68 int flag = 0;
70 MPI_Iprobe(MPI_ANY_SOURCE, tag, comm_, &flag, &st);
71 if (flag) {
72 int count = 0;
73 MPI_Get_count(&st, MPI_BYTE, &count);
74 recvBuf.resize(count);
75 MPI_Recv(recvBuf.data(), count, MPI_BYTE, st.MPI_SOURCE, st.MPI_TAG, comm_,
77 onRecv(st.MPI_SOURCE, recvBuf);
78 continue; // keep draining before testing for completion
79 }
80 if (!barrierStarted) {
81 int allSent = 0;
82 MPI_Testall(static_cast<int>(sendReqs.size()), sendReqs.data(), &allSent,
84 if (allSent) {
85 MPI_Ibarrier(comm_, &barrierReq);
86 barrierStarted = true;
87 }
88 } else {
89 int bdone = 0;
91 if (bdone)
92 done = true;
93 }
94 }
95 }
96
97 private:
98 MPI_Comm comm_;
99};
100
101} // namespace peclet::core::halo
102
103#endif // PECLET_CORE_HALO_NBX_HPP
void exchange(PackNext &&packNext, OnRecv &&onRecv, int tag=0)
Definition nbx.hpp:47
MPI_Comm comm() const
Definition nbx.hpp:30
NbxEngine(MPI_Comm comm=MPI_COMM_WORLD)
Definition nbx.hpp:28
int MPI_Comm_size(MPI_Comm, int *s)
Definition mpi_stub.hpp:69
int MPI_Testall(int, MPI_Request *, int *flag, MPI_Status *)
Definition mpi_stub.hpp:119
int MPI_Comm
Definition mpi_stub.hpp:16
int MPI_Request
Definition mpi_stub.hpp:17
#define MPI_COMM_WORLD
Definition mpi_stub.hpp:26
#define MPI_REQUEST_NULL
Definition mpi_stub.hpp:28
#define MPI_STATUS_IGNORE
Definition mpi_stub.hpp:30
#define MPI_BYTE
Definition mpi_stub.hpp:37
int MPI_Iprobe(int, int, MPI_Comm, int *flag, MPI_Status *)
Definition mpi_stub.hpp:107
int MPI_Recv(void *, int, MPI_Datatype, int, int, MPI_Comm, MPI_Status *)
Definition mpi_stub.hpp:95
#define MPI_STATUSES_IGNORE
Definition mpi_stub.hpp:31
int MPI_Ibarrier(MPI_Comm, MPI_Request *)
Definition mpi_stub.hpp:133
int MPI_Get_count(const MPI_Status *, MPI_Datatype, int *c)
Definition mpi_stub.hpp:111
int MPI_Issend(const void *, int, MPI_Datatype, int, int, MPI_Comm, MPI_Request *)
Definition mpi_stub.hpp:104
#define MPI_ANY_SOURCE
Definition mpi_stub.hpp:32
int MPI_Comm_rank(MPI_Comm, int *r)
Definition mpi_stub.hpp:65
int MPI_Test(MPI_Request *, int *flag, MPI_Status *)
Definition mpi_stub.hpp:115
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.hpp:26