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 <cstddef> 10 : #include <map> 11 : #include <tuple> 12 : 13 : #include "Domain/Structure/DirectionalId.hpp" 14 : #include "Domain/Structure/DirectionalIdMap.hpp" 15 : #include "Domain/Structure/MaxNumberOfNeighbors.hpp" 16 : #include "Evolution/DiscontinuousGalerkin/BoundaryData.hpp" 17 : #include "Parallel/StaticSpscQueue.hpp" 18 : #include "Time/TimeStepId.hpp" 19 : 20 : /// \cond 21 : namespace PUP { 22 : class er; 23 : } // namespace PUP 24 : /// \endcond 25 : 26 : namespace evolution::dg { 27 : /*! 28 : * \brief Holds the data in the different directions for the nodegroup 29 : * `DgElementArray` implementation. 30 : * 31 : * The reason for this class is to reduce contention between cores and to 32 : * allow the use of a Single-Producer-Single-Consumer (SPSC) queue instead of 33 : * an MPMC queue. This has significant performance improvements since it 34 : * drastically reduces contention. 35 : * 36 : * \warning Only `AtomicInboxBoundaryData` with zero messages can be 37 : * move constructed or serialized. This is necessary in order to be 38 : * able to serialize a 39 : * `std::unordered_map<Key,AtomicInboxBoundaryData>`. 40 : */ 41 : template <size_t Dim> 42 1 : struct AtomicInboxBoundaryData { 43 0 : using stored_type = evolution::dg::BoundaryData<Dim>; 44 0 : AtomicInboxBoundaryData() = default; 45 0 : AtomicInboxBoundaryData(const AtomicInboxBoundaryData&) = delete; 46 0 : AtomicInboxBoundaryData& operator=(const AtomicInboxBoundaryData&) = delete; 47 0 : AtomicInboxBoundaryData(AtomicInboxBoundaryData&& rhs) noexcept; 48 0 : AtomicInboxBoundaryData& operator=(AtomicInboxBoundaryData&&) noexcept = 49 : delete; 50 0 : ~AtomicInboxBoundaryData() = default; 51 : 52 : /*! 53 : * Computes the 1d index into the `boundary_data_in_directions` array 54 : * for a specific `directional_id` that has been re-oriented using the 55 : * `OrientationMap` to be put in the same block frame as the element that is 56 : * receiving the data (i.e. that whose inbox this is being inserted into). 57 : * 58 : * The hash is computed as 59 : * \f{align}{ 60 : * 2^D d + 2^{D-1} s + e 61 : * \f} 62 : * where \f$D\f$ is the number of spatial dimensions, \f$d\f$ is the logical 63 : * dimension of the direction to the neighbor from the element whose inbox 64 : * this is, \f$s\f$ is the side in the logical dimension \f$d\f$ with a value 65 : * of 1 for upper and 0 for lower, and \f$e\f$ is a hash of the index of the 66 : * `SegmentId`'s of the neighbor's `ElementId` for the dimensions other than 67 : * \f$d\f$. In particular: for \f$d=1\f$, \f$e\f$ is 0 (1) if 68 : * the `SegmentId` index along the face is even (odd); and for \f$d = 3\f$ 69 : * \f$e\f$ is 0 (1, 2, 3) if the `SegmentId` indices along the face are both 70 : * even (lower dim odd, higher dim odd, both dims odd). The element segment 71 : * hash is computed as the logical `and` of the `SegmentID`'s index in that 72 : * direction, left shifted by which direction on the face it is. 73 : */ 74 1 : static size_t index(const DirectionalId<Dim>& directional_id); 75 : 76 : /*! 77 : * Moves data from the SPSC queues into the `messages` map. 78 : */ 79 1 : void collect_messages(); 80 : 81 : /*! 82 : * Set a lower bound on the number of messages required for the 83 : * algorithm to make progress since the most recent call to 84 : * `collect_messages`. After that number of new messages have been 85 : * received, `BoundaryCorrectionAndGhostCellsInbox` will restart 86 : * the algorithm. 87 : * 88 : * \return whether enough messages have been received. 89 : */ 90 1 : bool set_missing_messages(size_t count); 91 : 92 0 : void pup(PUP::er& p); 93 : 94 : // We use 20 entries in the SPSC under the assumption that each neighbor 95 : // will never insert more than 20 entries before the element uses 96 : // them. While in practice a smaller buffer could be used, this is to 97 : // safeguard against future features. 98 : std::array<Parallel::StaticSpscQueue< 99 : std::tuple<::TimeStepId, stored_type, DirectionalId<Dim>>, 20>, 100 : maximum_number_of_neighbors(Dim)> 101 0 : boundary_data_in_directions{}; 102 0 : std::map<TimeStepId, DirectionalIdMap<Dim, stored_type>> messages{}; 103 0 : std::atomic_int missing_messages{}; 104 : 105 : // The number of messages in the SPSC queues is 106 : // passed_missing_messages - missing_messages - processed_messages. 107 : // The various manipulations change these fields as follows: 108 : // 109 : // New message => --missing_messages (by insert_into_inbox) 110 : // collect_messages => processed_messages += num unqueued messages 111 : // set_missing_messages(count) => 112 : // missing_messages += count + processed_messages - passed_missing_messages 113 : // processed_messages = 0 114 : // passed_missing_messages = count 115 : // 116 : // The processed_messages field exists (rather than just including 117 : // it in missing_messages) so that missing_messages is positive if 118 : // and only if the element is blocked on messages in this queue. 119 0 : int processed_messages{}; 120 0 : int passed_missing_messages{}; 121 : }; 122 : 123 : /// \brief `std::true` if `T` is a `AtomicInboxBoundaryData` 124 : template <typename T> 125 1 : struct is_atomic_inbox_boundary_data : std::false_type {}; 126 : 127 : /// \cond 128 : template <size_t Dim> 129 : struct is_atomic_inbox_boundary_data<AtomicInboxBoundaryData<Dim>> 130 : : std::true_type {}; 131 : /// \endcond 132 : 133 : /// \brief `true` if `T` is a `AtomicInboxBoundaryData` 134 : template <typename T> 135 1 : constexpr size_t is_atomic_inbox_boundary_data_v = 136 : is_atomic_inbox_boundary_data<T>::value; 137 : } // namespace evolution::dg