Block-Structured AMR Software Framework
Loading...
Searching...
No Matches
AMReX_ParReduce.H
Go to the documentation of this file.
1#ifndef AMREX_PAR_REDUCE_H_
2#define AMREX_PAR_REDUCE_H_
3#include <AMReX_Config.H>
4
5#include <AMReX_Concepts.H>
6#include <AMReX_Reduce.H>
7
8namespace amrex {
9
46template <typename... Ops, typename... Ts, BaseFabType FAB, typename F>
47typename ReduceData<Ts...>::Type
48ParReduce (TypeList<Ops...> operation_list, TypeList<Ts...> type_list,
49 FabArray<FAB> const& fa, IntVect const& nghost, F&& f)
50{
51 amrex::ignore_unused(operation_list,type_list);
52 ReduceOps<Ops...> reduce_op;
53 ReduceData<Ts...> reduce_data(reduce_op);
54 reduce_op.eval(fa, nghost, reduce_data, std::forward<F>(f));
55 return reduce_data.value(reduce_op);
56}
57
96template <typename Op, typename T, BaseFabType FAB, typename F>
97T
98ParReduce (TypeList<Op> operation_list, TypeList<T> type_list,
99 FabArray<FAB> const& fa, IntVect const& nghost, F&& f)
100{
101 amrex::ignore_unused(operation_list,type_list);
102 ReduceOps<Op> reduce_op;
103 ReduceData<T> reduce_data(reduce_op);
104 reduce_op.eval(fa, nghost, reduce_data, std::forward<F>(f));
105 auto const& hv = reduce_data.value(reduce_op);
106 return amrex::get<0>(hv);
107}
108
147template <typename... Ops, typename... Ts, BaseFabType FAB, typename F>
148typename ReduceData<Ts...>::Type
149ParReduce (TypeList<Ops...> operation_list, TypeList<Ts...> type_list,
150 FabArray<FAB> const& fa, IntVect const& nghost, int ncomp, F&& f)
151{
152 amrex::ignore_unused(operation_list,type_list);
153 ReduceOps<Ops...> reduce_op;
154 ReduceData<Ts...> reduce_data(reduce_op);
155 reduce_op.eval(fa, nghost, ncomp, reduce_data, std::forward<F>(f));
156 return reduce_data.value(reduce_op);
157}
158
194template <typename Op, typename T, BaseFabType FAB, typename F>
195T
196ParReduce (TypeList<Op> operation_list, TypeList<T> type_list,
197 FabArray<FAB> const& fa, IntVect const& nghost, int ncomp, F&& f)
198{
199 amrex::ignore_unused(operation_list,type_list);
200 ReduceOps<Op> reduce_op;
201 ReduceData<T> reduce_data(reduce_op);
202 reduce_op.eval(fa, nghost, ncomp, reduce_data, std::forward<F>(f));
203 auto const& hv = reduce_data.value(reduce_op);
204 return amrex::get<0>(hv);
205}
206
241template <typename... Ops, typename... Ts, BaseFabType FAB, typename F>
242typename ReduceData<Ts...>::Type
243ParReduce (TypeList<Ops...> operation_list, TypeList<Ts...> type_list,
244 FabArray<FAB> const& fa, F&& f)
245{
246 return ParReduce(operation_list, type_list, fa, IntVect(0), std::forward<F>(f));
247}
248
285template <typename Op, typename T, BaseFabType FAB, typename F>
286T
287ParReduce (TypeList<Op> operation_list, TypeList<T> type_list,
288 FabArray<FAB> const& fa, F&& f)
289{
290 return ParReduce(operation_list, type_list, fa, IntVect(0), std::forward<F>(f));
291}
292
294namespace Reduce::detail {
295
296// Reduce f(box_no,i,j,k,n) separately for each component n (PerBox false) or
297// for each local box and component (PerBox true, result index box_no*ncomp+n).
298// A class template, so that nvcc can name the function of the device lambdas.
299template <bool PerBox, typename... Ops>
300struct ParReduceComp
301{
302 template <typename RT, BaseFabType FAB, typename F>
303 static Vector<RT>
304 eval (FabArray<FAB> const& fa, IntVect const& nghost, int ncomp, F const& f)
305 {
306 const int nlocal = fa.local_size();
307 const int nkeys = PerBox ? nlocal*ncomp : ncomp;
308 Vector<RT> r(std::max(nkeys,0));
309 for (auto& x : r) { for_each_init<0, RT, Ops...>(x); }
310 if (nlocal == 0 || ncomp <= 0) { return r; }
311
312#ifdef AMREX_USE_GPU
313 // Block b works on component n = b/nb1 of local box L and stores its
314 // partial result in slot b, so each key's slots are contiguous. Box L
315 // owns blocks [hoff[L],hoff[L+1]) of every component.
316 constexpr int MT = AMREX_GPU_MAX_THREADS;
317 const int maxblocks = int(Gpu::Device::maxBlocksPerLaunch());
318 Long ntot = 0;
319 for (int L = 0; L < nlocal; ++L) {
320 Box b = fa.box(fa.IndexArray()[L]);
321 ntot += b.ok() ? amrex::grow(b,nghost).numPts() : Long(0);
322 }
323 // About one launch worth of blocks, with at least 4 cells per thread.
324 const Long cells_per_block = std::max(Long(MT)*4, (ntot*ncomp + maxblocks - 1) / maxblocks);
325 Gpu::PinnedVector<int> hoff(nlocal+1, 0);
326 for (int L = 0; L < nlocal; ++L) {
327 Box b = fa.box(fa.IndexArray()[L]);
328 Long const npts = b.ok() ? amrex::grow(b,nghost).numPts() : Long(0);
329 Long const nb = (npts + cells_per_block - 1) / cells_per_block;
330 AMREX_ALWAYS_ASSERT(Long(hoff[L]) + nb < Long(std::numeric_limits<int>::max()));
331 hoff[L+1] = hoff[L] + int(nb);
332 }
333 const int nb1 = hoff[nlocal];
334 AMREX_ALWAYS_ASSERT(Long(nb1)*ncomp < Long(std::numeric_limits<int>::max()));
335 const int nblocks = nb1*ncomp;
336 if (nblocks == 0) { return r; }
337
338 auto const& stream = Gpu::gpuStream();
339 Gpu::DeviceVector<int> doff(nlocal+1);
340 Gpu::copyAsync(Gpu::hostToDevice, hoff.begin(), hoff.end(), doff.begin());
341 Gpu::DeviceVector<RT> dpart(nblocks);
342 Gpu::DeviceVector<RT> dr(nkeys);
343 int const* poff = doff.data();
344 RT* ppart = dpart.data();
345 RT* pr = dr.data();
346 BoxIndexer const* pbx = fa.getParForInfo(nghost).getBoxes();
347
348#ifdef AMREX_USE_SYCL
349 constexpr std::size_t shared_mem_bytes = sizeof(RT)
350 * std::max(Gpu::Device::warp_size, MT/Gpu::Device::warp_size);
351 amrex::launch<MT>(std::min(nblocks,maxblocks), shared_mem_bytes, stream,
352 [=] AMREX_GPU_DEVICE (Gpu::Handler const& gh) noexcept
353 {
354 Dim1 blockIdx {gh.blockIdx()};
355 Dim1 threadIdx{gh.threadIdx()};
356 Dim1 gridDim {gh.gridDim()};
357#else
358 amrex::launch<MT>(std::min(nblocks,maxblocks), 0, stream,
359 [=] AMREX_GPU_DEVICE () noexcept
360 {
361#endif
362 for (int b = int(blockIdx.x); b < nblocks; b += int(gridDim.x)) {
363 const int n = b / nb1;
364 const int bb = b - n*nb1;
365 const int L = amrex::bisect(poff, 0, nlocal, bb);
366 const int nb = poff[L+1] - poff[L];
367 BoxIndexer const& indexer = pbx[L];
368 RT t;
369 for_each_init<0, RT, Ops...>(t);
370 for (auto icell = std::uint64_t(bb-poff[L])*MT + threadIdx.x;
371 icell < indexer.numPts(); icell += std::uint64_t(nb)*MT)
372 {
373 auto [i, j, k] = indexer(icell);
374 for_each_local<0, RT, Ops...>(t, f(L,i,j,k,n));
375 }
376 RT& dst = ppart[b];
377 if (threadIdx.x == 0) { for_each_init<0, RT, Ops...>(dst); }
378#ifdef AMREX_USE_SYCL
379 for_each_parallel<0, RT, Ops...>(dst, t, gh);
380#else
381 for_each_parallel<0, RT, Ops...>(dst, t);
382#endif
383 }
384 });
385
386 // Combine each key's partial results in a fixed order.
387#ifdef AMREX_USE_SYCL
388 amrex::launch<MT>(std::min(nkeys,maxblocks), shared_mem_bytes, stream,
389 [=] AMREX_GPU_DEVICE (Gpu::Handler const& gh) noexcept
390 {
391 Dim1 blockIdx {gh.blockIdx()};
392 Dim1 threadIdx{gh.threadIdx()};
393 Dim1 gridDim {gh.gridDim()};
394#else
395 amrex::launch<MT>(std::min(nkeys,maxblocks), 0, stream,
396 [=] AMREX_GPU_DEVICE () noexcept
397 {
398#endif
399 for (int key = int(blockIdx.x); key < nkeys; key += int(gridDim.x)) {
400 int begin = 0;
401 int end = 0;
402 if (PerBox) { // not constexpr: nvcc cannot first-capture ncomp there
403 const int L = key / ncomp;
404 const int n = key - L*ncomp;
405 begin = n*nb1 + poff[L];
406 end = n*nb1 + poff[L+1];
407 } else {
408 begin = key*nb1;
409 end = begin + nb1;
410 }
411 RT t;
412 for_each_init<0, RT, Ops...>(t);
413 for (int ib = begin + int(threadIdx.x); ib < end; ib += MT) {
414 for_each_local<0, RT, Ops...>(t, ppart[ib]);
415 }
416 RT& dst = pr[key];
417 if (threadIdx.x == 0) { for_each_init<0, RT, Ops...>(dst); }
418#ifdef AMREX_USE_SYCL
419 for_each_parallel<0, RT, Ops...>(dst, t, gh);
420#else
421 for_each_parallel<0, RT, Ops...>(dst, t);
422#endif
423 }
424 });
425
426 Gpu::dtoh_memcpy_async(r.data(), pr, sizeof(RT)*nkeys);
427 Gpu::streamSynchronize();
428#else
429 if constexpr (PerBox) {
430 // With too few (box, component) pairs for the threads, split each
431 // box along its last dimension into contiguous chunks of about 16K
432 // cells. Items are ordered by box, component and chunk, which is
433 // the memory order, and their partial results are combined in order.
434 constexpr int d = AMREX_SPACEDIM-1;
435 const int nthreads = OpenMP::in_parallel() ? 1 : OpenMP::get_max_threads();
436 const bool split = nthreads > 1 && nkeys < 4*nthreads;
437 Vector<Box> bx(nlocal);
438 Vector<int> thick(nlocal), nchunk(nlocal), ioff(nlocal+1, 0);
439 for (int L = 0; L < nlocal; ++L) {
440 bx[L] = fa.box(fa.IndexArray()[L]);
441 if (bx[L].ok()) { bx[L].grow(nghost); }
442 // An empty box is one empty chunk.
443 const int len = bx[L].ok() ? bx[L].length(d) : 1;
444 thick[L] = (split && bx[L].ok()) ? int(std::max(Long(1), Long(16384)/(bx[L].numPts()/len))) : len;
445 nchunk[L] = (len + thick[L] - 1) / thick[L];
446 ioff[L+1] = ioff[L] + nchunk[L]*ncomp;
447 }
448 // Without splitting, item is the result index.
449 const int nitems = ioff[nlocal];
450 Vector<RT> part(split ? nitems : 0);
451 RT* pr = split ? part.data() : r.data();
452#ifdef AMREX_USE_OMP
453#pragma omp parallel for schedule(dynamic) if (!system::regtest_reduction)
454#endif
455 for (int item = 0; item < nitems; ++item) {
456 const int L = split ? amrex::bisect(ioff.data(), 0, nlocal, item) : item/ncomp;
457 const int m = item - ioff[L];
458 const int n = m / nchunk[L];
459 const int c = m - n*nchunk[L];
460 Box b = bx[L];
461 b.setSmall(d, bx[L].smallEnd(d) + c*thick[L]);
462 b.setBig(d, std::min(b.smallEnd(d)+thick[L]-1, bx[L].bigEnd(d)));
463 const auto lo = amrex::lbound(b);
464 const auto hi = amrex::ubound(b);
465 RT rem;
466 for_each_init<0, RT, Ops...>(rem);
467 Lanes<RT> acc;
468 lanes_set<0>(acc, rem);
469 for (int k = lo.z; k <= hi.z; ++k) {
470 for (int j = lo.y; j <= hi.y; ++j) {
471 reduce_row<Ops...>(lo.x, hi.x, acc, rem,
472 [&] (int i) { return f(L,i,j,k,n); });
473 }}
474 RT& x = pr[item];
475 for_each_init<0, RT, Ops...>(x);
476 lanes_fold<0, RT, Ops...>(acc, x);
477 for_each_local<0, RT, Ops...>(x, rem);
478 }
479 if (split) {
480 for (int L = 0; L < nlocal; ++L) {
481 for (int m = 0; m < nchunk[L]*ncomp; ++m) {
482 for_each_local<0, RT, Ops...>(r[L*ncomp+m/nchunk[L]], part[ioff[L]+m]);
483 }
484 }
485 }
486 } else {
487 const int nthreads = OpenMP::in_parallel() ? 1 : OpenMP::get_max_threads();
488 Vector<RT> rt(std::size_t(nthreads)*ncomp);
489 for (auto& x : rt) { for_each_init<0, RT, Ops...>(x); }
490#ifdef AMREX_USE_OMP
491#pragma omp parallel if (nthreads > 1 && !system::regtest_reduction)
492#endif
493 {
494 RT* rr = rt.data() + std::size_t(nthreads > 1 ? OpenMP::get_thread_num() : 0)*ncomp;
495 // Component loop outermost, so that each pass streams one component.
496 for (int n = 0; n < ncomp; ++n) {
497 RT rem;
498 for_each_init<0, RT, Ops...>(rem);
499 Lanes<RT> acc;
500 lanes_set<0>(acc, rem);
501 for (MFIter mfi(fa,true); mfi.isValid(); ++mfi) {
502 Box const& b = mfi.growntilebox(nghost);
503 const int L = mfi.LocalIndex();
504 const auto lo = amrex::lbound(b);
505 const auto hi = amrex::ubound(b);
506 for (int k = lo.z; k <= hi.z; ++k) {
507 for (int j = lo.y; j <= hi.y; ++j) {
508 reduce_row<Ops...>(lo.x, hi.x, acc, rem,
509 [&] (int i) { return f(L,i,j,k,n); });
510 }}
511 }
512 lanes_fold<0, RT, Ops...>(acc, rr[n]);
513 for_each_local<0, RT, Ops...>(rr[n], rem);
514 }
515 }
516 for (int it = 0; it < nthreads; ++it) {
517 for (int n = 0; n < ncomp; ++n) {
518 for_each_local<0, RT, Ops...>(r[n], rt[std::size_t(it)*ncomp+n]);
519 }
520 }
521 }
522#endif
523 return r;
524 }
525};
526
527}
529
561template <typename... Ops, typename... Ts, BaseFabType FAB, typename F>
562Vector<typename ReduceData<Ts...>::Type>
564 FabArray<FAB> const& fa, IntVect const& nghost, int ncomp, F&& f)
565{
566 amrex::ignore_unused(operation_list,type_list);
567 return Reduce::detail::ParReduceComp<false,Ops...>::template
568 eval<typename ReduceData<Ts...>::Type>(fa, nghost, ncomp, std::forward<F>(f));
569}
570
576template <typename Op, typename T, BaseFabType FAB, typename F>
577Vector<T>
578ParReducePerComp (TypeList<Op> operation_list, TypeList<T> type_list,
579 FabArray<FAB> const& fa, IntVect const& nghost, int ncomp, F&& f)
580{
581 amrex::ignore_unused(operation_list,type_list);
582 auto const& rt = Reduce::detail::ParReduceComp<false,Op>::template
583 eval<typename ReduceData<T>::Type>(fa, nghost, ncomp, std::forward<F>(f));
584 Vector<T> r(rt.size());
585 for (int i = 0, N = int(rt.size()); i < N; ++i) { r[i] = amrex::get<0>(rt[i]); }
586 return r;
587}
588
598template <typename... Ops, typename... Ts, BaseFabType FAB, typename F>
599Vector<typename ReduceData<Ts...>::Type>
601 FabArray<FAB> const& fa, IntVect const& nghost, int ncomp, F&& f)
602{
603 amrex::ignore_unused(operation_list,type_list);
604 return Reduce::detail::ParReduceComp<true,Ops...>::template
605 eval<typename ReduceData<Ts...>::Type>(fa, nghost, ncomp, std::forward<F>(f));
606}
607
613template <typename Op, typename T, BaseFabType FAB, typename F>
614Vector<T>
616 FabArray<FAB> const& fa, IntVect const& nghost, int ncomp, F&& f)
617{
618 amrex::ignore_unused(operation_list,type_list);
619 auto const& rt = Reduce::detail::ParReduceComp<true,Op>::template
620 eval<typename ReduceData<T>::Type>(fa, nghost, ncomp, std::forward<F>(f));
621 Vector<T> r(rt.size());
622 for (int i = 0, N = int(rt.size()); i < N; ++i) { r[i] = amrex::get<0>(rt[i]); }
623 return r;
624}
625
626}
627#endif
#define AMREX_ALWAYS_ASSERT(EX)
Definition AMReX_BLassert.H:50
#define AMREX_GPU_DEVICE
Definition AMReX_GpuQualifiers.H:18
__host__ __device__ BoxND & setBig(const IntVectND< dim > &bg) noexcept
Redefine the big end of the BoxND.
Definition AMReX_Box.H:516
__host__ __device__ bool ok() const noexcept
Return true if high bounds are >= low bounds and the index type is valid.
Definition AMReX_Box.H:229
__host__ __device__ BoxND & setSmall(const IntVectND< dim > &sm) noexcept
Definition AMReX_Box.H:508
An Array of FortranArrayBox(FAB)-like Objects.
Definition AMReX_FabArray.H:356
Definition AMReX_Reduce.H:556
Type value()
Definition AMReX_Reduce.H:591
Definition AMReX_Reduce.H:715
void eval(MF const &mf, IntVect const &nghost, D &reduce_data, F &&f)
Definition AMReX_Reduce.H:852
This class is a thin wrapper around std::vector. Unlike vector, Vector::operator[] provides bound che...
Definition AMReX_Vector.H:29
Checks if a type is derived from amrex::BaseFab.
Definition AMReX_Concepts.H:13
amrex_long Long
Definition AMReX_INT.H:30
__host__ __device__ Dim3 ubound(Array4< T > const &a) noexcept
Return the inclusive upper bounds of an Array4 in Dim3 form.
Definition AMReX_Array4.H:1365
__host__ __device__ Dim3 lbound(Array4< T > const &a) noexcept
Return the inclusive lower bounds of an Array4 in Dim3 form.
Definition AMReX_Array4.H:1351
__host__ __device__ BoxND< dim > grow(const BoxND< dim > &b, int i) noexcept
Return a copy of b grown uniformly by i cells in every direction.
Definition AMReX_Box.H:1328
__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
Definition AMReX_Amr.cpp:50
__host__ __device__ void ignore_unused(const Ts &...)
No-op helper that marks variables as intentionally unused.
Definition AMReX.H:273
@ F
Definition AMReX_Order.H:7
ReduceData< Ts... >::Type ParReduce(TypeList< Ops... > operation_list, TypeList< Ts... > type_list, FabArray< FAB > const &fa, IntVect const &nghost, F &&f)
Parallel reduce for MultiFab/FabArray. The reduce result is local and it's the user's responsibility ...
Definition AMReX_ParReduce.H:48
Vector< typename ReduceData< Ts... >::Type > ParReducePerComp(TypeList< Ops... > operation_list, TypeList< Ts... > type_list, FabArray< FAB > const &fa, IntVect const &nghost, int ncomp, F &&f)
Parallel reduce for MultiFab/FabArray with a separate result for each component. The reduce result is...
Definition AMReX_ParReduce.H:563
__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
BoxND< 3 > Box
Box is an alias for amrex::BoxND instantiated with AMREX_SPACEDIM.
Definition AMReX_BaseFwd.H:35
@ x
Definition AMReX_Orientation.H:14
IntVectND< 3 > IntVect
IntVect is an alias for amrex::IntVectND instantiated with AMREX_SPACEDIM.
Definition AMReX_BaseFwd.H:38
BoxIndexerND< 3 > BoxIndexer
Definition AMReX_Box.H:2610
const int[]
Definition AMReX_BLProfiler.cpp:1665
Vector< typename ReduceData< Ts... >::Type > ParReducePerBoxComp(TypeList< Ops... > operation_list, TypeList< Ts... > type_list, FabArray< FAB > const &fa, IntVect const &nghost, int ncomp, F &&f)
Parallel reduce for MultiFab/FabArray with a separate result for each local box and component....
Definition AMReX_ParReduce.H:600
__host__ __device__ Dim3 end(BoxND< dim > const &box) noexcept
Return the iterator end coordinate of box as Dim3.
Definition AMReX_Box.H:2259
std::vector< std::string > split(std::string const &s, std::string const &sep)
Split a string using given tokens in sep.
Definition AMReX_String.cpp:44
__host__ __device__ constexpr int get(IntVectND< dim > const &iv) noexcept
Get the Idx-th element of IntVectND<dim>.
Definition AMReX_IntVect.H:1360
Struct for holding types.
Definition AMReX_TypeList.H:13