Teuchos - Trilinos Tools Package Version of the Day
Loading...
Searching...
No Matches
Teuchos_MpiReductionOpSetter.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
12#ifdef HAVE_MPI
13# ifdef MPIAPI
14# define CALL_API MPIAPI
15# else
16# define CALL_API
17# endif
18
19//
20// mfh 23 Nov 2014: My commits over the past day or two attempt to
21// address Bug 6263. In particular, the code as I found it had the
22// following issues:
23//
24// 1. Static RCP instances (that persist past return of main())
25// 2. Static MPI_Op datum (that persists past MPI_Finalize())
26// 3. Code won't work with MPI_THREAD_{SERIALIZED,MULTIPLE},
27// because it assumes that only one MPI_Op for reductions
28// is needed at any one time
29//
30// I'm neglecting Issue #3 for now and focusing on the first two
31// issues. #1 goes away if one doesn't use RCPs and handles
32// deallocation manually (we could also use std::shared_ptr, but that
33// would require C++11). #2 goes away with the standard idiom of an
34// MPI_Finalize() hook (attach a (key,value) pair to MPI_COMM_SELF).
35//
36
37extern "C" {
38
39// The MPI_Op that implements the reduction or scan operation will
40// call this function. We only need to create the MPI_Op once
41// (lazily, on demand). This function in turn will invoke
42// theReductOp_ (see below), which gets set to the current reduction
43// operation. Thus, we only never need to create one MPI_Op, but we
44// swap out the function. This is meant to save overhead in creating
45// and freeing MPI_Op for each reduction or scan.
46void CALL_API
47Teuchos_MPI_reduction_op (void* invec, void* inoutvec,
48 int* len, MPI_Datatype* datatype);
49} // extern "C"
50
51namespace { // anonymous
52
53//
54// theMpiOp_: The MPI_Op singleton that implements the Teuchos
55// reduction or scan operation. We only need to create the MPI_Op
56// once (lazily, on demand). When we create the MPI_Op, we stash its
57// "destructor" in MPI_COMM_SELF so that it gets freed at
58// MPI_Finalize. (This is a standard MPI idiom.)
59//
60// This variable is global, persistent (until MPI_Finalize is called),
61// and initialized lazily.
62MPI_Op theMpiOp_ = MPI_OP_NULL;
63
64// The current reduction or scan "function." (It's actually a class
65// instance.)
66//
67// This static variable is _NOT_ persistent. It does not need
68// deallocation.
69const Teuchos::Details::MpiReductionOpBase* theReductOp_ = NULL;
70
71// Free the given MPI_Op, and return the error code returned by MPI_Op_free.
72int
73freeMpiOp (MPI_Op* op)
74{
75 // If this function is called as an MPI_Finalize hook, MPI should
76 // still be initialized at this point, and it should be OK to call
77 // MPI functions. Thus, we don't need to check if MPI is
78 // initialized.
79 int err = MPI_SUCCESS;
80 if (op != NULL) {
81 err = MPI_Op_free (op);
82 if (err == MPI_SUCCESS) {
83 // No externally visible side effects unless the above function succeeded.
84 *op = MPI_OP_NULL;
85 }
86 }
87 return err;
88}
89
90// Free the MPI_Op singleton (theMpiOp_), and return the error code
91// returned by freeMpiOp(). As a side effect, if freeing succeeds,
92// set theMpiOp_ to MPI_OP_NULL.
93//
94// This is the singleton's "destructor" that we attach to
95// MPI_COMM_SELF as an MPI_Finalize hook.
96int
97freeMpiOpCallback (MPI_Comm, int, void*, void*)
98{
99 // We don't need any of the arguments to this function, since we're
100 // just freeing the singleton.
101 if (theMpiOp_ == MPI_OP_NULL) {
102 return MPI_SUCCESS;
103 } else {
104 return freeMpiOp (&theMpiOp_);
105 }
106}
107
108// Create the MPI_Op singleton that invokes the
109// Teuchos_MPI_reduction_op callback. Assign the MPI_Op to theMpiOp_,
110// and set it up with an MPI_Finalize hook so it gets freed
111// automatically.
112void createReductOp ()
113{
114#if MPI_VERSION >= 2
115
116 // This function has side effects on the global singleton theMpiOp_.
117 // This check ensures that the function is idempotent. We only need
118 // to create the MPI_Op singleton once.
119 if (theMpiOp_ != MPI_OP_NULL) {
120 return; // We've already called this function; we don't have to again.
121 }
122
123 MPI_Op mpi_op = MPI_OP_NULL;
124
125 // FIXME (mfh 23 Nov 2014) I found the following comment here:
126 // "Assume op is commutative". That's what it means to pass 1 as
127 // the second argument. I don't know whether it's a good idea to
128 // keep that assumption.
129 int err = MPI_Op_create (&Teuchos_MPI_reduction_op, 1, &mpi_op);
131 err != MPI_SUCCESS, std::runtime_error, "Teuchos::createReductOp: "
132 "MPI_Op_create (for custom reduction operator) failed!");
133
134 // Use the standard MPI idiom (attach a (key,value) pair to
135 // MPI_COMM_SELF with a "destructor" function) in order that
136 // theMpiOp_ gets freed at MPI_Finalize, if necessary.
137
138 // 'key' is an output argument of MPI_Comm_create_keyval.
139 int key = MPI_KEYVAL_INVALID;
140 err = MPI_Comm_create_keyval (MPI_COMM_NULL_COPY_FN, freeMpiOpCallback,
141 &key, NULL);
142 if (err != MPI_SUCCESS) {
143 // Attempt to clean up by freeing the newly created MPI_Op. If
144 // cleaning up fails, just let it slide, since we're already in
145 // trouble if MPI can't create a (key,value) pair.
146 (void) MPI_Op_free (&mpi_op);
148 true, std::runtime_error, "Teuchos::createReductOp: "
149 "MPI_Comm_create_keyval (for custom reduction operator) failed!");
150 }
151 int val = key; // doesn't matter
152
153 // Attach the attribute to MPI_COMM_SELF.
154 err = MPI_Comm_set_attr (MPI_COMM_SELF, key, &val);
155 if (err != MPI_SUCCESS) {
156 // MPI (versions up to and including 3.0) doesn't promise correct
157 // behavior after any function returns something other than
158 // MPI_SUCCESS. Thus, it's not required to try to free the new
159 // key via MPI_Comm_free_keyval. Furthermore, if something went
160 // wrong with MPI_Comm_set_attr, it's likely that the attribute
161 // mechanism is broken. Thus, it would be unwise to call
162 // MPI_Comm_free_keyval.
163 //
164 // I optimistically assume that the "rest" of MPI is still
165 // working, and attempt to clean up by freeing the newly created
166 // MPI_Op. If cleaning up fails, just let it slide, since we're
167 // already in trouble if MPI can't create a (key,value) pair.
168 (void) MPI_Op_free (&mpi_op);
170 true, std::runtime_error, "Teuchos::createReductOp: "
171 "MPI_Comm_set_attr (for custom reduction operator) failed!");
172 }
173
174 // It looks weird to "free" the key right away. However, this does
175 // not actually cause the "destructor" to be called. It only gets
176 // called at MPI_FINALIZE. See MPI 3.0 standard, Section 6.7.2,
177 // MPI_COMM_FREE_KEYVAL:
178 //
179 // "Note that it is not erroneous to free an attribute key that is
180 // in use, because the actual free does not transpire until after
181 // all references (in other communicators on the process) to the key
182 // have been freed. These references need to be explicitly freed by
183 // the program, either via calls to MPI_COMM_DELETE_ATTR that free
184 // one attribute instance, or by calls to MPI_COMM_FREE that free
185 // all attribute instances associated with the freed communicator."
186 //
187 // We rely here on the latter mechanism. MPI_FINALIZE calls
188 // MPI_COMM_FREE on MPI_COMM_SELF, so we do not need to call it
189 // explicitly.
190 //
191 // It's not clear what to do if the MPI_* calls above succeeded, but
192 // this call fails (i.e., returns != MPI_SUCCESS). We could throw;
193 // this would make sense to do, because MPI (versions up to and
194 // including 3.0) doesn't promise correct behavior after any MPI
195 // function returns something other than MPI_SUCCESS. We could also
196 // be optimistic and just ignore the return value, hoping that if
197 // the above calls succeeded, then the communicator will get freed
198 // at MPI_FINALIZE, even though the unfreed key may leak memory (see
199 // Bug 6338). I've chosen the latter.
200 (void) MPI_Comm_free_keyval (&key);
201
202 // The "transaction" succeeded; save the result.
203 theMpiOp_ = mpi_op;
204
205#else // MPI_VERSION < 2
206# error "Sorry, you need an MPI implementation that supports at least MPI 2.0 in order to build this code. MPI 2.0 came out in 1997. I wrote this comment in 2017. If you really _really_ want MPI 1.x support, please file a GitHub issue for this feature request at github.com/trilinos/trilinos/issues with an expression of its priority and we will get to it as soon as we can."
207#endif // MPI_VERSION >= 2
208}
209
210void
211setReductOp (const Teuchos::Details::MpiReductionOpBase* reductOp)
212{
213 if (theMpiOp_ == MPI_OP_NULL) {
214 createReductOp ();
215 }
216 theReductOp_ = reductOp;
217}
218
219} // namespace (anonymous)
220
221extern "C" {
222
223void CALL_API
224Teuchos_MPI_reduction_op (void* invec,
225 void* inoutvec,
226 int* len,
227 MPI_Datatype* datatype)
228{
229 if (theReductOp_ != NULL) {
230 theReductOp_->reduce (invec, inoutvec, len, datatype);
231 }
232}
233
234} // extern "C"
235
236namespace Teuchos {
237namespace Details {
238
239MPI_Op setMpiReductionOp (const MpiReductionOpBase& reductOp)
240{
241 setReductOp (&reductOp);
243 (theMpiOp_ == MPI_OP_NULL, std::logic_error, "Teuchos::Details::"
244 "setMpiReductionOp: Failed to create reduction MPI_Op theMpiOp_. "
245 "This should never happen. "
246 "Please report this bug to the Teuchos developers.");
247 return theMpiOp_;
248}
249
250} // namespace Details
251} // namespace Teuchos
252
253#endif // HAVE_MPI
Implementation detail of Teuchos' MPI wrapper.
#define TEUCHOS_TEST_FOR_EXCEPTION(throw_exception_test, Exception, msg)
Macro for throwing an exception with breakpointing to ease debugging.
Namespace of implementation details.
The Teuchos namespace contains all of the classes, structs and enums used by Teuchos,...