Line data Source code
1 0 : // Distributed under the MIT License. 2 : // See LICENSE.txt for details. 3 : 4 : #pragma once 5 : 6 : #include <atomic> 7 : #include <cstddef> 8 : 9 : #include "Domain/Structure/MaxNumberOfNeighbors.hpp" 10 : #include "Evolution/DiscontinuousGalerkin/BoundaryData.hpp" 11 : #include "Parallel/StaticSpscQueue.hpp" 12 : #include "Time/TimeStepId.hpp" 13 : 14 : /// \cond 15 : template <size_t Dim> 16 : struct DirectionalId; 17 : /// \endcond 18 : 19 : namespace evolution::dg { 20 : /*! 21 : * \brief Holds the data in the different directions for the nodegroup 22 : * `DgElementArray` implementation. 23 : * 24 : * The reason for this class is to reduce contention between cores and to 25 : * allow the use of a Single-Producer-Single-Consumer (SPSC) queue instead of 26 : * an MPMC queue. This has significant performance improvements since it 27 : * drastically reduces contention. 28 : * 29 : * The uint message counter is used to count how many neighbors have contributed 30 : * for the next time. This is used to delay calling `perform_algorithm()` in 31 : * order to reduce the number of messages we send through the runtime 32 : * system. The `number_of_neighbors` is used to track the number of expected 33 : * messages. Note that some additional logic is needed also for supporting 34 : * local time stepping, since not every message entry "counts" since it 35 : * depends on the time level of neighboring elements. 36 : * 37 : * \warning Only `AtomicInboxBoundaryData` with zero messages can be move 38 : * constructed. A non-zero number of neighbors is allowed. This is necessary 39 : * in order to be able to serialize a 40 : * `std::unordered_map<Key,AtomicInboxBoundaryData>`. 41 : */ 42 : template <size_t Dim> 43 1 : struct AtomicInboxBoundaryData { 44 0 : using stored_type = evolution::dg::BoundaryData<Dim>; 45 0 : AtomicInboxBoundaryData() = default; 46 0 : AtomicInboxBoundaryData(const AtomicInboxBoundaryData&) = delete; 47 0 : AtomicInboxBoundaryData& operator=(const AtomicInboxBoundaryData&) = delete; 48 0 : AtomicInboxBoundaryData(AtomicInboxBoundaryData&& rhs) noexcept; 49 0 : AtomicInboxBoundaryData& operator=(AtomicInboxBoundaryData&&) noexcept = 50 : delete; 51 0 : ~AtomicInboxBoundaryData() = default; 52 : 53 : /*! 54 : * Computes the 1d index into the `boundary_data_in_directions` array 55 : * for a specific `directional_id` that has been re-oriented using the 56 : * `OrientationMap` to be put in the same block frame as the element that is 57 : * receiving the data (i.e. that whose inbox this is being inserted into). 58 : * 59 : * The hash is computed as 60 : * \f{align}{ 61 : * 2^D d + 2^{D-1} s + e 62 : * \f} 63 : * where \f$D\f$ is the number of spatial dimensions, \f$d\f$ is the logical 64 : * dimension of the direction to the neighbor from the element whose inbox 65 : * this is, \f$s\f$ is the side in the logical dimension \f$d\f$ with a value 66 : * of 1 for upper and 0 for lower, and \f$e\f$ is a hash of the index of the 67 : * `SegmentId`'s of the neighbor's `ElementId` for the dimensions other than 68 : * \f$d\f$. In particular: for \f$d=1\f$, \f$e\f$ is 0 (1) if 69 : * the `SegmentId` index along the face is even (odd); and for \f$d = 3\f$ 70 : * \f$e\f$ is 0 (1, 2, 3) if the `SegmentId` indices along the face are both 71 : * even (lower dim odd, higher dim odd, both dims odd). The element segment 72 : * hash is computed as the logical `and` of the `SegmentID`'s index in that 73 : * direction, left shifted by which direction on the face it is. 74 : */ 75 1 : static size_t index(const DirectionalId<Dim>& directional_id); 76 : 77 0 : void pup(PUP::er& p); 78 : 79 : // We use 20 entries in the SPSC under the assumption that each neighbor 80 : // will never insert more than 20 entries before the element uses 81 : // them. While in practice a smaller buffer could be used, this is to 82 : // safeguard against future features. 83 : std::array<Parallel::StaticSpscQueue< 84 : std::tuple<::TimeStepId, stored_type, DirectionalId<Dim>>, 20>, 85 : maximum_number_of_neighbors(Dim)> 86 0 : boundary_data_in_directions{}; 87 0 : std::atomic_uint message_count{}; 88 0 : std::atomic_uint number_of_neighbors{}; 89 : }; 90 : 91 : /// \brief `std::true` if `T` is a `AtomicInboxBoundaryData` 92 : template <typename T> 93 1 : struct is_atomic_inbox_boundary_data : std::false_type {}; 94 : 95 : /// \cond 96 : template <size_t Dim> 97 : struct is_atomic_inbox_boundary_data<AtomicInboxBoundaryData<Dim>> 98 : : std::true_type {}; 99 : /// \endcond 100 : 101 : /// \brief `true` if `T` is a `AtomicInboxBoundaryData` 102 : template <typename T> 103 1 : constexpr size_t is_atomic_inbox_boundary_data_v = 104 : is_atomic_inbox_boundary_data<T>::value; 105 : } // namespace evolution::dg