Line data Source code
1 0 : // Distributed under the MIT License. 2 : // See LICENSE.txt for details. 3 : 4 : // This file is tested in Test_InboxTags.cpp 5 : 6 : #pragma once 7 : 8 : #include <atomic> 9 : #include <boost/container/small_vector.hpp> 10 : #include <cstddef> 11 : #include <map> 12 : #include <tuple> 13 : #include <utility> 14 : 15 : #include "Domain/Structure/DirectionalId.hpp" 16 : #include "Domain/Structure/MaxNumberOfNeighbors.hpp" 17 : #include "Evolution/DiscontinuousGalerkin/BoundaryData.hpp" 18 : #include "Parallel/StaticSpscQueue.hpp" 19 : #include "Time/TimeStepId.hpp" 20 : 21 : /// \cond 22 : namespace PUP { 23 : class er; 24 : } // namespace PUP 25 : /// \endcond 26 : 27 : namespace evolution::dg { 28 : /*! 29 : * \brief Holds the data in the different directions for the nodegroup 30 : * `DgElementArray` implementation. 31 : * 32 : * The reason for this class is to reduce contention between cores and to 33 : * allow the use of a Single-Producer-Single-Consumer (SPSC) queue instead of 34 : * an MPMC queue. This has significant performance improvements since it 35 : * drastically reduces contention. 36 : * 37 : * \warning Only `AtomicInboxBoundaryData` with zero messages can be 38 : * move constructed or serialized. This is necessary in order to be 39 : * 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 : /*! 78 : * Moves data from the SPSC queues into the `messages` map. 79 : */ 80 1 : void collect_messages(); 81 : 82 : /*! 83 : * Set a lower bound on the number of messages required for the 84 : * algorithm to make progress since the most recent call to 85 : * `collect_messages`. After that number of new messages have been 86 : * received, `BoundaryCorrectionAndGhostCellsInbox` will restart 87 : * the algorithm. 88 : * 89 : * \return whether enough messages have been received. 90 : */ 91 1 : bool set_missing_messages(size_t count); 92 : 93 0 : void pup(PUP::er& p); 94 : 95 : // We use 20 entries in the SPSC under the assumption that each neighbor 96 : // will never insert more than 20 entries before the element uses 97 : // them. While in practice a smaller buffer could be used, this is to 98 : // safeguard against future features. 99 : std::array<Parallel::StaticSpscQueue< 100 : std::tuple<::TimeStepId, stored_type, DirectionalId<Dim>>, 20>, 101 : maximum_number_of_neighbors(Dim)> 102 0 : boundary_data_in_directions{}; 103 : std::map<TimeStepId, boost::container::small_vector< 104 : std::pair<DirectionalId<Dim>, stored_type>, 105 : maximum_number_of_neighbors(Dim)>> 106 0 : messages{}; 107 0 : std::atomic_int missing_messages{}; 108 : 109 : // The number of messages in the SPSC queues is 110 : // passed_missing_messages - missing_messages - processed_messages. 111 : // The various manipulations change these fields as follows: 112 : // 113 : // New message => --missing_messages (by insert_into_inbox) 114 : // collect_messages => processed_messages += num unqueued messages 115 : // set_missing_messages(count) => 116 : // missing_messages += count + processed_messages - passed_missing_messages 117 : // processed_messages = 0 118 : // passed_missing_messages = count 119 : // 120 : // The processed_messages field exists (rather than just including 121 : // it in missing_messages) so that missing_messages is positive if 122 : // and only if the element is blocked on messages in this queue. 123 0 : int processed_messages{}; 124 0 : int passed_missing_messages{}; 125 : }; 126 : 127 : /// \brief `std::true` if `T` is a `AtomicInboxBoundaryData` 128 : template <typename T> 129 1 : struct is_atomic_inbox_boundary_data : std::false_type {}; 130 : 131 : /// \cond 132 : template <size_t Dim> 133 : struct is_atomic_inbox_boundary_data<AtomicInboxBoundaryData<Dim>> 134 : : std::true_type {}; 135 : /// \endcond 136 : 137 : /// \brief `true` if `T` is a `AtomicInboxBoundaryData` 138 : template <typename T> 139 1 : constexpr size_t is_atomic_inbox_boundary_data_v = 140 : is_atomic_inbox_boundary_data<T>::value; 141 : } // namespace evolution::dg