|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- /*!
- * \file
- * \brief Distributed sort implementation header
- *
- * \author
- * Christos Choutouridis AEM:8997
- * <cchoutou@ece.auth.gr>
- */
-
- #ifndef DISTBITONIC_H_
- #define DISTBITONIC_H_
-
- #include <vector>
- #include <algorithm>
- #include <cmath>
- #include <cstdint>
- #if !defined DEBUG
- #define NDEBUG
- #endif
- #include <cassert>
-
- #include "utils.hpp"
- #include "config.h"
-
-
- /*!
- * Enumerator for the different versions of the sorting method
- */
- enum class SortMode {
- Bubbletonic, //!< The v0.5 of the algorithm where we use a bubble-sort like approach
- Bitonic //!< The v1.0 of the algorithm where we use the bitonic data-exchange approach
- };
-
- /*
- * ============================== Sort utilities ==============================
- */
-
- /*!
- * The primary function template of ascending(). It is DISABLED since , it is explicitly specialized
- * for each of the \c SortMode
- */
- template <SortMode Mode> inline bool ascending(mpi_id_t, [[maybe_unused]] size_t) noexcept = delete;
-
- /*!
- * Returns the ascending or descending configuration of the node's sequence based on
- * the current node (MPI process) and the depth of the sorting network
- *
- * @param node [mpi_id_t] The current node (MPI process)
- * @return [bool] True if we need ascending configuration, false otherwise
- */
- template <> inline
- bool ascending<SortMode::Bubbletonic>(mpi_id_t node, [[maybe_unused]] size_t depth) noexcept {
- return (node % 2) == 0;
- }
-
- /*!
- * Returns the ascending or descending configuration of the node's sequence based on
- * the current node (MPI process) and the depth of the sorting network
- *
- * @param node [mpi_id_t] The current node (MPI process)
- * @param depth [size_t] The total depth of the sorting network (same for each step for a given network)
- * @return [bool] True if we need ascending configuration, false otherwise
- */
- template <> inline
- bool ascending<SortMode::Bitonic>(mpi_id_t node, size_t depth) noexcept {
- return !(node & (1 << depth));
- }
-
- /*!
- * The primary function template of partner(). It is DISABLED since , it is explicitly specialized
- * for each of the \c SortMode
- */
- template <SortMode Mode> inline mpi_id_t partner(mpi_id_t, size_t) noexcept = delete;
-
- /*!
- * Returns the node's partner for data exchange during the sorting network iterations
- * of Bubbletonic
- *
- * @param node [mpi_id_t] The current node
- * @param step [size_t] The step of the sorting network
- * @return [mpi_id_t] The node id of the partner for data exchange
- */
- template <> inline
- mpi_id_t partner<SortMode::Bubbletonic>(mpi_id_t node, size_t step) noexcept {
- //return (node % 2 == step % 2) ? node + 1 : node - 1;
- return (((node+step) % 2) == 0) ? node + 1 : node - 1;
- }
-
- /*!
- * Returns the node's partner for data exchange during the sorting network iterations
- * of Bitonic
- *
- * @param node [mpi_id_t] The current node
- * @param step [size_t] The step of the sorting network
- * @return [mpi_id_t] The node id of the partner for data exchange
- */
- template <> inline
- mpi_id_t partner<SortMode::Bitonic>(mpi_id_t node, size_t step) noexcept {
- return (node ^ (1 << step));
- }
-
-
- /*!
- * The primary function template of keepSmall(). It is DISABLED since , it is explicitly specialized
- * for each of the \c SortMode
- */
- template<SortMode Mode> inline bool keepSmall(mpi_id_t, mpi_id_t, [[maybe_unused]] size_t) = delete;
-
- /*!
- * Predicate to check if a node keeps the small numbers during the bubbletonic sort network exchange.
- *
- * @param node [mpi_id_t] The node for which we check
- * @param partner [mpi_id_t] The partner of the data exchange
- * @return [bool] True if the node should keep the small values, false otherwise
- */
- template <> inline
- bool keepSmall<SortMode::Bubbletonic>(mpi_id_t node, mpi_id_t partner, [[maybe_unused]] size_t depth) {
- if (node == partner)
- throw std::runtime_error("(keepSmall) Node and Partner can not be the same\n");
- return (node < partner);
- }
-
- /*!
- * Predicate to check if a node keeps the small numbers during the bitonic sort network exchange.
- *
- * @param node [mpi_id_t] The node for which we check
- * @param partner [mpi_id_t] The partner of the data exchange
- * @param depth [size_t] The total depth of the sorting network (same for each step for a given network)
- * @return [bool] True if the node should keep the small values, false otherwise
- */
- template <> inline
- bool keepSmall<SortMode::Bitonic>(mpi_id_t node, mpi_id_t partner, size_t depth) {
- if (node == partner)
- throw std::runtime_error("(keepSmall) Node and Partner can not be the same\n");
- return ascending<SortMode::Bitonic>(node, depth) == (node < partner);
- }
-
- /*!
- * Predicate to check if the node is active in the current iteration of the bubbletonic
- * sort exchange.
- *
- * @param node [mpi_id_t] The node to check
- * @param nodes [size_t] The total number of nodes
- * @return [bool] True if the node is active, false otherwise
- */
- bool isActive(mpi_id_t node, size_t nodes);
-
- /*
- * ============================== Data utilities ==============================
- */
-
- /*!
- * Sort a range using the build-in O(Nlog(N)) algorithm
- *
- * @tparam RangeT A range type with random access iterator
- *
- * @param data [RangeT] The data to be sorted
- * @param ascending [bool] Flag to indicate the sorting order
- */
- template<typename RangeT>
- void fullSort(RangeT& data, bool ascending) noexcept {
- // Use introsort from stdlib++ here, unless ...
- if (ascending)
- std::sort(data.begin(), data.end(), std::less<>());
- else
- std::sort(data.begin(), data.end(), std::greater<>());
- }
-
- /*!
- * Core functionality of sort for shadowed buffer types using
- * the "elbow sort" algorithm.
- *
- * @note:
- * This algorithm can not work "in place".
- * We use the active buffer as source and the shadow as target.
- * At the end we switch which buffer is active and which is the shadow.
- * @note
- * This is the core functionality. Use the elbowSort() function instead
- *
- * @tparam ShadowedT A Shadowed buffer type with random access iterator.
- * @tparam CompT A Comparison type for binary operation comparisons
- *
- * @param data [ShadowedT] The data to sort
- * @param ascending [bool] Flag to indicate the sorting order
- * @param comp [CompT] The binary operator object
- */
- template<typename ShadowedT, typename CompT>
- void elbowSortCore(ShadowedT& data, bool ascending, CompT comp) noexcept {
- auto& active = data.getActive(); // Get the source vector (the data to sort)
- auto& shadow = data.getShadow(); // Get the target vector (the sorted data)
-
- size_t N = data.size(); // The total size is the same or both vectors
- size_t left = std::distance(
- active.begin(),
- (ascending) ?
- std::min_element(active.begin(), active.end()) :
- std::max_element(active.begin(), active.end())
- ); // start 'left' from elbow of the bitonic
- size_t right = (left == N-1) ? 0 : left + 1;
-
- // Walk in opposite directions from elbow and insert-sort to target vector
- for (size_t i = 0 ; i<N ; ++i) {
- if (comp(active[left], active[right])) {
- shadow[i] = active[left];
- left = (left == 0) ? N-1 : left -1; // cycle decrease
- }
- else {
- shadow[i] = active[right];
- right = (right + 1) % N; // cycle increase
- }
- }
- data.switch_active(); // Switch active-shadow buffers
- }
-
- /*!
- * Sort a shadowed buffer using the "elbow sort" algorithm.
- *
- * @tparam ShadowedT A Shadowed buffer type with random access iterator.
- *
- * @param data [ShadowedT] The data to sort
- * @param ascending [bool] Flag to indicate the sorting order
- */
- template<typename ShadowedT>
- void elbowSort(ShadowedT& data, bool ascending) noexcept {
- if (ascending)
- elbowSortCore(data, ascending, std::less<>());
- else
- elbowSortCore(data, ascending, std::greater<>());
- }
-
- /*!
- * Takes two sorted sequences where one is in increasing and the other is in decreasing order
- * and selects either the larger or the smaller items in one-to-one comparison between them.
- * The result is a bitonic sequence.
- *
- * @tparam RangeT A range type with random access iterator
- *
- * @param local [RangeT] Reference to the local sequence
- * @param remote [const RangeT] Reference to the remote sequence (copied locally by MPI)
- * @param keepSmall [bool] Flag to indicate if we keep the small items in local sequence
- */
- template<typename RangeT>
- void minmax(RangeT& local, const RangeT& remote, bool keepSmall) noexcept {
- using value_t = typename RangeT::value_type;
- std::transform(
- local.begin(), local.end(),
- remote.begin(),
- local.begin(),
- [&keepSmall](const value_t& a, const value_t& b){
- return (keepSmall) ? std::min(a, b) : std::max(a, b);
- });
- }
-
- /*
- * ============================== Sort algorithms ==============================
- */
-
- /*!
- * A distributed version of the Bubbletonic sort algorithm.
- *
- * @note
- * Each MPI process should run an instance of this function.
- *
- * @tparam ShadowedT A Shadowed buffer type with random access iterator.
- *
- * @param data [ShadowedT] The local to MPI process data to sort
- * @param Processes [mpi_id_t] The total number of MPI processes
- */
- template<typename ShadowedT>
- void distBubbletonic(ShadowedT& data, mpi_id_t Processes) {
- // Initially sort to create a half part of a bitonic sequence
- fullSort(data, ascending<SortMode::Bubbletonic>(mpi.rank(), 0));
-
- // Sort network (O(N) iterations)
- for (size_t step = 0; step < static_cast<size_t>(Processes-1); ++step) {
- // Find out exchange configuration
- auto part = partner<SortMode::Bubbletonic>(mpi.rank(), step);
- auto ks = keepSmall<SortMode::Bubbletonic>(mpi.rank(), part, Processes);
- if ( isActive(mpi.rank(), Processes) &&
- isActive(part, Processes) ) {
- // Exchange with partner, keep nim-or-max and sort - O(N)
- mpi.exchange(part, data.getActive(), data.getShadow(), step);
- minmax(data.getActive(), data.getShadow(), ks);
- elbowSort(data, ascending<SortMode::Bubbletonic>(mpi.rank(), Processes));
- }
- }
-
- // Invert if the node was descending.
- if (!ascending<SortMode::Bubbletonic>(mpi.rank(), 0))
- elbowSort(data, true);
-
- }
-
-
- /*!
- * A distributed version of the Bitonic sort algorithm.
- *
- * @note
- * Each MPI process should run an instance of this function.
- *
- * @tparam ShadowedT A Shadowed buffer type with random access iterator.
- *
- * @param data [ShadowedT] The local to MPI process data to sort
- * @param Processes [mpi_id_t] The total number of MPI processes
- */
- template<typename ShadowedT>
- void distBitonic(ShadowedT& data, mpi_id_t Processes) {
- // Initially sort to create a half part of a bitonic sequence
- fullSort(data, ascending<SortMode::Bitonic>(mpi.rank(), 0));
-
- // Run through sort network using elbow-sort ( O(LogN * LogN) iterations )
- auto p = static_cast<uint32_t>(std::log2(Processes));
- for (size_t depth = 1; depth <= p; ++depth) {
- for (size_t step = depth; step > 0;) {
- --step;
- // Find out exchange configuration
- auto part = partner<SortMode::Bitonic>(mpi.rank(), step);
- auto ks = keepSmall<SortMode::Bitonic>(mpi.rank(), part, depth);
- // Exchange with partner, keep nim-or-max
- mpi.exchange(part, data.getActive(), data.getShadow(), (depth << 8) | step);
- minmax(data.getActive(), data.getShadow(), ks);
- }
- // sort - O(N)
- elbowSort (data, ascending<SortMode::Bitonic>(mpi.rank(), depth));
- }
- }
-
- #endif //DISTBITONIC_H_
|