// Copyright (C) 2006 Douglas Gregor . // Use, modification and distribution is subject to the Boost Software // License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) /** @file nonblocking.hpp * * This header defines operations for completing non-blocking * communication requests. */ #ifndef BOOST_MPI_NONBLOCKING_HPP #define BOOST_MPI_NONBLOCKING_HPP #include #include #include // for std::iterator_traits #include #include // for std::pair #include // for iter_swap, reverse #include #include #include #include namespace boost { namespace mpi { /** * @brief Wait until any non-blocking request has completed. * * This routine takes in a set of requests stored in the iterator * range @c [first,last) and waits until any of these requests has * been completed. It provides functionality equivalent to * @c MPI_Waitany. * * @param first The iterator that denotes the beginning of the * sequence of request objects. * * @param last The iterator that denotes the end of the sequence of * request objects. This may not be equal to @c first. * * @returns A pair containing the status object that corresponds to * the completed operation and the iterator referencing the completed * request. */ template std::pair wait_any(ForwardIterator first, ForwardIterator last) { using std::advance; BOOST_ASSERT(first != last); typedef typename std::iterator_traits::difference_type difference_type; bool all_trivial_requests = true; difference_type n = 0; ForwardIterator current = first; while (true) { // Check if we have found a completed request. If so, return it. if (optional result = current->test()) return std::make_pair(*result, current); // Check if this request (and all others before it) are "trivial" // requests, e.g., they can be represented with a single // MPI_Request. all_trivial_requests = all_trivial_requests && !current->m_handler && current->m_requests[1] == MPI_REQUEST_NULL; // Move to the next request. ++n; if (++current == last) { // We have reached the end of the list. If all requests thus far // have been trivial, we can call MPI_Waitany directly, because // it may be more efficient than our busy-wait semantics. if (all_trivial_requests) { std::vector requests; requests.reserve(n); for (current = first; current != last; ++current) requests.push_back(current->m_requests[0]); // Let MPI wait until one of these operations completes. int index; status stat; BOOST_MPI_CHECK_RESULT(MPI_Waitany, (n, &requests[0], &index, &stat.m_status)); // We don't have a notion of empty requests or status objects, // so this is an error. if (index == MPI_UNDEFINED) boost::throw_exception(exception("MPI_Waitany", MPI_ERR_REQUEST)); // Find the iterator corresponding to the completed request. current = first; advance(current, index); current->m_requests[0] = requests[index]; return std::make_pair(stat, current); } // There are some nontrivial requests, so we must continue our // busy waiting loop. n = 0; current = first; all_trivial_requests = true; } } // We cannot ever get here BOOST_ASSERT(false); } /** * @brief Test whether any non-blocking request has completed. * * This routine takes in a set of requests stored in the iterator * range @c [first,last) and tests whether any of these requests has * been completed. This routine is similar to @c wait_any, but will * not block waiting for requests to completed. It provides * functionality equivalent to @c MPI_Testany. * * @param first The iterator that denotes the beginning of the * sequence of request objects. * * @param last The iterator that denotes the end of the sequence of * request objects. * * @returns If any outstanding requests have completed, a pair * containing the status object that corresponds to the completed * operation and the iterator referencing the completed * request. Otherwise, an empty @c optional<>. */ template optional > test_any(ForwardIterator first, ForwardIterator last) { for (ForwardIterator current = first; first != last; ++first) { // Check if we have found a completed request. If so, return it. if (optional result = current->test()) return std::make_pair(*result, current); } // We found nothing return optional >(); } /** * @brief Wait until all non-blocking requests have completed. * * This routine takes in a set of requests stored in the iterator * range @c [first,last) and waits until all of these requests have * been completed. It provides functionality equivalent to * @c MPI_Waitall. * * @param first The iterator that denotes the beginning of the * sequence of request objects. * * @param last The iterator that denotes the end of the sequence of * request objects. * * @param out If provided, an output iterator through which the * status of each request will be emitted. The @c status objects are * emitted in the same order as the requests are retrieved from * @c [first,last). * * @returns If an @p out parameter was provided, the value @c out * after all of the @c status objects have been emitted. */ template OutputIterator wait_all(ForwardIterator first, ForwardIterator last, OutputIterator out) { typedef typename std::iterator_traits::difference_type difference_type; using std::distance; difference_type num_outstanding_requests = distance(first, last); std::vector results(num_outstanding_requests); std::vector completed(num_outstanding_requests); while (num_outstanding_requests > 0) { bool all_trivial_requests = true; difference_type idx = 0; for (ForwardIterator current = first; current != last; ++current, ++idx) { if (!completed[idx]) { if (optional stat = current->test()) { // This outstanding request has been completed. We're done. results[idx] = *stat; completed[idx] = true; --num_outstanding_requests; all_trivial_requests = false; } else { // Check if this request (and all others before it) are "trivial" // requests, e.g., they can be represented with a single // MPI_Request. all_trivial_requests = all_trivial_requests && !current->m_handler && current->m_requests[1] == MPI_REQUEST_NULL; } } } // If we have yet to fulfill any requests and all of the requests // are trivial (i.e., require only a single MPI_Request to be // fulfilled), call MPI_Waitall directly. if (all_trivial_requests && num_outstanding_requests == (difference_type)results.size()) { std::vector requests; requests.reserve(num_outstanding_requests); for (ForwardIterator current = first; current != last; ++current) requests.push_back(current->m_requests[0]); // Let MPI wait until all of these operations completes. std::vector stats(num_outstanding_requests); BOOST_MPI_CHECK_RESULT(MPI_Waitall, (num_outstanding_requests, &requests[0], &stats[0])); for (std::vector::iterator i = stats.begin(); i != stats.end(); ++i, ++out) { status stat; stat.m_status = *i; *out = stat; } return out; } all_trivial_requests = false; } return std::copy(results.begin(), results.end(), out); } /** * \overload */ template void wait_all(ForwardIterator first, ForwardIterator last) { typedef typename std::iterator_traits::difference_type difference_type; using std::distance; difference_type num_outstanding_requests = distance(first, last); std::vector completed(num_outstanding_requests); while (num_outstanding_requests > 0) { bool all_trivial_requests = true; difference_type idx = 0; for (ForwardIterator current = first; current != last; ++current, ++idx) { if (!completed[idx]) { if (optional stat = current->test()) { // This outstanding request has been completed. completed[idx] = true; --num_outstanding_requests; all_trivial_requests = false; } else { // Check if this request (and all others before it) are "trivial" // requests, e.g., they can be represented with a single // MPI_Request. all_trivial_requests = all_trivial_requests && !current->m_handler && current->m_requests[1] == MPI_REQUEST_NULL; } } } // If we have yet to fulfill any requests and all of the requests // are trivial (i.e., require only a single MPI_Request to be // fulfilled), call MPI_Waitall directly. if (all_trivial_requests && num_outstanding_requests == (difference_type)completed.size()) { std::vector requests; requests.reserve(num_outstanding_requests); for (ForwardIterator current = first; current != last; ++current) requests.push_back(current->m_requests[0]); // Let MPI wait until all of these operations completes. BOOST_MPI_CHECK_RESULT(MPI_Waitall, (num_outstanding_requests, &requests[0], MPI_STATUSES_IGNORE)); // Signal completion num_outstanding_requests = 0; } } } /** * @brief Tests whether all non-blocking requests have completed. * * This routine takes in a set of requests stored in the iterator * range @c [first,last) and determines whether all of these requests * have been completed. However, due to limitations of the underlying * MPI implementation, if any of the requests refers to a * non-blocking send or receive of a serialized data type, @c * test_all will always return the equivalent of @c false (i.e., the * requests cannot all be finished at this time). This routine * performs the same functionality as @c wait_all, except that this * routine will not block. This routine provides functionality * equivalent to @c MPI_Testall. * * @param first The iterator that denotes the beginning of the * sequence of request objects. * * @param last The iterator that denotes the end of the sequence of * request objects. * * @param out If provided and all requests hav been completed, an * output iterator through which the status of each request will be * emitted. The @c status objects are emitted in the same order as * the requests are retrieved from @c [first,last). * * @returns If an @p out parameter was provided, the value @c out * after all of the @c status objects have been emitted (if all * requests were completed) or an empty @c optional<>. If no @p out * parameter was provided, returns @c true if all requests have * completed or @c false otherwise. */ template optional test_all(ForwardIterator first, ForwardIterator last, OutputIterator out) { std::vector requests; for (; first != last; ++first) { // If we have a non-trivial request, then no requests can be // completed. if (first->m_handler || first->m_requests[1] != MPI_REQUEST_NULL) return optional(); requests.push_back(first->m_requests[0]); } int flag = 0; int n = requests.size(); std::vector stats(n); BOOST_MPI_CHECK_RESULT(MPI_Testall, (n, &requests[0], &flag, &stats[0])); if (flag) { for (int i = 0; i < n; ++i, ++out) { status stat; stat.m_status = stats[i]; *out = stat; } return out; } else { return optional(); } } /** * \overload */ template bool test_all(ForwardIterator first, ForwardIterator last) { std::vector requests; for (; first != last; ++first) { // If we have a non-trivial request, then no requests can be // completed. if (first->m_handler || first->m_requests[1] != MPI_REQUEST_NULL) return false; requests.push_back(first->m_requests[0]); } int flag = 0; int n = requests.size(); BOOST_MPI_CHECK_RESULT(MPI_Testall, (n, &requests[0], &flag, MPI_STATUSES_IGNORE)); return flag != 0; } /** * @brief Wait until some non-blocking requests have completed. * * This routine takes in a set of requests stored in the iterator * range @c [first,last) and waits until at least one of the requests * has completed. It then completes all of the requests it can, * partitioning the input sequence into pending requests followed by * completed requests. If an output iterator is provided, @c status * objects will be emitted for each of the completed requests. This * routine provides functionality equivalent to @c MPI_Waitsome. * * @param first The iterator that denotes the beginning of the * sequence of request objects. * * @param last The iterator that denotes the end of the sequence of * request objects. This may not be equal to @c first. * * @param out If provided, the @c status objects corresponding to * completed requests will be emitted through this output iterator. * @returns If the @p out parameter was provided, a pair containing * the output iterator @p out after all of the @c status objects have * been written through it and an iterator referencing the first * completed request. If no @p out parameter was provided, only the * iterator referencing the first completed request will be emitted. */ template std::pair wait_some(BidirectionalIterator first, BidirectionalIterator last, OutputIterator out) { using std::advance; if (first == last) return std::make_pair(out, first); typedef typename std::iterator_traits::difference_type difference_type; bool all_trivial_requests = true; difference_type n = 0; BidirectionalIterator current = first; BidirectionalIterator start_of_completed = last; while (true) { // Check if we have found a completed request. if (optional result = current->test()) { using std::iter_swap; // Emit the resulting status object *out++ = *result; // We're expanding the set of completed requests --start_of_completed; if (current == start_of_completed) { // If we have hit the end of the list of pending // requests. Finish up by fixing the order of the completed // set to match the order in which we emitted status objects, // then return. std::reverse(start_of_completed, last); return std::make_pair(out, start_of_completed); } // Swap the request we just completed with the last request that // has not yet been tested. iter_swap(current, start_of_completed); continue; } // Check if this request (and all others before it) are "trivial" // requests, e.g., they can be represented with a single // MPI_Request. all_trivial_requests = all_trivial_requests && !current->m_handler && current->m_requests[1] == MPI_REQUEST_NULL; // Move to the next request. ++n; if (++current == start_of_completed) { if (start_of_completed != last) { // We have satisfied some requests. Make the order of the // completed requests match that of the status objects we've // already emitted and we're done. std::reverse(start_of_completed, last); return std::make_pair(out, start_of_completed); } // We have reached the end of the list. If all requests thus far // have been trivial, we can call MPI_Waitsome directly, because // it may be more efficient than our busy-wait semantics. if (all_trivial_requests) { std::vector requests; std::vector indices(n); std::vector stats(n); requests.reserve(n); for (current = first; current != last; ++current) requests.push_back(current->m_requests[0]); // Let MPI wait until some of these operations complete. int num_completed; BOOST_MPI_CHECK_RESULT(MPI_Waitsome, (n, &requests[0], &num_completed, &indices[0], &stats[0])); // Translate the index-based result of MPI_Waitsome into a // partitioning on the requests. int current_offset = 0; current = first; for (int index = 0; index < num_completed; ++index, ++out) { using std::iter_swap; // Move "current" to the request object at this index advance(current, indices[index] - current_offset); current_offset = indices[index]; // Emit the status object status stat; stat.m_status = stats[index]; *out = stat; // Finish up the request and swap it into the "completed // requests" partition. current->m_requests[0] = requests[indices[index]]; --start_of_completed; iter_swap(current, start_of_completed); } // We have satisfied some requests. Make the order of the // completed requests match that of the status objects we've // already emitted and we're done. std::reverse(start_of_completed, last); return std::make_pair(out, start_of_completed); } // There are some nontrivial requests, so we must continue our // busy waiting loop. n = 0; current = first; } } // We cannot ever get here BOOST_ASSERT(false); } /** * \overload */ template BidirectionalIterator wait_some(BidirectionalIterator first, BidirectionalIterator last) { using std::advance; if (first == last) return first; typedef typename std::iterator_traits::difference_type difference_type; bool all_trivial_requests = true; difference_type n = 0; BidirectionalIterator current = first; BidirectionalIterator start_of_completed = last; while (true) { // Check if we have found a completed request. if (optional result = current->test()) { using std::iter_swap; // We're expanding the set of completed requests --start_of_completed; // If we have hit the end of the list of pending requests, we're // done. if (current == start_of_completed) return start_of_completed; // Swap the request we just completed with the last request that // has not yet been tested. iter_swap(current, start_of_completed); continue; } // Check if this request (and all others before it) are "trivial" // requests, e.g., they can be represented with a single // MPI_Request. all_trivial_requests = all_trivial_requests && !current->m_handler && current->m_requests[1] == MPI_REQUEST_NULL; // Move to the next request. ++n; if (++current == start_of_completed) { // If we have satisfied some requests, we're done. if (start_of_completed != last) return start_of_completed; // We have reached the end of the list. If all requests thus far // have been trivial, we can call MPI_Waitsome directly, because // it may be more efficient than our busy-wait semantics. if (all_trivial_requests) { std::vector requests; std::vector indices(n); requests.reserve(n); for (current = first; current != last; ++current) requests.push_back(current->m_requests[0]); // Let MPI wait until some of these operations complete. int num_completed; BOOST_MPI_CHECK_RESULT(MPI_Waitsome, (n, &requests[0], &num_completed, &indices[0], MPI_STATUSES_IGNORE)); // Translate the index-based result of MPI_Waitsome into a // partitioning on the requests. int current_offset = 0; current = first; for (int index = 0; index < num_completed; ++index) { using std::iter_swap; // Move "current" to the request object at this index advance(current, indices[index] - current_offset); current_offset = indices[index]; // Finish up the request and swap it into the "completed // requests" partition. current->m_requests[0] = requests[indices[index]]; --start_of_completed; iter_swap(current, start_of_completed); } // We have satisfied some requests, so we are done. return start_of_completed; } // There are some nontrivial requests, so we must continue our // busy waiting loop. n = 0; current = first; } } // We cannot ever get here BOOST_ASSERT(false); } /** * @brief Test whether some non-blocking requests have completed. * * This routine takes in a set of requests stored in the iterator * range @c [first,last) and tests to see if any of the requests has * completed. It completes all of the requests it can, partitioning * the input sequence into pending requests followed by completed * requests. If an output iterator is provided, @c status objects * will be emitted for each of the completed requests. This routine * is similar to @c wait_some, but does not wait until any requests * have completed. This routine provides functionality equivalent to * @c MPI_Testsome. * * @param first The iterator that denotes the beginning of the * sequence of request objects. * * @param last The iterator that denotes the end of the sequence of * request objects. This may not be equal to @c first. * * @param out If provided, the @c status objects corresponding to * completed requests will be emitted through this output iterator. * @returns If the @p out parameter was provided, a pair containing * the output iterator @p out after all of the @c status objects have * been written through it and an iterator referencing the first * completed request. If no @p out parameter was provided, only the * iterator referencing the first completed request will be emitted. */ template std::pair test_some(BidirectionalIterator first, BidirectionalIterator last, OutputIterator out) { BidirectionalIterator current = first; BidirectionalIterator start_of_completed = last; while (current != start_of_completed) { // Check if we have found a completed request. if (optional result = current->test()) { using std::iter_swap; // Emit the resulting status object *out++ = *result; // We're expanding the set of completed requests --start_of_completed; // Swap the request we just completed with the last request that // has not yet been tested. iter_swap(current, start_of_completed); continue; } // Move to the next request. ++current; } // Finish up by fixing the order of the completed set to match the // order in which we emitted status objects, then return. std::reverse(start_of_completed, last); return std::make_pair(out, start_of_completed); } /** * \overload */ template BidirectionalIterator test_some(BidirectionalIterator first, BidirectionalIterator last) { BidirectionalIterator current = first; BidirectionalIterator start_of_completed = last; while (current != start_of_completed) { // Check if we have found a completed request. if (optional result = current->test()) { using std::iter_swap; // We're expanding the set of completed requests --start_of_completed; // Swap the request we just completed with the last request that // has not yet been tested. iter_swap(current, start_of_completed); continue; } // Move to the next request. ++current; } return start_of_completed; } } } // end namespace boost::mpi #endif // BOOST_MPI_NONBLOCKING_HPP