/*! * \file * \brief Distributed sort implementation header * * \author * Christos Choutouridis AEM:8997 * */ #ifndef DISTBITONIC_H_ #define DISTBITONIC_H_ #include #include #include #include #if !defined DEBUG #define NDEBUG #endif #include #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 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 The current node (MPI process) * @return True if we need ascending configuration, false otherwise */ template <> inline bool ascending(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 The current node (MPI process) * @param depth The total depth of the sorting network (same for each step for a given network) * * @return True if we need ascending configuration, false otherwise */ template <> inline bool ascending(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 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 The current node * @param step The step of the sorting network * @return The node id of the partner for data exchange */ template <> inline mpi_id_t partner(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 The current node * @param step The step of the sorting network * @return The node id of the partner for data exchange */ template <> inline mpi_id_t partner(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 inline bool keepSmall(mpi_id_t, mpi_id_t, [[maybe_unused]] size_t) noexcept = delete; /*! * Predicate to check if a node keeps the small numbers during the bubbletonic sort network exchange. * * @param node The node for which we check * @param partner The partner of the data exchange * @return True if the node should keep the small values, false otherwise */ template <> inline bool keepSmall(mpi_id_t node, mpi_id_t partner, [[maybe_unused]] size_t depth) noexcept { assert(node != partner); return (node < partner); } /*! * Predicate to check if a node keeps the small numbers during the bitonic sort network exchange. * * @param node The node for which we check * @param partner The partner of the data exchange * @param depth The total depth of the sorting network (same for each step for a given network) * @return True if the node should keep the small values, false otherwise */ template <> inline bool keepSmall(mpi_id_t node, mpi_id_t partner, size_t depth) noexcept { assert(node != partner); return ascending(node, depth) == (node < partner); } /*! * Predicate to check if the node is active in the current iteration of the bubbletonic * sort exchange. * * @param node The node to check * @param nodes The total number of nodes * @return True if the node is active, false otherwise */ bool isActive(mpi_id_t node, size_t nodes) noexcept; /* * ============================== Data utilities ============================== */ /*! * * @tparam RangeT * @param data * @param ascending */ template void fullSort(RangeT& data, bool ascending) { // 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<>()); } /*! * * @tparam ShadowedT * @tparam CompT * @param data * @param comp */ template void elbowSortCore(ShadowedT& data, CompT comp) { size_t N = data.size(); auto active = data.getActive(); auto shadow = data.getShadow(); size_t left = std::distance( active.begin(), std::min_element(active.begin(), active.end()) ); size_t right = (left == N-1) ? 0 : left + 1; for (size_t i = 0 ; i void elbowSort(ShadowedT& data, bool ascending) { if (ascending) elbowSortCore(data, std::less<>()); else elbowSortCore(data, std::greater<>()); } /*! * * @tparam RangeT * @param local * @param remote * @param keepsmall */ template void minmax(RangeT& local, RangeT& remote, bool keepsmall) { 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 ============================== */ /*! * * @tparam ShadowedT * @param data * @param Processes */ template void distBubbletonic(ShadowedT& data, mpi_id_t Processes) { // Initially sort to create a half part of a bitonic sequence fullSort(data, ascending(mpi.rank(), 0)); // Sort network for (size_t step = 0; step < Processes-1; ++step) { auto part = partner(mpi.rank(), step); auto ks = keepSmall(mpi.rank(), part, Processes); if (isActive(mpi.rank(), Processes)) { mpi.exchange(part, data.getActive(), data.getShadow(), step); minmax(data.getActive(), data.getShadow(), ks); elbowSort(data, ascending(mpi.rank(), Processes)); } } if (!ascending(mpi.rank(), 0)) elbowSort(data, true); } /*! * * @tparam ShadowedT * @param data * @param Processes */ template void distBitonic(ShadowedT& data, mpi_id_t Processes) { auto p = static_cast(std::log2(Processes)); // Initially sort to create a half part of a bitonic sequence fullSort(data, ascending(mpi.rank(), 0)); // Run through sort network using elbow-sort for (size_t depth = 1; depth <= p; ++depth) { for (size_t step = depth; step > 0;) { --step; auto part = partner(mpi.rank(), step); auto ks = keepSmall(mpi.rank(), part, depth); mpi.exchange(part, data.getActive(), data.getShadow(), (depth << 8) | step); minmax(data.getActive(), data.getShadow(), ks); } elbowSort (data, ascending(mpi.rank(), depth)); } } #endif //DISTBITONIC_H_