Block-Structured AMR Software Framework
Loading...
Searching...
No Matches
AMReX_DenseBins.H
Go to the documentation of this file.
1#ifndef AMREX_DENSEBINS_H_
2#define AMREX_DENSEBINS_H_
3#include <AMReX_Config.H>
4
5#include <AMReX_Gpu.H>
6#include <AMReX_Scan.H>
7#include <AMReX_IntVect.H>
8#include <AMReX_BLProfiler.H>
9#include <AMReX_BinIterator.H>
10
11namespace amrex
12{
14namespace detail
15{
16#if defined(AMREX_USE_CUDA) || defined(AMREX_USE_HIP)
29 template <typename T>
31 T warpRunAggregatedIncrement (T* counts, T bin, bool active) noexcept
32 {
33 constexpr int warp_size = Gpu::Device::warp_size;
34#if defined(AMREX_USE_HIP)
35 using mask_t = unsigned long long;
36 const int lane = static_cast<int>(__lane_id());
37 const mask_t active_mask = __ballot(active);
38 const T prev_bin = __shfl_up(bin, 1);
39#else
40 using mask_t = unsigned int;
41 constexpr mask_t full_mask = 0xffffffffu;
42 const int lane = static_cast<int>(threadIdx.x) % warp_size;
43 const mask_t active_mask = __ballot_sync(full_mask, active);
44 const T prev_bin = __shfl_up_sync(full_mask, bin, 1);
45#endif
46 const bool prev_active = (lane > 0) && ((active_mask >> (lane-1)) & 1);
47 const bool head = active && (!prev_active || bin != prev_bin);
48#if defined(AMREX_USE_HIP)
49 const mask_t heads = __ballot(head);
50#else
51 const mask_t heads = __ballot_sync(full_mask, head);
52#endif
53 // lanes <= this lane
54 const mask_t lanes_le = (lane == warp_size-1) ? ~mask_t(0)
55 : (mask_t(1) << (lane+1)) - 1;
56
57 // The first lane of this lane's run, and (for heads) the length of the run,
58 // which ends at the next head or inactive lane.
59 int run_start = lane;
60 T base = 0;
61 if (active) {
62 const mask_t heads_le = heads & lanes_le;
63#if defined(AMREX_USE_HIP)
64 run_start = 63 - __clzll(static_cast<long long>(heads_le));
65#else
66 run_start = 31 - __clz(static_cast<int>(heads_le));
67#endif
68 }
69 if (head) {
70 const mask_t ends = (heads | ~active_mask) & ~lanes_le;
71#if defined(AMREX_USE_HIP)
72 const int run_end = (ends != 0) ? __ffsll(static_cast<long long>(ends)) - 1
73 : warp_size;
74#else
75 const int run_end = (ends != 0) ? __ffs(static_cast<int>(ends)) - 1
76 : warp_size;
77#endif
78 base = Gpu::Atomic::Add(&counts[bin], static_cast<T>(run_end - lane));
79 }
80#if defined(AMREX_USE_HIP)
81 base = __shfl(base, run_start);
82#else
83 base = __shfl_sync(full_mask, base, run_start);
84#endif
85 return base + static_cast<T>(lane - run_start);
86 }
87#endif
88}
90
91namespace BinPolicy
92{
93 struct GPUBinPolicy {};
94 struct OpenMPBinPolicy {};
95 struct SerialBinPolicy {};
96
97 static constexpr GPUBinPolicy GPU{};
98 static constexpr OpenMPBinPolicy OpenMP{};
99 static constexpr SerialBinPolicy Serial{};
100
101#ifdef AMREX_USE_GPU
102 static constexpr GPUBinPolicy Default{};
103#else
104 static constexpr OpenMPBinPolicy Default{};
105#endif
106}
107
108template <typename T>
110{
112
113 using const_pointer_type = std::conditional_t<IsParticleTileData<T>(),
114 T,
115 const T*
116 >;
117
119 const Gpu::DeviceVector<index_type>& permutation,
120 const T* items)
121 : m_offsets_ptr(offsets.dataPtr()),
122 m_permutation_ptr(permutation.dataPtr()),
123 m_items(items)
124 {}
125
126 [[nodiscard]] AMREX_GPU_HOST_DEVICE
127 BinIterator<T> getBinIterator(const int bin_number) const noexcept
128 {
130 }
131
135};
136
137
153template <typename T>
155{
156public:
157
160
161 using const_pointer_type = std::conditional_t<IsParticleTileData<T>(),
162 T,
163 const T*
164 >;
165
166 using const_pointer_input_type = std::conditional_t<IsParticleTileData<T>(),
167 const T&,
168 const T*
169 >;
170
171private:
172
173 template <typename F, typename I>
175 static auto call_f (F const& f, const_pointer_input_type v, I& index) {
176 if constexpr (IsCallable<F, decltype(v), I>::value) {
177 return f(v, index);
178 } else {
179 return f(v[index]);
180 }
181 }
182
183public:
184
207 template <typename N, typename F>
208 void build (N nitems, const_pointer_input_type v, const Box& bx, F&& f)
209 {
210 build(BinPolicy::Default, nitems, v, bx, std::forward<F>(f));
211 }
212
234 template <typename N, typename F>
235 void build (N nitems, const_pointer_input_type v, int nbins, F&& f)
236 {
237 build(BinPolicy::Default, nitems, v, nbins, std::forward<F>(f));
238 }
239
262 template <typename N, typename F>
263 void build (BinPolicy::GPUBinPolicy, N nitems, const_pointer_input_type v, const Box& bx, F const& f)
264 {
265 const auto lo = lbound(bx);
266 const auto hi = ubound(bx);
267 build(BinPolicy::GPU, nitems, v, bx.numPts(),
269 {
270 auto iv = call_f(f,t,i);
271 auto iv3 = iv.dim3();
272 int nx = hi.x-lo.x+1;
273 int ny = hi.y-lo.y+1;
274 int nz = hi.z-lo.z+1;
275 index_type uix = amrex::min(nx-1,amrex::max(0,iv3.x));
276 index_type uiy = amrex::min(ny-1,amrex::max(0,iv3.y));
277 index_type uiz = amrex::min(nz-1,amrex::max(0,iv3.z));
278 return (uiz * ny + uiy) * nx + uix;
279 });
280 }
281
303 template <typename N, typename F>
304 void build (BinPolicy::GPUBinPolicy, N nitems, const_pointer_input_type v, int nbins, F const& f)
305 {
306 BL_PROFILE("DenseBins<T>::buildGPU");
307
308 m_items = v;
309
310 m_bins.resize(nitems);
311 m_perm.resize(nitems);
312 m_local_offsets.resize(nitems);
313
314 m_counts.resize(0);
315 m_counts.resize(nbins+1, 0);
316
317 m_offsets.resize(0);
318 m_offsets.resize(nbins+1);
319
320 index_type* pbins = m_bins.dataPtr();
321 index_type* pcount = m_counts.dataPtr();
322 index_type* plocal_offsets = m_local_offsets.dataPtr();
323#if defined(AMREX_USE_CUDA) || defined(AMREX_USE_HIP)
324 // Items are often (partially) sorted by bin, so adjacent threads tend to increment
325 // the same counter. Aggregate those increments within each warp to reduce contention.
326 constexpr int block_size = AMREX_GPU_MAX_THREADS;
327 const auto nblocks = static_cast<int>((Long(nitems) + block_size - 1) / block_size);
328 if (nblocks > 0) {
329 amrex::launch<block_size>(nblocks, Gpu::gpuStream(),
330 [=] AMREX_GPU_DEVICE () noexcept
331 {
332 const Long li = Long(blockIdx.x) * block_size + threadIdx.x;
333 const bool active = li < Long(nitems);
334 const auto i = static_cast<index_type>(li);
335 index_type bin = 0;
336 if (active) {
337 bin = static_cast<index_type>(call_f(f,v,i));
338 pbins[i] = bin;
339 }
340 index_type off = detail::warpRunAggregatedIncrement(pcount, bin, active);
341 if (active) {
342 plocal_offsets[i] = off;
343 }
344 });
345 }
346#else
347 amrex::ParallelFor(nitems, [=] AMREX_GPU_DEVICE (int i) noexcept
348 {
349 pbins[i] = call_f(f,v,i);
350 index_type off = Gpu::Atomic::Add(&pcount[pbins[i]], index_type{ 1 });
351 plocal_offsets[i] = off;
352 });
353#endif
354
355 Gpu::exclusive_scan(m_counts.begin(), m_counts.end(), m_offsets.begin());
356
357 index_type* pperm = m_perm.dataPtr();
358 index_type* poffsets = m_offsets.dataPtr();
359 amrex::ParallelFor(nitems, [=] AMREX_GPU_DEVICE (int i) noexcept
360 {
361 index_type index = poffsets[pbins[i]] + plocal_offsets[i];
362 pperm[index] = i;
363 });
364
366 }
367
391 template <typename N, typename F>
392 void build (BinPolicy::OpenMPBinPolicy, N nitems, const_pointer_input_type v, const Box& bx, F const& f)
393 {
394 const auto lo = lbound(bx);
395 const auto hi = ubound(bx);
396 build(BinPolicy::OpenMP, nitems, v, bx.numPts(),
397 [=] (const_pointer_type t, index_type i) noexcept
398 {
399 auto iv = call_f(f,t,i);
400 auto iv3 = iv.dim3();
401 int nx = hi.x-lo.x+1;
402 int ny = hi.y-lo.y+1;
403 int nz = hi.z-lo.z+1;
404 index_type uix = amrex::min(nx-1,amrex::max(0,iv3.x));
405 index_type uiy = amrex::min(ny-1,amrex::max(0,iv3.y));
406 index_type uiz = amrex::min(nz-1,amrex::max(0,iv3.z));
407 return (uiz * ny + uiy) * nx + uix;
408 });
409 }
410
433 template <typename N, typename F>
434 void build (BinPolicy::OpenMPBinPolicy, N nitems, const_pointer_input_type v, int nbins, F const& f)
435 {
436 BL_PROFILE("DenseBins<T>::buildOpenMP");
437
438 if (nbins <= 0) { return; }
439
440 m_items = v;
441
442 m_bins.resize(nitems);
443 m_perm.resize(nitems);
444
445 int nchunks = OpenMP::get_max_threads();
446 int chunksize = nitems / nchunks;
447 auto* counts = (index_type*)(The_Arena()->alloc(nchunks*nbins*sizeof(index_type)));
448 for (int i = 0; i < nbins*nchunks; ++i) { counts[i] = 0;}
449
450 m_counts.resize(0);
451 m_counts.resize(nbins+1, 0);
452
453 m_offsets.resize(0);
454 m_offsets.resize(nbins+1);
455
456#ifdef AMREX_USE_OMP
457#pragma omp parallel for
458#endif
459 for (int j = 0; j < nchunks; ++j) {
460 int istart = j*chunksize;
461 int istop = (j == nchunks-1) ? nitems : (j+1)*chunksize;
462 for (int i = istart; i < istop; ++i) {
463 m_bins[i] = call_f(f,v,i);
464 ++counts[nbins*j+m_bins[i]];
465 }
466 }
467
468#ifdef AMREX_USE_OMP
469#pragma omp parallel for
470#endif
471 for (int i = 0; i < nbins; ++i) {
472 index_type total = 0;
473 for (int j = 0; j < nchunks; ++j) {
474 auto tmp = counts[nbins*j+i];
475 counts[nbins*j+i] = total;
476 total += tmp;
477 }
478 m_counts[i] = total;
479 }
480
481 // note - this part has to be serial
482 m_offsets[0] = 0;
483 for (int i = 0; i < nbins; ++i) {m_offsets[i+1] = m_offsets[i] + m_counts[i];}
484
485#ifdef AMREX_USE_OMP
486#pragma omp parallel for
487#endif
488 for (int i = 0; i < nbins; ++i) {
489 for (int j = 0; j < nchunks; ++j) {
490 counts[nbins*j+i] += m_offsets[i];
491 }
492 }
493
494#ifdef AMREX_USE_OMP
495#pragma omp parallel for
496#endif
497 for (int j = 0; j < nchunks; ++j) {
498 int istart = j*chunksize;
499 int istop = (j == nchunks-1) ? nitems : (j+1)*chunksize;
500 for (int i = istart; i < istop; ++i) {
501 auto bid = m_bins[i];
502 m_perm[counts[nbins*j+bid]++] = i;
503 }
504 }
505
506 The_Arena()->free(counts);
507 }
508
532 template <typename N, typename F>
533 void build (BinPolicy::SerialBinPolicy, N nitems, const_pointer_input_type v, const Box& bx, F const& f)
534 {
535 const auto lo = lbound(bx);
536 const auto hi = ubound(bx);
537 build(BinPolicy::Serial, nitems, v, bx.numPts(),
538 [=] (const_pointer_type t, index_type i) noexcept
539 {
540 auto iv = call_f(f,t,i);
541 auto iv3 = iv.dim3();
542 int nx = hi.x-lo.x+1;
543 int ny = hi.y-lo.y+1;
544 int nz = hi.z-lo.z+1;
545 index_type uix = amrex::min(nx-1,amrex::max(0,iv3.x));
546 index_type uiy = amrex::min(ny-1,amrex::max(0,iv3.y));
547 index_type uiz = amrex::min(nz-1,amrex::max(0,iv3.z));
548 return (uiz * ny + uiy) * nx + uix;
549 });
550 }
551
574 template <typename N, typename F>
575 void build (BinPolicy::SerialBinPolicy, N nitems, const_pointer_input_type v, int nbins, F const& f)
576 {
577 BL_PROFILE("DenseBins<T>::buildSerial");
578
579 m_items = v;
580
581 m_bins.resize(nitems);
582 m_perm.resize(nitems);
583
584 m_counts.resize(0);
585 m_counts.resize(nbins+1, 0);
586
587 m_offsets.resize(0);
588 m_offsets.resize(nbins+1);
589
590 for (N i = 0; i < nitems; ++i) {
591 m_bins[i] = call_f(f,v,i);
592 ++m_counts[m_bins[i]];
593 }
594
595 Gpu::exclusive_scan(m_counts.begin(), m_counts.end(), m_offsets.begin());
596
597 Gpu::copy(Gpu::deviceToDevice, m_offsets.begin(), m_offsets.end(), m_counts.begin());
598
599 for (N i = 0; i < nitems; ++i) {
600 index_type index = m_counts[m_bins[i]]++;
601 m_perm[index] = i;
602 }
603 }
604
606 [[nodiscard]] Long numItems () const noexcept { return m_perm.size(); }
607
609 [[nodiscard]] Long numBins () const noexcept { return m_offsets.size()-1; }
610
612 [[nodiscard]] index_type* permutationPtr () noexcept { return m_perm.dataPtr(); }
613
615 [[nodiscard]] index_type* offsetsPtr () noexcept { return m_offsets.dataPtr(); }
616
618 [[nodiscard]] index_type* binsPtr () noexcept { return m_bins.dataPtr(); }
619
621 [[nodiscard]] const index_type* permutationPtr () const noexcept { return m_perm.dataPtr(); }
622
624 [[nodiscard]] const index_type* offsetsPtr () const noexcept { return m_offsets.dataPtr(); }
625
627 [[nodiscard]] const index_type* binsPtr () const noexcept { return m_bins.dataPtr(); }
628
631 {
632 return DenseBinIteratorFactory<T>(m_offsets, m_perm, m_items);
633 }
634
635private:
636
637 const_pointer_type m_items;
638
641 Gpu::DeviceVector<index_type> m_local_offsets;
644};
645
646}
647
648#endif
#define BL_PROFILE(a)
Definition AMReX_BLProfiler.H:562
#define AMREX_FORCE_INLINE
Definition AMReX_Extension.H:124
#define AMREX_GPU_DEVICE
Definition AMReX_GpuQualifiers.H:18
#define AMREX_GPU_HOST_DEVICE
Definition AMReX_GpuQualifiers.H:20
Convenience header for the core AMReX GPU facilities.
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.
__host__ __device__ Long numPts() const noexcept
Return the number of points contained in the BoxND.
Definition AMReX_Box.H:385
A container for storing items in a set of bins.
Definition AMReX_DenseBins.H:155
std::conditional_t< IsParticleTileData< T >(), T, const T * > const_pointer_type
Definition AMReX_DenseBins.H:164
const index_type * permutationPtr() const noexcept
returns const pointer to the permutation array
Definition AMReX_DenseBins.H:621
std::conditional_t< IsParticleTileData< T >(), const T &, const T * > const_pointer_input_type
Definition AMReX_DenseBins.H:169
DenseBinIteratorFactory< T > getBinIteratorFactory() const noexcept
returns a GPU-capable object that can create iterators over the items in a bin.
Definition AMReX_DenseBins.H:630
const index_type * binsPtr() const noexcept
returns the const pointer to the bins array
Definition AMReX_DenseBins.H:627
index_type * offsetsPtr() noexcept
returns the pointer to the offsets array
Definition AMReX_DenseBins.H:615
index_type * permutationPtr() noexcept
returns the pointer to the permutation array
Definition AMReX_DenseBins.H:612
void build(BinPolicy::OpenMPBinPolicy, N nitems, const_pointer_input_type v, const Box &bx, F const &f)
Populate the bins with a set of items.
Definition AMReX_DenseBins.H:392
void build(BinPolicy::GPUBinPolicy, N nitems, const_pointer_input_type v, const Box &bx, F const &f)
Populate the bins with a set of items.
Definition AMReX_DenseBins.H:263
void build(N nitems, const_pointer_input_type v, const Box &bx, F &&f)
Populate the bins with a set of items.
Definition AMReX_DenseBins.H:208
int index_type
Definition AMReX_DenseBins.H:159
void build(BinPolicy::SerialBinPolicy, N nitems, const_pointer_input_type v, const Box &bx, F const &f)
Populate the bins with a set of items.
Definition AMReX_DenseBins.H:533
void build(BinPolicy::GPUBinPolicy, N nitems, const_pointer_input_type v, int nbins, F const &f)
Populate the bins with a set of items.
Definition AMReX_DenseBins.H:304
const index_type * offsetsPtr() const noexcept
returns const pointer to the offsets array
Definition AMReX_DenseBins.H:624
void build(BinPolicy::OpenMPBinPolicy, N nitems, const_pointer_input_type v, int nbins, F const &f)
Populate the bins with a set of items.
Definition AMReX_DenseBins.H:434
void build(N nitems, const_pointer_input_type v, int nbins, F &&f)
Populate the bins with a set of items.
Definition AMReX_DenseBins.H:235
Long numItems() const noexcept
the number of items in the container
Definition AMReX_DenseBins.H:606
void build(BinPolicy::SerialBinPolicy, N nitems, const_pointer_input_type v, int nbins, F const &f)
Populate the bins with a set of items.
Definition AMReX_DenseBins.H:575
Long numBins() const noexcept
the number of bins in the container
Definition AMReX_DenseBins.H:609
index_type * binsPtr() noexcept
returns the pointer to the bins array
Definition AMReX_DenseBins.H:618
static void streamSynchronize() noexcept
Definition AMReX_GpuDevice.cpp:861
static constexpr int warp_size
Definition AMReX_GpuDevice.H:236
Dynamically allocated vector for trivially copyable data.
Definition AMReX_PODVector.H:308
amrex_long Long
Definition AMReX_INT.H:30
OutIter exclusive_scan(InIter begin, InIter end, OutIter result)
Definition AMReX_Scan.H:1181
__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
Arena * The_Arena()
Definition AMReX_Arena.cpp:829
__host__ __device__ constexpr const T & min(const T &a, const T &b) noexcept
Definition AMReX_Algorithm.H:31
__host__ __device__ constexpr const T & max(const T &a, const T &b) noexcept
Definition AMReX_Algorithm.H:53
static constexpr OpenMPBinPolicy OpenMP
Definition AMReX_DenseBins.H:98
static constexpr GPUBinPolicy Default
Definition AMReX_DenseBins.H:102
static constexpr SerialBinPolicy Serial
Definition AMReX_DenseBins.H:99
static constexpr GPUBinPolicy GPU
Definition AMReX_DenseBins.H:97
__host__ __device__ AMREX_FORCE_INLINE T Add(T *sum, T value) noexcept
Definition AMReX_GpuAtomic.H:200
void copy(HostToDevice, InIter begin, InIter end, OutIter result) noexcept
A host-to-device copy routine. Note this is just a wrapper around memcpy, so it assumes contiguous st...
Definition AMReX_GpuContainers.H:128
static constexpr DeviceToDevice deviceToDevice
Definition AMReX_GpuContainers.H:107
gpuStream_t gpuStream() noexcept
Definition AMReX_GpuDevice.H:291
constexpr int get_max_threads()
Definition AMReX_OpenMP.H:36
Definition AMReX_Amr.cpp:50
void ParallelFor(TypeList< CTOs... > ctos, std::array< int, sizeof...(CTOs)> const &runtime_options, T N, F &&f)
Definition AMReX_CTOParallelForImpl.H:202
const int[]
Definition AMReX_BLProfiler.cpp:1665
Definition AMReX_BinIterator.H:24
Definition AMReX_DenseBins.H:93
Definition AMReX_DenseBins.H:94
Definition AMReX_DenseBins.H:95
Definition AMReX_DenseBins.H:110
DenseBinIteratorFactory(const Gpu::DeviceVector< index_type > &offsets, const Gpu::DeviceVector< index_type > &permutation, const T *items)
Definition AMReX_DenseBins.H:118
const index_type * m_permutation_ptr
Definition AMReX_DenseBins.H:133
int index_type
Definition AMReX_DenseBins.H:111
__host__ __device__ BinIterator< T > getBinIterator(const int bin_number) const noexcept
Definition AMReX_DenseBins.H:127
const_pointer_type m_items
Definition AMReX_DenseBins.H:134
const index_type * m_offsets_ptr
Definition AMReX_DenseBins.H:132
std::conditional_t< IsParticleTileData< T >(), T, const T * > const_pointer_type
Definition AMReX_DenseBins.H:116
Test if a given type T is callable with arguments of type Args...
Definition AMReX_TypeTraits.H:208