PDS/homework_2/include/distsort.hpp

289 lines
8.4 KiB
C++

/*!
* \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 The current node (MPI process)
* @return 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 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<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 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<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 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<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) 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<SortMode::Bubbletonic>(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<SortMode::Bitonic>(mpi_id_t node, mpi_id_t partner, size_t depth) noexcept {
assert(node != partner);
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 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<typename RangeT>
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<typename ShadowedT, typename CompT>
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<N ; ++i) {
if (comp(active[left], active[right])) {
shadow[i] = active[left];
left = (left == 0) ? N-1 : left -1;
}
else {
shadow[i] = active[right];
right = (right + 1) % N;
}
}
data.switch_active();
}
/*!
*
* @tparam ShadowedT
* @param data
* @param ascending
*/
template<typename ShadowedT>
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<typename RangeT>
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<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
for (size_t step = 0; step < Processes-1; ++step) {
auto part = partner<SortMode::Bubbletonic>(mpi.rank(), step);
auto ks = keepSmall<SortMode::Bubbletonic>(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<SortMode::Bubbletonic>(mpi.rank(), Processes));
}
}
if (!ascending<SortMode::Bubbletonic>(mpi.rank(), 0))
elbowSort(data, true);
}
/*!
*
* @tparam ShadowedT
* @param data
* @param Processes
*/
template<typename ShadowedT>
void distBitonic(ShadowedT& data, mpi_id_t Processes) {
auto p = static_cast<uint32_t>(std::log2(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
for (size_t depth = 1; depth <= p; ++depth) {
for (size_t step = depth; step > 0;) {
--step;
auto part = partner<SortMode::Bitonic>(mpi.rank(), step);
auto ks = keepSmall<SortMode::Bitonic>(mpi.rank(), part, depth);
mpi.exchange(part, data.getActive(), data.getShadow(), (depth << 8) | step);
minmax(data.getActive(), data.getShadow(), ks);
}
elbowSort (data, ascending<SortMode::Bitonic>(mpi.rank(), depth));
}
}
#endif //DISTBITONIC_H_