Teuchos - Trilinos Tools Package Version of the Day
Loading...
Searching...
No Matches
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
15namespace 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
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
482 const ECounterSetOp setOp)
483 {
484 const int myRank = comm.getRank();
485 const int left = 0;
486 const int right = comm.getSize() - 1;
490
491 // Proc 0 has the list of counter names. Now broadcast it back to
492 // all the procs.
494
495 // "Transactional" semantics ensure strong exception safety for
496 // output.
498 }
499
500} // namespace Teuchos
Common capabilities for collecting and reporting performance data collectively across MPI processes.
Ordinal size_type
The type of Array sizes and capacities.
std::vector< T >::const_iterator const_iterator
The type of a const forward iterator.
static Teuchos::RCP< const Comm< OrdinalType > > getComm()
Return the default global communicator.
Smart reference counting pointer class for automatic garbage collection.
void swap(RCP< T > &r_ptr)
Swap the contents with some other RCP object.
#define TEUCHOS_TEST_FOR_EXCEPTION(throw_exception_test, Exception, msg)
Macro for throwing an exception with breakpointing to ease debugging.
The Teuchos namespace contains all of the classes, structs and enums used by Teuchos,...
ECounterSetOp
Set operation type for mergeCounterNames() to perform.
void mergeCounterNames(const Comm< int > &comm, const Array< std::string > &localNames, Array< std::string > &globalNames, const ECounterSetOp setOp)
Merge counter names over all processors.
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 unsortedMergePair(const Array< std::string > &localNames, Array< std::string > &globalNames, const ECounterSetOp setOp)