NEURON
have2want.hpp
Go to the documentation of this file.
1 #pragma once
2 
3 #include <numeric>
4 #include <unordered_map>
5 #include <vector>
6 
7 /*
8 A rank owns a set of T keys and wants information associated with
9 a set of T keys owned by unknown ranks. Owners do not know which
10 ranks want their information. Ranks that want info do not know which ranks
11 own that info.
12 
13 The have_to_want function returns two new vectors of keys along with
14 associated count and displacement vectors of length nhost and nhost+1
15 respectively. Note that a send_to_want_displ[i+1] =
16  send_to_want_cnt[i] + send_to_want_displ[i] .
17 
18 send_to_want[send_to_want_displ[i] to send_to_want_displ[i+1]] contains
19 the keys from this rank for which rank i wants information.
20 
21 recv_from_have[recv_from_have_displ[i] to recv_from_have_displ[i+1] contains
22 the keys from which rank i is sending information to this rank.
23 
24 Note that on rank i, the order of keys in the rank j area of send_to_want
25 is the same order of keys on rank j in the ith area in recv_from_have.
26 
27 The rendezvous_rank function is used to parallelize this computation
28 and minimize memory usage so that no single rank ever needs to know all keys.
29 */
30 
31 // round robin rendezvous rank function
32 template <typename T>
33 int rendezvous_rank(const T& key) {
34  return key % nrnmpi_numprocs;
35 }
36 
37 template <typename T>
38 struct Data {
39  std::vector<T> data{};
40  std::vector<int> cnt{};
41  std::vector<int> displ{};
42 };
43 
44 static std::vector<int> cnt2displ(const std::vector<int>& cnt) {
45  std::vector<int> displ(nrnmpi_numprocs + 1);
46  std::partial_sum(cnt.cbegin(), cnt.cend(), displ.begin() + 1);
47  return displ;
48 }
49 
50 static std::vector<int> srccnt2destcnt(std::vector<int> srccnt) {
51  std::vector<int> destcnt(nrnmpi_numprocs);
52  nrnmpi_int_alltoall(srccnt.data(), destcnt.data(), 1);
53  return destcnt;
54 }
55 
56 template <typename T, typename F>
57 static std::tuple<Data<T>, Data<T>> rendezvous_rank_get(const std::vector<T>& data,
58  F alltoall_function) {
59  int nhost = nrnmpi_numprocs;
60 
61  Data<T> s;
62  // count what gets sent
63  s.cnt = std::vector<int>(nhost);
64 
65  for (const auto& e: data) {
66  int r = rendezvous_rank<T>(e);
67  s.cnt[r] += 1;
68  }
69 
70  s.displ = cnt2displ(s.cnt);
71  s.data.resize(s.displ[nhost] + 1);
72 
73  Data<T> r;
74  r.cnt = srccnt2destcnt(s.cnt);
75  r.displ = cnt2displ(r.cnt);
76  r.data.resize(r.displ[nhost]);
77  // scatter data into sdata by recalculating s.cnt.
78  std::fill(s.cnt.begin(), s.cnt.end(), 0);
79  for (const auto& e: data) {
80  int rank = rendezvous_rank<T>(e);
81  s.data[s.displ[rank] + s.cnt[rank]] = e;
82  s.cnt[rank] += 1;
83  }
84  alltoall_function(s, r);
85  return {s, r};
86 }
87 
88 template <typename T, typename F>
89 std::pair<Data<T>, Data<T>> have_to_want(const std::vector<T>& have,
90  const std::vector<T>& want,
91  F alltoall_function) {
92  // 1) Send have and want to the rendezvous ranks.
93  // 2) Rendezvous rank matches have and want.
94  // 3) Rendezvous ranks tell the want ranks which ranks own the keys
95  // 4) Ranks that want tell owner ranks where to send.
96 
97  int nhost = nrnmpi_numprocs;
98 
99  // 1) Send have and want to the rendezvous ranks.
100 
101  // hash table of key2rank. Will also need it for matching have and want
102  std::unordered_map<T, int> havekey2rank{};
103  {
104  auto [_, have_r] = rendezvous_rank_get<T>(have, alltoall_function);
105  // assume it is an error if two ranks have the same key so create
106  havekey2rank.reserve(have_r.displ[nhost] + 1);
107  for (int r = 0; r < nhost; ++r) {
108  for (int i = 0; i < have_r.cnt[r]; ++i) {
109  T key = have_r.data[have_r.displ[r] + i];
110  if (havekey2rank.find(key) != havekey2rank.end()) {
112  "internal error in have_to_want: key %lld owned by multiple ranks\n",
113  (long long) key);
114  }
115  havekey2rank[key] = r;
116  }
117  }
118  }
119 
120  auto [want_s, want_r] = rendezvous_rank_get<T>(want, alltoall_function);
121 
122  // 2) Rendezvous rank matches have and want.
123  // we already have made the havekey2rank map.
124  // Create an array parallel to want_r_data which contains the ranks that
125  // have that data.
126  int n = want_r.displ[nhost];
127  std::vector<int> want_r_ownerranks(n);
128  for (int r = 0; r < nhost; ++r) {
129  for (int i = 0; i < want_r.cnt[r]; ++i) {
130  int ix = want_r.displ[r] + i;
131  T key = want_r.data[ix];
132  auto search = havekey2rank.find(key);
133  if (search == havekey2rank.end()) {
135  "internal error in have_to_want: key = %lld is wanted but does not exist\n",
136  (long long) key);
137  }
138  want_r_ownerranks[ix] = search->second;
139  }
140  }
141 
142  // 3) Rendezvous ranks tell the want ranks which ranks own the keys
143  // The ranks that want keys need to know the ranks that own those keys.
144  // The want_s_ownerranks will be parallel to the want_s_data.
145  // That is, each item defines the rank from which information associated
146  // with that key is coming from
147  std::vector<int> want_s_ownerranks(want_s.displ[nhost]);
148  if (nrn_sparse_partrans > 0) {
149  nrnmpi_int_alltoallv_sparse(want_r_ownerranks.data(),
150  want_r.cnt.data(),
151  want_r.displ.data(),
152  want_s_ownerranks.data(),
153  want_s.cnt.data(),
154  want_s.displ.data());
155  } else {
156  nrnmpi_int_alltoallv(want_r_ownerranks.data(),
157  want_r.cnt.data(),
158  want_r.displ.data(),
159  want_s_ownerranks.data(),
160  want_s.cnt.data(),
161  want_s.displ.data());
162  }
163 
164  // 4) Ranks that want tell owner ranks where to send.
165  // Finished with the rendezvous ranks. The ranks that want keys know the
166  // owner ranks for those keys. The next step is for the want ranks to
167  // tell the owner ranks where to send.
168  // The parallel want_s_ownerranks and want_s_data are now uselessly ordered
169  // by rendezvous rank. Reorganize so that want ranks can tell owner ranks
170  // what they want.
171  n = want_s.displ[nhost];
172  for (int i = 0; i < nhost; ++i) {
173  want_s.cnt[i] = 0;
174  }
175  std::vector<T> old_want_s_data(n);
176  std::swap(old_want_s_data, want_s.data);
177  // compute the counts
178  for (int i = 0; i < n; ++i) {
179  int r = want_s_ownerranks[i];
180  ++want_s.cnt[r];
181  }
182  want_s.displ = cnt2displ(want_s.cnt);
183  for (int i = 0; i < nhost; ++i) {
184  want_s.cnt[i] = 0;
185  } // recount while filling
186  for (int i = 0; i < n; ++i) {
187  int r = want_s_ownerranks[i];
188  T key = old_want_s_data[i];
189  want_s.data[want_s.displ[r] + want_s.cnt[r]] = key;
190  ++want_s.cnt[r];
191  }
192 
193  Data<T> new_want_r{};
194  new_want_r.cnt = srccnt2destcnt(want_s.cnt);
195  new_want_r.displ = cnt2displ(new_want_r.cnt);
196  new_want_r.data.resize(new_want_r.displ[nhost]);
197  alltoall_function(want_s, new_want_r);
198  // now the want_r_data on the have_ranks are grouped according to the ranks
199  // that want those keys.
200 
201  return {new_want_r, want_s};
202 }
static void nrnmpi_int_alltoallv(const int *s, const int *scnt, const int *sdispl, int *r, int *rcnt, int *rdispl)
#define cnt
Definition: tqueue.hpp:44
#define key
Definition: tqueue.hpp:45
#define i
Definition: md1redef.h:19
void hoc_execerr_ext(const char *fmt,...)
printf style specification of hoc_execerror message.
Definition: fileio.cpp:828
static std::vector< int > srccnt2destcnt(std::vector< int > srccnt)
Definition: have2want.hpp:50
static std::tuple< Data< T >, Data< T > > rendezvous_rank_get(const std::vector< T > &data, F alltoall_function)
Definition: have2want.hpp:57
int rendezvous_rank(const T &key)
Definition: have2want.hpp:33
std::pair< Data< T >, Data< T > > have_to_want(const std::vector< T > &have, const std::vector< T > &want, F alltoall_function)
Definition: have2want.hpp:89
static std::vector< int > cnt2displ(const std::vector< int > &cnt)
Definition: have2want.hpp:44
int const size_t const size_t n
Definition: nrngsl.h:10
s
Definition: multisend.cpp:521
int nrn_sparse_partrans
Definition: init.cpp:112
static double nhost(void *v)
Definition: ocbbs.cpp:201
std::vector< T > data
Definition: have2want.hpp:39
std::vector< int > cnt
Definition: have2want.hpp:40
std::vector< int > displ
Definition: have2want.hpp:41