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
|