289 lines
8.4 KiB
C++
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_
|