Block-Structured AMR Software Framework
Loading...
Searching...
No Matches
AMReX_FBUnpackOnArrival.H
Go to the documentation of this file.
1#ifndef AMREX_FB_UNPACK_ON_ARRIVAL_H_
2#define AMREX_FB_UNPACK_ON_ARRIVAL_H_
3
17
18#ifdef AMREX_FB_UNPACK_ON_ARRIVAL
19
20namespace amrex {
21
23namespace detail {
24
25// Unpack functor: ADD with plain += when the tags are thread safe, atomics
26// otherwise; COPY stores (benign races store the same value).
27template <typename BUF, typename T, bool ADD, bool ATOMIC>
28struct FBOnArrivalUnpack {
29 char const* dbase;
30 int dcomp;
31 int ncomp;
32
34 void operator() (int icell, int ncells, int i, int j, int k,
35 CommRecvBufTag<T> const& tag) const noexcept
36 {
37 if (icell < ncells) {
38 Array4<BUF const> sfab{(BUF const*)(dbase + tag.poff),
39 amrex::begin(tag.bx), amrex::end(tag.bx), ncomp};
40 for (int n = 0; n < ncomp; ++n) {
41 if constexpr (ADD && ATOMIC) {
42 Gpu::Atomic::AddNoRet(&tag.dfab(i,j,k,n+dcomp), (T)sfab(i,j,k,n));
43 } else if constexpr (ADD) {
44 tag.dfab(i,j,k,n+dcomp) += (T)sfab(i,j,k,n);
45 } else {
46 tag.dfab(i,j,k,n+dcomp) = (T)sfab(i,j,k,n);
47 }
48 }
49 }
50 }
51};
52
53template <typename BUF, typename T>
54void fb_oa_pack (TagVector<CommSendBufTag<T>> const& tv, char* dbase, int scomp, int ncomp)
55{
56 detail::ParallelFor_doit(tv,
58 int icell, int ncells, int i, int j, int k, CommSendBufTag<T> const& tag) noexcept
59 {
60 if (icell < ncells) {
61 Array4<BUF> dfab{(BUF*)(dbase + tag.poff), amrex::begin(tag.bx),
62 amrex::end(tag.bx), ncomp};
63 for (int n = 0; n < ncomp; ++n) {
64 dfab(i,j,k,n) = (BUF)tag.sfab(i,j,k,n+scomp);
65 }
66 }
67 });
68}
69
70// Ranges of warps of a TagVector, passed to the kernel by value. The
71// messages completed by one Waitsome are unpacked by one launch over them.
72struct FBOnArrivalWarpRanges {
73 static constexpr int max_ranges = 16;
74 int begin[max_ranges] = {0}; // first warp of each range in the TagVector
75 int prefix[max_ranges+1] = {0}; // prefix sum of the range sizes
76 int n = 0;
77
78 // Returns false if the table is full and the range cannot be merged.
79 bool add (int b, int nw) noexcept {
80 if (n > 0 && begin[n-1] + (prefix[n]-prefix[n-1]) == b) {
81 prefix[n] += nw;
82 } else if (n < max_ranges) {
83 begin[n] = b;
84 prefix[n+1] = prefix[n] + nw;
85 ++n;
86 } else {
87 return false;
88 }
89 return true;
90 }
91};
92
93template <typename T, typename F>
94void fb_oa_unpack_ranges (TagVector<CommRecvBufTag<T>> const& tv,
95 FBOnArrivalWarpRanges const& r, F const& f)
96{
97 const int nwarps = r.prefix[r.n];
98 if (nwarps == 0) { return; }
99
100 constexpr int nthreads = TagVector<CommRecvBufTag<T>>::nthreads;
101 constexpr int nwarps_per_block = nthreads / Gpu::Device::warp_size;
102 const int nblocks = (nwarps + nwarps_per_block-1) / nwarps_per_block;
103 const auto d_tags = tv.d_tags;
104 const auto d_nwarps = tv.d_nwarps;
105 const auto ntags = tv.ntags;
106
108#ifdef AMREX_USE_SYCL
109 [=] AMREX_GPU_DEVICE (sycl::nd_item<1> const& item) noexcept
110 [[sycl::reqd_work_group_size(nthreads)]]
111 [[sycl::reqd_sub_group_size(Gpu::Device::warp_size)]]
112#else
113 [=] AMREX_GPU_DEVICE () noexcept
114#endif
115 {
116#ifdef AMREX_USE_SYCL
117 std::size_t g_tid = item.get_global_id(0);
118#else
119 auto g_tid = std::size_t(blockDim.x)*blockIdx.x + threadIdx.x;
120#endif
121 auto l_wid = int(g_tid / Gpu::Device::warp_size);
122 if (l_wid >= nwarps) { return; }
123 int ir = amrex::bisect(r.prefix, 0, r.n, l_wid);
124 int g_wid = r.begin[ir] + (l_wid - r.prefix[ir]);
125 int tag_id = amrex::bisect(d_nwarps, 0, ntags, g_wid);
126 int b_wid = g_wid - d_nwarps[tag_id];
127#ifdef AMREX_USE_SYCL
128 int lane = item.get_local_id(0) % Gpu::Device::warp_size;
129#else
130 int lane = threadIdx.x % Gpu::Device::warp_size;
131#endif
132 int icell = b_wid*Gpu::Device::warp_size + lane;
133 tagparfor_call_f(icell, d_tags[tag_id], f);
134 });
135}
136
137}
139
140template <class FAB>
141template <typename BUF>
143FabArray<FAB>::FB_on_arrival_meta (const FB& TheFB, int ncomp)
144{
145 RecvSendCopyHandlerKey key{TheFB.m_id, sizeof(BUF), ncomp};
146 if (auto it = m_fb_on_arrival_meta.find(key); it != m_fb_on_arrival_meta.end()) {
147 return it->second.get();
148 }
149
150 if (m_fb_on_arrival_meta.size() >= 32) { m_fb_on_arrival_meta.clear(); }
151
152 auto e = std::make_unique<FBOnArrivalMeta<FAB>>();
153
154 auto make_layout = [&] (MapOfCopyComTagContainers const& tags, bool is_send,
155 typename FBOnArrivalMeta<FAB>::Layout& lo)
156 {
157 for (auto const& [rank, cctc] : tags) {
158 std::size_t nbytes = 0;
159 for (auto const& cct : cctc) {
160 nbytes += (is_send ? cct.sbox : cct.dbox).numPts() * ncomp * sizeof(BUF);
161 }
162 std::size_t acd = ParallelDescriptor::sizeof_selected_comm_data_type(nbytes);
163 nbytes = amrex::aligned_size(acd, nbytes);
164 lo.nbytes = amrex::aligned_size(std::max(alignof(BUF), acd), lo.nbytes);
165 lo.rank.push_back(rank);
166 lo.size.push_back(nbytes);
167 lo.offset.push_back(lo.nbytes);
168 lo.nbytes += nbytes;
169 }
170 };
171
172 make_layout(*TheFB.m_SndTags, true, e->snd);
173 make_layout(*TheFB.m_RcvTags, false, e->rcv);
174
175 {
177 int m = 0;
178 for (auto const& kv : *TheFB.m_SndTags) {
179 auto off = static_cast<std::ptrdiff_t>(e->snd.offset[m]);
180 for (auto const& cct : kv.second) {
181 tags.push_back(CommSendBufTag<value_type>{
182 .sfab = this->const_array(cct.srcIndex),
183 .poff = off, .bx = cct.sbox});
184 off += cct.sbox.numPts() * ncomp * sizeof(BUF);
185 }
186 ++m;
187 }
188 e->send_tv.define(tags);
189 }
190
191 {
192 // Tags are ordered by message, so each message owns a contiguous
193 // range of warps in recv_tv (same warp count as TagVector::define).
195 e->rcv_warp_begin.push_back(0);
196 int m = 0;
197 for (auto const& kv : *TheFB.m_RcvTags) {
198 auto off = static_cast<std::ptrdiff_t>(e->rcv.offset[m]);
199 int nwarps = e->rcv_warp_begin.back();
200 for (auto const& cct : kv.second) {
201 const int li = this->localindex(cct.dstIndex);
202 tags.push_back(CommRecvBufTag<value_type>{
203 .dfab = this->atLocalIdx(li).array(),
204 .poff = off, .bx = cct.dbox});
205 off += cct.dbox.numPts() * ncomp * sizeof(BUF);
206 nwarps += static_cast<int>((detail::get_tag_size(tags.back())
209 }
210 e->rcv_warp_begin.push_back(nwarps);
211 ++m;
212 }
213 e->recv_tv.define(tags);
214 AMREX_ALWAYS_ASSERT(e->rcv_warp_begin.back() == e->recv_tv.ntotwarps);
215 }
216
217 auto* r = e.get();
218 m_fb_on_arrival_meta[key] = std::move(e);
219 return r;
220}
221
222template <class FAB>
223template <typename BUF>
224void
225FabArray<FAB>::FB_on_arrival_nowait (const FB& TheFB, int scomp, int ncomp, int SeqNum,
226 bool sumboundary)
227{
228 auto* e = FB_on_arrival_meta<BUF>(TheFB, ncomp);
229
230 const int N_locs = TheFB.m_LocTags->size();
231 const auto N_rcvs = static_cast<int>(e->rcv.rank.size());
232 const auto N_snds = static_cast<int>(e->snd.rank.size());
233
234 fbd = std::make_unique<FBData<FAB>>();
235 fbd->fb = &TheFB;
236 fbd->scomp = scomp;
237 fbd->ncomp = ncomp;
238 fbd->tag = SeqNum;
239 fbd->oa_meta = e;
240
241 auto alloc = [] (std::size_t n) {
242 return n > 0 ? static_cast<char*>(The_Comms_Arena()->alloc(n)) : nullptr;
243 };
244 fbd->the_recv_data = alloc(e->rcv.nbytes);
245 fbd->the_send_data = alloc(e->snd.nbytes);
246
248
249 if (N_rcvs > 0) {
250 BL_PROFILE("FB::post_recvs");
251 fbd->recv_reqs.assign(N_rcvs, MPI_REQUEST_NULL);
252 fbd->recv_stat.resize(N_rcvs);
253 for (int i = 0; i < N_rcvs; ++i) {
254 if (e->rcv.size[i] > 0) {
255 const int rank = ParallelContext::global_to_local_rank(e->rcv.rank[i]);
256 fbd->recv_reqs[i] = ParallelDescriptor::Arecv
257 (fbd->the_recv_data + e->rcv.offset[i], e->rcv.size[i],
258 rank, SeqNum, comm).req();
259 }
260 }
261 }
262
263 if (N_snds > 0) {
264 {
265 BL_PROFILE("FB::pack");
266 detail::fb_oa_pack<BUF>(e->send_tv, fbd->the_send_data, scomp, ncomp);
268 }
269 BL_PROFILE("FB::post_sends");
270 fbd->send_reqs.assign(N_snds, MPI_REQUEST_NULL);
271 for (int j = 0; j < N_snds; ++j) {
272 if (e->snd.size[j] > 0) {
273 const int rank = ParallelContext::global_to_local_rank(e->snd.rank[j]);
274 fbd->send_reqs[j] = ParallelDescriptor::Asend
275 (fbd->the_send_data + e->snd.offset[j], e->snd.size[j],
276 rank, SeqNum, comm).req();
277 }
278 }
279 }
280
281 if (N_rcvs > 0) { FillBoundary_test(); }
282
283 if (N_locs > 0) {
284 if (sumboundary) {
285 if constexpr (amrex::IsAddAssignable<value_type>::value) {
286 BL_PROFILE("FB::local_add");
287 FB_local_add_gpu(TheFB, scomp, ncomp, false);
288 }
289 } else {
290 BL_PROFILE("FB::local_copy");
291 FB_local_copy_gpu(TheFB, scomp, ncomp);
292 }
293 if (N_rcvs > 0) { FillBoundary_test(); }
294 }
295}
296
297template <class FAB>
298template <typename BUF>
299void
301{
302 auto* e = fbd->oa_meta;
303 const FB* TheFB = fbd->fb;
304 const auto N_rcvs = static_cast<int>(e->rcv.rank.size());
305 const auto N_snds = static_cast<int>(e->snd.rank.size());
306
307 if (N_rcvs > 0) {
308 const bool sumboundary = TheFB->m_sb_snghost.allGE(0);
309 // Below the threshold, wait for all messages and unpack them in one
310 // launch (the Waitsome loop costs more than it hides for tiny messages).
311 const bool on_arrival = e->rcv.nbytes >=
312 static_cast<std::size_t>(std::max(Long(0), FabArrayBase::m_fb_on_arrival_min_bytes));
313 auto unpack = [&] (auto const& f) {
314 if (!on_arrival) {
315 {
316 BL_PROFILE("FB::wait_recvs");
317 ParallelDescriptor::Waitall(fbd->recv_reqs, fbd->recv_stat);
318 }
319 BL_PROFILE("FB::unpack");
320 detail::ParallelFor_doit(e->recv_tv, f);
321 return;
322 }
323 // The messages completed by each Waitsome are unpacked by one
324 // launch. Messages already completed by FillBoundary_test have
325 // null requests, which Waitsome never returns, so they go first.
326 detail::FBOnArrivalWarpRanges ranges;
327 auto flush = [&] () {
328 if (ranges.n > 0) {
329 BL_PROFILE("FB::unpack");
330 detail::fb_oa_unpack_ranges(e->recv_tv, ranges, f);
331 ranges.n = 0;
332 }
333 };
334 auto add = [&] (int m) {
335 const int b = e->rcv_warp_begin[m];
336 const int nw = e->rcv_warp_begin[m+1] - b;
337 if (nw > 0 && !ranges.add(b, nw)) {
338 flush();
339 ranges.add(b, nw);
340 }
341 };
342 for (int m = 0; m < N_rcvs; ++m) {
343 if (fbd->recv_reqs[m] == MPI_REQUEST_NULL) { add(m); }
344 }
345 flush();
346 Vector<int> indx(N_rcvs);
347 for (;;) {
348 int completed = 0;
349 {
350 BL_PROFILE("FB::wait_recvs");
351 ParallelDescriptor::Waitsome(fbd->recv_reqs, completed, indx, fbd->recv_stat);
352 }
353 if (completed == MPI_UNDEFINED || completed == 0) { break; }
354 std::sort(indx.begin(), indx.begin()+completed);
355 for (int c = 0; c < completed; ++c) { add(indx[c]); }
356 flush();
357 }
358 };
359
360 using T = value_type;
361 if (sumboundary) {
362 if constexpr (amrex::IsAddAssignable<T>::value) {
363 if (TheFB->m_threadsafe_rcv) {
364 unpack(detail::FBOnArrivalUnpack<BUF,T,true,false>
365 {fbd->the_recv_data, fbd->scomp, fbd->ncomp});
366 } else if constexpr (amrex::HasAtomicAdd<T>::value) {
367 unpack(detail::FBOnArrivalUnpack<BUF,T,true,true>
368 {fbd->the_recv_data, fbd->scomp, fbd->ncomp});
369 } else {
370 amrex::Abort("FB_on_arrival_finish: SumBoundary needs thread-safe tags or atomic add");
371 }
372 }
373 } else {
374 unpack(detail::FBOnArrivalUnpack<BUF,T,false,false>
375 {fbd->the_recv_data, fbd->scomp, fbd->ncomp});
376 }
377 }
378
379 // In a no-sync region the recv buffer is released in stream order.
380 const bool sync = !Gpu::inNoSyncRegion();
381 if (sync) {
382 BL_PROFILE("FB::unpack_sync");
384 }
385 if (fbd->the_recv_data) {
386 if (sync) {
387 The_Comms_Arena()->free(fbd->the_recv_data);
388 } else {
389 Gpu::freeAsync(The_Comms_Arena(), fbd->the_recv_data);
390 }
391 }
392
393 if (N_snds > 0) {
394 BL_PROFILE("FB::wait_sends");
395 Vector<MPI_Status> stats(fbd->send_reqs.size());
396 ParallelDescriptor::Waitall(fbd->send_reqs, stats);
397 }
398 // The pack kernel finished before the sends were posted.
399 if (fbd->the_send_data) { The_Comms_Arena()->free(fbd->the_send_data); }
400
401 fbd.reset();
402}
403
404}
405
406#endif
407#endif
#define BL_PROFILE(a)
Definition AMReX_BLProfiler.H:593
#define AMREX_ALWAYS_ASSERT(EX)
Definition AMReX_BLassert.H:50
#define AMREX_FORCE_INLINE
Definition AMReX_Extension.H:124
#define AMREX_GPU_DEVICE
Definition AMReX_GpuQualifiers.H:18
virtual void free(void *pt)=0
Free a previously allocated block pointed to by pt.
virtual void * alloc(std::size_t sz)=0
Allocate sz bytes from this arena.
static Long m_fb_on_arrival_min_bytes
Definition AMReX_FabArrayBase.H:889
FBOnArrivalMeta< FAB > * FB_on_arrival_meta(const FB &TheFB, int ncomp)
void FB_on_arrival_nowait(const FB &TheFB, int scomp, int ncomp, int SeqNum, bool sumboundary)
void FB_on_arrival_finish()
static constexpr int warp_size
Definition AMReX_GpuDevice.H:239
MPI_Request req() const
Definition AMReX_ParallelDescriptor.H:74
This class is a thin wrapper around std::vector. Unlike vector, Vector::operator[] provides bound che...
Definition AMReX_Vector.H:29
amrex_long Long
Definition AMReX_INT.H:30
ArrayND< T, 4, true > Array4
Convenient alias for a 4D ArrayND with three spatial dimensions plus a component dimension.
Definition AMReX_Array4.H:1340
Arena * The_Comms_Arena()
Definition AMReX_Arena.cpp:899
std::size_t aligned_size(std::size_t align_requirement, std::size_t size) noexcept
Return the smallest multiple of align_requirement that is >= size.
Definition AMReX_Arena.H:39
__host__ __device__ T bisect(T lo, T hi, F f, T tol=1e-12, int max_iter=100)
Find a root of a scalar function on a bracketing interval using bisection.
Definition AMReX_Algorithm.H:170
void freeAsync(Arena *arena, void *mem) noexcept
Definition AMReX_GpuDevice.H:364
void streamSynchronize() noexcept
Definition AMReX_GpuDevice.H:329
bool inNoSyncRegion() noexcept
Definition AMReX_GpuControl.H:148
gpuStream_t gpuStream() noexcept
Definition AMReX_GpuDevice.H:310
MPI_Comm CommunicatorSub() noexcept
sub-communicator for current frame
Definition AMReX_ParallelContext.H:70
int global_to_local_rank(int rank) noexcept
Definition AMReX_ParallelContext.H:101
Message Asend(const T *, size_t n, int pid, int tag)
Definition AMReX_ParallelDescriptor.H:1163
void Waitsome(Vector< MPI_Request > &, int &, Vector< int > &, Vector< MPI_Status > &)
Definition AMReX_ParallelDescriptor.cpp:1320
void Waitall(Vector< MPI_Request > &, Vector< MPI_Status > &)
Definition AMReX_ParallelDescriptor.cpp:1312
int SeqNum() noexcept
Returns sequential message sequence numbers, usually used as tags for send/recv.
Definition AMReX_ParallelDescriptor.H:678
Message Arecv(T *, size_t n, int pid, int tag)
Definition AMReX_ParallelDescriptor.H:1205
int MPI_Comm
Definition AMReX_ccse-mpi.H:51
static constexpr int MPI_REQUEST_NULL
Definition AMReX_ccse-mpi.H:57
Definition AMReX_Amr.cpp:50
@ F
Definition AMReX_Order.H:7
__host__ __device__ Dim3 begin(BoxND< dim > const &box) noexcept
Return the iterator begin coordinate of box as Dim3.
Definition AMReX_Box.H:2241
void launch(T const &n, L const &f) noexcept
Definition AMReX_GpuLaunchFunctsC.H:122
void Abort(const std::string &msg)
Print a fatal-error message to stderr and abort execution.
Definition AMReX.cpp:244
const int[]
Definition AMReX_BLProfiler.cpp:1665
__host__ __device__ Dim3 end(BoxND< dim > const &box) noexcept
Return the iterator end coordinate of box as Dim3.
Definition AMReX_Box.H:2259
Definition AMReX_TagParallelFor.H:106
Definition AMReX_TagParallelFor.H:116
One message per neighbor rank, all in one device buffer.
Definition AMReX_FabArray.H:130
Definition AMReX_FabArray.H:126