Teuchos - Trilinos Tools Package  Version of the Day
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Teuchos_PerformanceMonitorBase.cpp
1 // @HEADER
2 // *****************************************************************************
3 // Teuchos: Common Tools Package
4 //
5 // Copyright 2004 NTESS and the Teuchos contributors.
6 // SPDX-License-Identifier: BSD-3-Clause
7 // *****************************************************************************
8 // @HEADER
9 
11 #include "Teuchos_CommHelpers.hpp"
12 #include "Teuchos_DefaultComm.hpp"
13 #include <iterator> // std::back_inserter
14 
15 namespace Teuchos {
16 
17  namespace {
18 
19  // Pack the given array of strings into a single string with an
20  // offsets array. This is a helper routine of \c sendStrings().
21  // For strings[k], offsets[k] gives its start position in
22  // packedString, and offsets[k+1]-ofsets[k] gives its length.
23  // Thus, packedString.substr (offsets[k], offsets[k+1]-offsets[k])
24  // == strings[k].
25  //
26  // \param packedString [out] The packed string. It will be
27  // resized on output as necessary.
28  //
29  // \param offsets [out] Array of offsets, of length one more than
30  // the number of strings in the \c strings array. Thus, the
31  // offsets array always has positive length.
32  //
33  // \param strings [in] The array of strings to pack. It may have
34  // zero length. In that case, on output, the offsets array will
35  // have length one, offsets[0] = 0, and packedString will have
36  // length zero.
37  //
38  // Although std::string could be considered an array, it does not
39  // have a size_type typedef. Instead, it uses size_t for that
40  // purpose.
41  void
42  packStringsForSend (std::string& packedString,
43  Array<size_t>& offsets,
44  const Array<std::string>& strings)
45  {
46  using std::cerr;
47  using std::endl;
48  using std::string;
49 
50  const bool debug = false;
51 
52  // Compute index offsets in the packed string.
53  offsets.resize (strings.size() + 1);
54  size_t totalLength = 0;
55  Array<size_t>::size_type offsetsIndex = 0;
56  for (Array<string>::const_iterator it = strings.begin();
57  it != strings.end(); ++it, ++offsetsIndex)
58  {
59  offsets[offsetsIndex] = totalLength;
60  totalLength += it->size();
61  }
62  offsets[offsetsIndex] = totalLength;
63 
64  // Pack the array of strings into the packed string.
65  packedString.resize (totalLength);
66  string::iterator packedStringIter = packedString.begin();
67  for (Array<string>::const_iterator it = strings.begin();
68  it != strings.end(); ++it)
69  packedStringIter = std::copy (it->begin(), it->end(), packedStringIter);
70 
71  if (debug)
72  {
73  std::ostringstream out;
74  RCP<const Comm<int> > pComm = DefaultComm<int>::getComm ();
75  out << "Proc " << pComm->getRank() << ": in pack: offsets = [";
76  for (Array<size_t>::const_iterator it = offsets.begin();
77  it != offsets.end(); ++it)
78  {
79  out << *it;
80  if (it + 1 != offsets.end())
81  out << ", ";
82  }
83  out << "], packedString = " << packedString << endl;
84  cerr << out.str();
85  }
86  }
87 
88  // \brief Send an array of strings.
89  //
90  // Teuchos::send() (or rather, Teuchos::SerializationTraits)
91  // doesn't know how to send an array of strings. This function
92  // packs an array of strings into a single string with an offsets
93  // array, and sends the offsets array (and the packed string, if
94  // it is not empty).
95  void
96  sendStrings (const Comm<int>& comm, // in
97  const Array<std::string>& strings, // in
98  const int destRank) // in
99  {
100  // Pack the string array into the packed string, and compute
101  // offsets.
102  std::string packedString;
103  Array<size_t> offsets;
104  packStringsForSend (packedString, offsets, strings);
105  TEUCHOS_TEST_FOR_EXCEPTION(offsets.size() == 0, std::logic_error,
106  "packStringsForSend() returned a zero-length offsets "
107  "array on MPI Proc " << comm.getRank() << ", to be "
108  "sent to Proc " << destRank << ". The offsets array "
109  "should always have positive length. Please report "
110  "this bug to the Teuchos developers.");
111 
112  // Send the count of offsets.
113  send (comm, offsets.size(), destRank);
114 
115  // Send the array of offsets. There is always at least one
116  // element in the offsets array, so we can always take the
117  // address of the first element.
118  const int offsetsSendCount = static_cast<int> (offsets.size());
119  send (comm, offsetsSendCount, &offsets[0], destRank);
120 
121  // Now send the packed string. It may be empty if the strings
122  // array has zero elements or if all the strings in the array
123  // are empty. If the packed string is empty, we don't send
124  // anything, since the receiving process already knows (from the
125  // offsets array) not to expect anything.
126  const int stringSendCount = static_cast<int> (packedString.size());
127  if (stringSendCount > 0)
128  send (comm, stringSendCount, &packedString[0], destRank);
129  }
130 
131  void
132  unpackStringsAfterReceive (Array<std::string>& strings,
133  const std::string& packedString,
134  const Array<size_t> offsets)
135  {
136  const bool debug = false;
137  if (debug)
138  {
139  using std::cerr;
140  using std::endl;
141 
142  std::ostringstream out;
143  RCP<const Comm<int> > pComm = DefaultComm<int>::getComm ();
144  out << "Proc " << pComm->getRank() << ": in unpack: offsets = [";
145  for (Array<size_t>::const_iterator it = offsets.begin();
146  it != offsets.end(); ++it)
147  {
148  out << *it;
149  if (it + 1 != offsets.end())
150  out << ", ";
151  }
152  out << "], packedString = " << packedString << endl;
153  cerr << out.str();
154  }
155  TEUCHOS_TEST_FOR_EXCEPTION(offsets.size() == 0, std::logic_error,
156  "The offsets array has length zero, which does not "
157  "make sense. Even when sending / receiving zero "
158  "strings, the offsets array should have one entry "
159  "(namely, zero).");
160  const Array<size_t>::size_type numStrings = offsets.size() - 1;
161  strings.resize (numStrings);
162  for (Array<size_t>::size_type k = 0; k < numStrings; ++k)
163  { // Exclusive index range in the packed string in which to
164  // find the current string.
165  const size_t start = offsets[k];
166  const size_t end = offsets[k+1];
167  strings[k] = packedString.substr (start, end - start);
168  }
169  }
170 
171  // Function corresponding to \c sendStrings() that receives an
172  // array of strings (Array<std::string>) in packed form.
173  void
174  receiveStrings (const Comm<int>& comm,
175  const int sourceRank,
176  Array<std::string>& strings)
177  {
178  // Receive the number of offsets. There should always be at
179  // least 1 offset.
180  Array<size_t>::size_type numOffsets = 0;
181  receive (comm, sourceRank, &numOffsets);
182  TEUCHOS_TEST_FOR_EXCEPTION(numOffsets == 0, std::logic_error,
183  "Invalid number of offsets numOffsets=" << numOffsets
184  << " received on MPI Rank " << comm.getRank()
185  << " from Rank " << sourceRank << ". Please report "
186  "this bug to the Teuchos developers.");
187 
188  // Receive the array of offsets.
189  Array<size_t> offsets (numOffsets);
190  const int offsetsRecvCount = static_cast<int> (numOffsets);
191  receive (comm, sourceRank, offsetsRecvCount, &offsets[0]);
192 
193  // If the packed string is nonempty, receive the packed string,
194  // and unpack it. The last entry of offsets is the length of
195  // the packed string.
196  std::string packedString (offsets.back(), ' ');
197  const int stringRecvCount = static_cast<int> (offsets.back());
198  if (stringRecvCount > 0)
199  {
200  receive (comm, sourceRank, stringRecvCount, &packedString[0]);
201  unpackStringsAfterReceive (strings, packedString, offsets);
202  }
203  }
204 
205 
206  void
207  broadcastStringsHelper (const Comm<int>& comm,
208  const int myRank,
209  const int left,
210  const int right,
211  Array<std::string>& globalNames)
212  {
213  // If left >= right, there is only one process, so we don't need
214  // to do anything.
215  //
216  // If left < right, then split the inclusive interval [left,
217  // right] into [left, mid-1] and [mid, right]. Send from left
218  // to mid, and recurse on the two subintervals.
219  if (left >= right)
220  return;
221  else
222  {
223  const int mid = left + (right - left + 1) / 2;
224 
225  // This could be optimized further on the sending rank, by
226  // prepacking the strings so that they don't have to be
227  // packed more than once.
228  if (myRank == left)
229  sendStrings (comm, globalNames, mid);
230  else if (myRank == mid)
231  receiveStrings (comm, left, globalNames);
232 
233  // Recurse on [left, mid-1] or [mid, right], depending on myRank.
234  if (myRank >= left && myRank <= mid-1)
235  broadcastStringsHelper (comm, myRank, left, mid-1, globalNames);
236  else if (myRank >= mid && myRank <= right)
237  broadcastStringsHelper (comm, myRank, mid, right, globalNames);
238  else
239  return; // Don't recurse if not participating.
240  }
241  }
242 
243 
244  void
245  broadcastStrings (const Comm<int>& comm,
246  Array<std::string>& globalNames)
247  {
248  const int myRank = comm.getRank();
249  const int left = 0;
250  const int right = comm.getSize() - 1;
251 
252  broadcastStringsHelper (comm, myRank, left, right, globalNames);
253  }
254 
255  // \brief Helper function for \c mergeCounterNamesHelper().
256  //
257  // The \c mergeCounterNamesHelper() function implements (using a
258  // parallel reduction) the set union resp. intersection (depending
259  // on the \c setOp argument) of the MPI process' sets of counter
260  // names. This function implements the binary associative
261  // operator which computes the set union resp. intersection of two
262  // sets: the "left" process' intermediate reduction result (global
263  // counter names), and the "mid" process' local counter names.
264  //
265  // \param comm [in] Communicator for which \c
266  // mergeCounterNamesHelper() was called.
267  //
268  // \param myRank [in] Rank of the calling MPI process; must be
269  // either == left or == mid.
270  //
271  // \param left [in] The "left" input argument of
272  // \c mergeCounterNamesHelper().
273  //
274  // \param mid [in] The value of "mid" in the implementation of \c
275  // mergeCounterNamesHelper().
276  //
277  // \param globalNames [in/out] Only accessed if myRank == left.
278  // If so, on input: the intermediate reduction result of the
279  // union resp. intersection (depending on \c setOp). On output:
280  // the union resp. intersection of the input value of the "left"
281  // MPI process' globalNames with the "mid" MPI process'
282  // localNames.
283  //
284  // \param setOp [in] If Intersection, compute the set intersection
285  // of counter names, else if Union, compute the set union of
286  // counter names.
287  void
288  mergeCounterNamesPair (const Comm<int>& comm,
289  const int myRank,
290  const int left,
291  const int mid,
292  Array<std::string>& globalNames,
293  const ECounterSetOp setOp)
294  {
295  using std::cerr;
296  using std::endl;
297  using std::string;
298 
299  const bool debug = false;
300 
301  if (myRank == left)
302  { // Receive names from the other process, and merge its names
303  // with the names on this process.
304  Array<string> otherNames;
305  receiveStrings (comm, mid, otherNames);
306 
307  if (debug)
308  {
309  // Buffering locally in an ostringstream before writing to
310  // the shared stderr sometimes helps avoid interleaved
311  // debugging output.
312  std::ostringstream out;
313  out << "Proc " << myRank << ": in mergePair: otherNames = [";
314  for (Array<std::string>::const_iterator it = otherNames.begin();
315  it != otherNames.end(); ++it)
316  {
317  out << "\"" << *it << "\"";
318  if (it + 1 != otherNames.end())
319  out << ", ";
320  }
321  out << "]" << endl;
322  cerr << out.str();
323  }
324 
325  // Assume that both globalNames and otherNames are sorted.
326  // Compute the set intersection / union as specified by the
327  // enum.
328  Array<string> newNames;
329  if ( std::is_sorted(globalNames.begin(), globalNames.end()) &&
330  std::is_sorted(otherNames.begin(), otherNames.end())) {
331  if (setOp == Intersection)
332  std::set_intersection (globalNames.begin(), globalNames.end(),
333  otherNames.begin(), otherNames.end(),
334  std::back_inserter (newNames));
335  else if (setOp == Union)
336  std::set_union (globalNames.begin(), globalNames.end(),
337  otherNames.begin(), otherNames.end(),
338  std::back_inserter (newNames));
339  else
340  TEUCHOS_TEST_FOR_EXCEPTION(setOp != Intersection && setOp != Union,
341  std::logic_error,
342  "Invalid set operation enum value. Please "
343  "report this bug to the Teuchos developers.");
344  globalNames.swap (newNames);
345  } else { // Need a brute force merge
346  unsortedMergePair(otherNames, globalNames, setOp);
347  }
348  }
349  else if (myRank == mid)
350  sendStrings (comm, globalNames, left);
351  else
352  TEUCHOS_TEST_FOR_EXCEPTION(myRank != left && myRank != mid,
353  std::logic_error,
354  "myRank=" << myRank << " is neither left=" << left
355  << " nor mid=" << mid << ". Please report this "
356  "bug to the Teuchos developers.");
357  }
358 
359 
360  // Recursive helper function for \c mergeCounterNames().
361  //
362  // This function implements the set union resp. intersection
363  // (depending on the \c setOp argument) of the MPI process' sets
364  // of counter names, using a parallel reduction. (Since the
365  // Teuchos comm wrappers as of 11 July 2011 lack a wrapper for
366  // MPI_Reduce(), we hand-roll the reduction using a binary tree
367  // via recursion. We don't need an all-reduce in this case.)
368  void
369  mergeCounterNamesHelper (const Comm<int>& comm,
370  const int myRank,
371  const int left,
372  const int right, // inclusive range [left, right]
373  const Array<std::string>& localNames,
374  Array<std::string>& globalNames,
375  const ECounterSetOp setOp)
376  {
377  // Correctness proof:
378  //
379  // 1. Both set intersection and set union are associative (and
380  // indeed even commutative) operations.
381  // 2. mergeCounterNamesHelper() is just a reduction by binary tree.
382  // 3. Reductions may use any tree shape as long as the binary
383  // operation is associative.
384  //
385  // Recursive "reduction" algorithm:
386  //
387  // Let mid be the midpoint of the inclusive interval [left,
388  // right]. If the (intersection, union) of [left, mid-1] and
389  // the (intersection, union) of [mid, right] are both computed
390  // correctly, then the (intersection, union) of these two sets
391  // is the (intersection, union) of [left, right].
392  //
393  // The base case is left == right: the (intersection, union) of
394  // one set is simply that set, so copy localNames into
395  // globalNames.
396  //
397  // We include another base case for safety: left > right,
398  // meaning that the set of processes is empty, so we do nothing
399  // (the (intersection, union) of an empty set of sets is the
400  // empty set).
401  if (left > right)
402  return;
403  else if (left == right)
404  {
405  Array<string> newNames;
406  newNames.reserve (localNames.size());
407  std::copy (localNames.begin(), localNames.end(),
408  std::back_inserter (newNames));
409  globalNames.swap (newNames);
410  }
411  else
412  { // You're sending messages across the network, so don't
413  // bother to optimize away a few branches here.
414  //
415  // Recurse on [left, mid-1] or [mid, right], depending on myRank.
416  const int mid = left + (right - left + 1) / 2;
417  if (myRank >= left && myRank <= mid-1)
418  mergeCounterNamesHelper (comm, myRank, left, mid-1,
419  localNames, globalNames, setOp);
420  else if (myRank >= mid && myRank <= right)
421  mergeCounterNamesHelper (comm, myRank, mid, right,
422  localNames, globalNames, setOp);
423  else
424  return; // Don't call mergeCounterNamesPair() if not participating.
425 
426  // Combine the results of the recursive step.
427  if (myRank == left || myRank == mid)
428  mergeCounterNamesPair (comm, myRank, left, mid,
429  globalNames, setOp);
430  }
431  }
432 
433  } // namespace (anonymous)
434 
445  void unsortedMergePair(const Array<std::string>& localNames,
446  Array<std::string>& globalNames,
447  const ECounterSetOp setOp){
448  if (setOp == Union) {
449  for (int i=0; i<localNames.size();++i) {
450  bool found=false;
451  // If the name is not in globalNames add it
452  for (int j=0;j<globalNames.size() && !found; ++j)
453  if (localNames[i] == globalNames[j])
454  found=true;
455  if (!found)
456  globalNames.push_back(localNames[i]);
457  }
458  } else if (setOp == Intersection) {
459  for (int i=0; i<globalNames.size();++i) {
460  bool found=false;
461  // If the name is not in localNames remove it
462  for (int j=0;j<localNames.size() && !found; ++j)
463  if (localNames[j] == globalNames[i])
464  found=true;
465  if (!found) {
466  globalNames.remove(i);
467  --i;
468  }
469  }
470  } else
471  TEUCHOS_TEST_FOR_EXCEPTION(setOp != Intersection && setOp != Union,
472  std::logic_error,
473  "Invalid set operation enum value. Please "
474  "report this bug to the Teuchos developers.");
475  }
476 
477 
478  void
480  const Array<std::string>& localNames,
481  Array<std::string>& globalNames,
482  const ECounterSetOp setOp)
483  {
484  const int myRank = comm.getRank();
485  const int left = 0;
486  const int right = comm.getSize() - 1;
487  Array<std::string> theGlobalNames;
488  mergeCounterNamesHelper (comm, myRank, left, right,
489  localNames, theGlobalNames, setOp);
490 
491  // Proc 0 has the list of counter names. Now broadcast it back to
492  // all the procs.
493  broadcastStrings (comm, theGlobalNames);
494 
495  // "Transactional" semantics ensure strong exception safety for
496  // output.
497  globalNames.swap (theGlobalNames);
498  }
499 
500 } // namespace Teuchos
void remove(int i)
Remove the i-th element from the array, with optional boundschecking.
virtual int getSize() const =0
Returns the number of processes that make up this communicator.
virtual int getRank() const =0
Returns the rank of this process.
#define TEUCHOS_TEST_FOR_EXCEPTION(throw_exception_test, Exception, msg)
Macro for throwing an exception with breakpointing to ease debugging.
static Teuchos::RCP< const Comm< OrdinalType > > getComm()
Return the default global communicator.
void send(const Packet sendBuffer[], const Ordinal count, const int destRank, const int tag, const Comm< Ordinal > &comm)
Variant of send() that takes a tag (and restores the correct order of arguments). ...
void mergeCounterNames(const Comm< int > &comm, const Array< std::string > &localNames, Array< std::string > &globalNames, const ECounterSetOp setOp)
Merge counter names over all processors.
friend void swap(Array< T2 > &a1, Array< T2 > &a2)
void unsortedMergePair(const Array< std::string > &localNames, Array< std::string > &globalNames, const ECounterSetOp setOp)
std::vector< std::string >::const_iterator const_iterator
The type of a const forward iterator.
void push_back(const value_type &x)
size_type size() const
Common capabilities for collecting and reporting performance data collectively across MPI processes...
ECounterSetOp
Set operation type for mergeCounterNames() to perform.