@@ -185,6 +185,8 @@ distbubbletonic: CFLAGS := $(REL_CFLAGS) -DCODE_VERSION=BUBBLETONIC | |||
distbubbletonic: CXXFLAGS := $(REL_CXXFLAGS) -DCODE_VERSION=BUBBLETONIC | |||
distbubbletonic: TARGET := distbubbletonic | |||
distbubbletonic: $(BUILD_DIR)/$(TARGET) | |||
@mkdir -p out | |||
cp $(BUILD_DIR)/$(TARGET) out/$(TARGET) | |||
distbitonic: CC := mpicc | |||
distbitonic: CXX := mpic++ | |||
@@ -192,6 +194,8 @@ distbitonic: CFLAGS := $(REL_CFLAGS) -DCODE_VERSION=BITONIC | |||
distbitonic: CXXFLAGS := $(REL_CXXFLAGS) -DCODE_VERSION=BITONIC | |||
distbitonic: TARGET := distbitonic | |||
distbitonic: $(BUILD_DIR)/$(TARGET) | |||
@mkdir -p out | |||
cp $(BUILD_DIR)/$(TARGET) out/$(TARGET) | |||
deb_distbubbletonic: CC := mpicc | |||
deb_distbubbletonic: CXX := mpic++ | |||
@@ -199,6 +203,8 @@ deb_distbubbletonic: CFLAGS := $(DEB_CFLAGS) -DCODE_VERSION=BUBBLETONIC -DDEBUG | |||
deb_distbubbletonic: CXXFLAGS := $(DEB_CXXFLAGS) -DCODE_VERSION=BUBBLETONIC -DDEBUG | |||
deb_distbubbletonic: TARGET := deb_distbubbletonic | |||
deb_distbubbletonic: $(BUILD_DIR)/$(TARGET) | |||
@mkdir -p out | |||
cp $(BUILD_DIR)/$(TARGET) out/$(TARGET) | |||
deb_distbitonic: CC := mpicc | |||
deb_distbitonic: CXX := mpic++ | |||
@@ -206,6 +212,8 @@ deb_distbitonic: CFLAGS := $(DEB_CFLAGS) -DCODE_VERSION=BITONIC -DDEBUG | |||
deb_distbitonic: CXXFLAGS := $(DEB_CXXFLAGS) -DCODE_VERSION=BITONIC -DDEBUG | |||
deb_distbitonic: TARGET := deb_distbitonic | |||
deb_distbitonic: $(BUILD_DIR)/$(TARGET) | |||
@mkdir -p out | |||
cp $(BUILD_DIR)/$(TARGET) out/$(TARGET) | |||
tests: CC := mpicc | |||
tests: CXX := mpic++ | |||
@@ -213,6 +221,14 @@ tests: CFLAGS := $(DEB_CFLAGS) -DCODE_VERSION=BITONIC -DDEBUG -DTESTING | |||
tests: CXXFLAGS := $(DEB_CXXFLAGS) -DCODE_VERSION=BITONIC -DDEBUG -DTESTING | |||
tests: TARGET := tests | |||
tests: $(BUILD_DIR)/$(TARGET) | |||
@mkdir -p out | |||
cp $(BUILD_DIR)/$(TARGET) out/$(TARGET) | |||
measurements: | |||
make clean | |||
make distbubbletonic | |||
make clean | |||
make distbitonic | |||
all: debug distbubbletonic distbitonic | |||
@@ -25,7 +25,7 @@ | |||
#endif | |||
// Value type selection | |||
using distValue_t = uint8_t; | |||
using distValue_t = uint32_t; | |||
/*! | |||
* Session option for each invocation of the executable | |||
@@ -45,8 +45,8 @@ template <SortMode Mode> inline bool ascending(mpi_id_t, [[maybe_unused]] size_t | |||
* 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 | |||
* @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 { | |||
@@ -57,10 +57,9 @@ bool ascending<SortMode::Bubbletonic>(mpi_id_t node, [[maybe_unused]] size_t dep | |||
* 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 | |||
* @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 { | |||
@@ -77,9 +76,9 @@ template <SortMode Mode> inline mpi_id_t partner(mpi_id_t, size_t) noexcept = de | |||
* 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 | |||
* @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 { | |||
@@ -91,9 +90,9 @@ mpi_id_t partner<SortMode::Bubbletonic>(mpi_id_t node, size_t step) noexcept { | |||
* 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 | |||
* @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 { | |||
@@ -105,32 +104,34 @@ mpi_id_t partner<SortMode::Bitonic>(mpi_id_t node, size_t step) noexcept { | |||
* 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; | |||
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 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 | |||
* @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) noexcept { | |||
assert(node != partner); | |||
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 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 | |||
* @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) noexcept { | |||
assert(node != partner); | |||
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); | |||
} | |||
@@ -138,24 +139,26 @@ bool keepSmall<SortMode::Bitonic>(mpi_id_t node, mpi_id_t partner, size_t depth) | |||
* 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 | |||
* @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) noexcept; | |||
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 | |||
* | |||
* @tparam RangeT | |||
* @param data | |||
* @param ascending | |||
* @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) { | |||
void fullSort(RangeT& data, bool ascending) noexcept { | |||
// Use introsort from stdlib++ here, unless ... | |||
if (ascending) | |||
std::sort(data.begin(), data.end(), std::less<>()); | |||
@@ -164,66 +167,87 @@ void fullSort(RangeT& data, bool ascending) { | |||
} | |||
/*! | |||
* 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 | |||
* | |||
* @tparam ShadowedT | |||
* @tparam CompT | |||
* @param data | |||
* @param comp | |||
* @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, CompT comp) { | |||
size_t N = data.size(); | |||
auto active = data.getActive(); | |||
auto shadow = data.getShadow(); | |||
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(), | |||
std::min_element(active.begin(), active.end()) | |||
); | |||
(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; | |||
left = (left == 0) ? N-1 : left -1; // cycle decrease | |||
} | |||
else { | |||
shadow[i] = active[right]; | |||
right = (right + 1) % N; | |||
right = (right + 1) % N; // cycle increase | |||
} | |||
} | |||
data.switch_active(); | |||
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. | |||
* | |||
* @tparam ShadowedT | |||
* @param data | |||
* @param ascending | |||
* @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) { | |||
void elbowSort(ShadowedT& data, bool ascending) noexcept { | |||
if (ascending) | |||
elbowSortCore(data, std::less<>()); | |||
elbowSortCore(data, ascending, std::less<>()); | |||
else | |||
elbowSortCore(data, std::greater<>()); | |||
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 | |||
* | |||
* @tparam RangeT | |||
* @param local | |||
* @param remote | |||
* @param keepsmall | |||
* @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, RangeT& remote, bool keepsmall) { | |||
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); | |||
[&keepSmall](const value_t& a, const value_t& b){ | |||
return (keepSmall) ? std::min(a, b) : std::max(a, b); | |||
}); | |||
} | |||
@@ -232,27 +256,36 @@ void minmax(RangeT& local, RangeT& remote, bool keepsmall) { | |||
*/ | |||
/*! | |||
* A distributed version of the Bubbletonic sort algorithm. | |||
* | |||
* @tparam ShadowedT | |||
* @param data | |||
* @param Processes | |||
* @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 | |||
for (size_t step = 0; step < Processes-1; ++step) { | |||
// 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)) { | |||
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); | |||
@@ -260,27 +293,34 @@ void distBubbletonic(ShadowedT& data, mpi_id_t Processes) { | |||
/*! | |||
* 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. | |||
* | |||
* @tparam ShadowedT | |||
* @param data | |||
* @param Processes | |||
* @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) { | |||
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 | |||
// 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)); | |||
} | |||
} | |||
@@ -23,14 +23,19 @@ template <typename T> struct MPI_TypeMapper; | |||
// Specializations for supported types | |||
template <> struct MPI_TypeMapper<char> { static MPI_Datatype getType() { return MPI_CHAR; } }; | |||
template <> struct MPI_TypeMapper<unsigned char> { static MPI_Datatype getType() { return MPI_UNSIGNED_CHAR; } }; | |||
template <> struct MPI_TypeMapper<short> { static MPI_Datatype getType() { return MPI_SHORT; } }; | |||
template <> struct MPI_TypeMapper<int> { static MPI_Datatype getType() { return MPI_INT; } }; | |||
template <> struct MPI_TypeMapper<long> { static MPI_Datatype getType() { return MPI_LONG; } }; | |||
template <> struct MPI_TypeMapper<long long> { static MPI_Datatype getType() { return MPI_LONG_LONG; } }; | |||
template <> struct MPI_TypeMapper<unsigned short>{ static MPI_Datatype getType() { return MPI_UNSIGNED_SHORT; } }; | |||
template <> struct MPI_TypeMapper<unsigned int> { static MPI_Datatype getType() { return MPI_UNSIGNED; } }; | |||
template <> struct MPI_TypeMapper<unsigned long> { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG; } }; | |||
template <> struct MPI_TypeMapper<unsigned long long> { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG_LONG; } }; | |||
template <> struct MPI_TypeMapper<float> { static MPI_Datatype getType() { return MPI_FLOAT; } }; | |||
template <> struct MPI_TypeMapper<double> { static MPI_Datatype getType() { return MPI_DOUBLE; } }; | |||
template<typename TID = int> | |||
struct MPI_t { | |||
using ID_t = TID; // Export TID type (currently int defined by the standard) | |||
@@ -38,6 +43,7 @@ struct MPI_t { | |||
void init(int *argc, char ***argv) { | |||
// Initialize the MPI environment | |||
MPI_Init(argc, argv); | |||
initialized_ = true; | |||
// Get the number of processes | |||
int size_value, rank_value; | |||
@@ -53,11 +59,6 @@ struct MPI_t { | |||
name_ = std::string (processor_name, name_len); | |||
} | |||
void finalize() { | |||
// Finalize the MPI environment. | |||
MPI_Finalize(); | |||
} | |||
template<typename T> | |||
void exchange(ID_t partner, const std::vector<T>& send_data, std::vector<T>& recv_data, int tag) { | |||
using namespace std::string_literals; | |||
@@ -83,10 +84,21 @@ struct MPI_t { | |||
[[nodiscard]] ID_t size() const noexcept { return size_; } | |||
[[nodiscard]] const std::string& name() const noexcept { return name_; } | |||
void finalize() { | |||
// Finalize the MPI environment | |||
initialized_ = false; | |||
MPI_Finalize(); | |||
} | |||
~MPI_t() { | |||
// Finalize the MPI environment even on unexpected errors | |||
if (initialized_) | |||
MPI_Finalize(); | |||
} | |||
private: | |||
ID_t rank_{}; | |||
ID_t size_{}; | |||
std::string name_{}; | |||
bool initialized_{}; | |||
}; | |||
extern MPI_t<> mpi; | |||
@@ -100,6 +112,42 @@ struct ShadowedVec_t { | |||
using const_iterator = typename std::vector<Value_t>::const_iterator; | |||
using size_type = typename std::vector<Value_t>::size_type; | |||
// Default constructor | |||
ShadowedVec_t() = default; | |||
// Constructor from an std::vector | |||
explicit ShadowedVec_t(const std::vector<Value_t>& vec) | |||
: North(vec), South(), active(north) { | |||
South.resize(North.size()); | |||
} | |||
explicit ShadowedVec_t(std::vector<Value_t>&& vec) | |||
: North(std::move(vec)), South(), active(north) { | |||
South.resize(North.size()); | |||
} | |||
// Copy assignment operator | |||
ShadowedVec_t& operator=(const ShadowedVec_t& other) { | |||
if (this != &other) { // Avoid self-assignment | |||
North = other.North; | |||
South = other.South; | |||
active = other.active; | |||
} | |||
return *this; | |||
} | |||
// Move assignment operator | |||
ShadowedVec_t& operator=(ShadowedVec_t&& other) noexcept { | |||
if (this != &other) { // Avoid self-assignment | |||
North = std::move(other.North); | |||
South = std::move(other.South); | |||
active = other.active; | |||
// There is no need to zero out other since it is valid but in a non-defined state | |||
} | |||
return *this; | |||
} | |||
// Dispatch to active vector | |||
Value_t& operator[](size_type index) { return getActive()[index]; } | |||
const Value_t& operator[](size_type index) const { return getActive()[index]; } | |||
@@ -155,10 +203,25 @@ struct ShadowedVec_t { | |||
const std::vector<Value_t>& getShadow() const { | |||
return (active == north) ? South : North; | |||
} | |||
// Comparisons | |||
bool operator== (const ShadowedVec_t& other) { | |||
return getActive() == other.getActive(); | |||
} | |||
bool operator!= (const ShadowedVec_t& other) { | |||
return getActive() != other.getActive(); | |||
} | |||
bool operator== (const std::vector<value_type>& other) { | |||
return getActive() == other; | |||
} | |||
bool operator!= (const std::vector<value_type>& other) { | |||
return getActive() != other; | |||
} | |||
private: | |||
enum { north, south } active{north}; | |||
std::vector<Value_t> North{}; | |||
std::vector<Value_t> South{}; | |||
enum { north, south } active{north}; | |||
}; | |||
using distBuffer_t = ShadowedVec_t<distValue_t>; | |||
@@ -6,26 +6,16 @@ | |||
* Christos Choutouridis AEM:8997 | |||
* <cchoutou@ece.auth.gr> | |||
*/ | |||
#if !defined DEBUG | |||
#define NDEBUG | |||
#endif | |||
#include <cassert> | |||
#include "utils.hpp" | |||
#include "distsort.hpp" | |||
/*! | |||
* 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 { | |||
assert(nodes > 0); | |||
return (node >= 0) && (node < (nodes-1)); | |||
bool isActive(mpi_id_t node, size_t nodes) { | |||
if (!((nodes > 0) && | |||
(nodes <= std::numeric_limits<mpi_id_t>::max()) )) | |||
throw std::runtime_error("(isActive) Non-acceptable value of MPI Nodes\n"); | |||
// ^ Assert that mpi_id_t can hold nodes, and thus we can cast without data loss! | |||
return (node >= 0) && (node < static_cast<mpi_id_t>(nodes)); | |||
} | |||
@@ -10,6 +10,7 @@ | |||
#include <exception> | |||
#include <iostream> | |||
#include <algorithm> | |||
#include <random> | |||
#include "utils.hpp" | |||
#include "config.h" | |||
@@ -121,9 +122,15 @@ int main(int argc, char* argv[]) try { | |||
#endif | |||
logger << "Initialize local array of " << session.arraySize << " elements" << logger.endl; | |||
std::srand(unsigned(std::time(nullptr))); | |||
std::random_device rd; // Mersenne seeded from hw if possible. range: [type_min, type_max] | |||
std::mt19937 gen(rd()); | |||
std::uniform_int_distribution<distValue_t > dis( | |||
std::numeric_limits<distValue_t>::min(), | |||
std::numeric_limits<distValue_t>::max() | |||
); | |||
// Fill vector | |||
Data.resize(session.arraySize); | |||
std::generate(Data.begin(), Data.end(), std::rand); | |||
std::generate(Data.begin(), Data.end(), [&]() { return dis(gen); }); | |||
if (mpi.rank() == 0) | |||
logger << "Starting distributed sorting ... "; | |||
@@ -139,7 +146,7 @@ int main(int argc, char* argv[]) try { | |||
std::string timeMsg = "rank " + std::to_string(mpi.rank()); | |||
timer.print_dt(timeMsg.c_str()); | |||
std::cout << "[Data]: Rank " << mpi.rank() << ": [" << (int)Data.front() << " .. " << (int)Data.back() << "]" << std::endl; | |||
std::cout << "[Data]: Rank " << mpi.rank() << ": [" << +Data.front() << " .. " << +Data.back() << "]" << std::endl; | |||
mpi.finalize(); | |||
return 0; | |||
} | |||
@@ -230,12 +230,13 @@ TEST(TdistBitonic_UT, partner_test3) { | |||
/* ================================== keepSmall ================================== */ | |||
/* | |||
* bool keepSmall(mpi_id_t node, mpi_id_t partner, size_t depth); | |||
* Assertion check | |||
* Throw check (Not assert - ASSERT_DEATH) | |||
*/ | |||
TEST(TdistBitonic_UT, keepsmall_test1) { | |||
ASSERT_DEATH(keepSmall<SortMode::Bitonic>(0, 0, 0), ""); | |||
ASSERT_DEATH(keepSmall<SortMode::Bitonic>(1, 1, 42), ""); | |||
ASSERT_DEATH(keepSmall<SortMode::Bitonic>(7, 7, 42), ""); | |||
// node and partner must differ or else ... | |||
EXPECT_THROW(keepSmall<SortMode::Bitonic>(0, 0, 0), std::runtime_error); | |||
EXPECT_THROW(keepSmall<SortMode::Bitonic>(1, 1, 42), std::runtime_error); | |||
EXPECT_THROW(keepSmall<SortMode::Bitonic>(7, 7, 42), std::runtime_error); | |||
} | |||
/* | |||
@@ -95,12 +95,13 @@ TEST(TdistBubbletonic_UT, partner_Bubbletonic_test3) { | |||
/* ================================== keepSmall ================================== */ | |||
/* | |||
* bool keepSmall<SortMode::Bubbletonic>(mpi_id_t node, mpi_id_t partner, size_t depth); | |||
* Assertion check | |||
* Throw check (Not assert - ASSERT_DEATH) | |||
*/ | |||
TEST(TdistBubbletonic_UT, keepsmall_test1) { | |||
ASSERT_DEATH(keepSmall<SortMode::Bubbletonic>(0, 0, 0), ""); | |||
ASSERT_DEATH(keepSmall<SortMode::Bubbletonic>(1, 1, 42), ""); | |||
ASSERT_DEATH(keepSmall<SortMode::Bubbletonic>(7, 7, 42), ""); | |||
// node and partner must differ or else ... | |||
EXPECT_THROW(keepSmall<SortMode::Bubbletonic>(0, 0, 0), std::runtime_error); | |||
EXPECT_THROW(keepSmall<SortMode::Bubbletonic>(1, 1, 42), std::runtime_error); | |||
EXPECT_THROW(keepSmall<SortMode::Bubbletonic>(7, 7, 42), std::runtime_error); | |||
} | |||
/* | |||
@@ -119,6 +120,30 @@ TEST(TdistBubbletonic_UT, keepsmall_test2) { | |||
EXPECT_EQ(keepSmall<SortMode::Bubbletonic>(4, 9, 42), true); | |||
} | |||
/* ================================== isActive ================================== */ | |||
/* | |||
* bool isActive(mpi_id_t node, size_t nodes); | |||
* Throw check | |||
*/ | |||
TEST(TdistBubbletonic_UT, isActive_test1) { | |||
EXPECT_THROW(isActive(0, 0), std::runtime_error); | |||
EXPECT_THROW(isActive(0, static_cast<size_t>(std::numeric_limits<mpi_id_t>::max()) + 1), std::runtime_error); | |||
} | |||
/* | |||
* bool isActive(mpi_id_t node, size_t nodes); | |||
* Boundary 3 BVA | |||
*/ | |||
TEST(TdistBubbletonic_UT, isActive_test2) { | |||
EXPECT_EQ(isActive(-1, 8), false); | |||
EXPECT_EQ(isActive(0, 8), true); | |||
EXPECT_EQ(isActive(1, 8), true); | |||
EXPECT_EQ(isActive(7, 8), true); | |||
EXPECT_EQ(isActive(8, 8), false); | |||
EXPECT_EQ(isActive(9, 8), false); | |||
} | |||
#if 0 | |||
TEST(TdistBubbletonic_UT, distBubbletonic_test1) { | |||
@@ -0,0 +1,156 @@ | |||
/** | |||
* \file | |||
* \brief PDS HW2 tests | |||
* | |||
* \author | |||
* Christos Choutouridis AEM:8997 | |||
* <cchoutou@ece.auth.gr> | |||
*/ | |||
#include <gtest/gtest.h> | |||
#include <algorithm> // rand/srand | |||
#include <ctime> // rand/srand | |||
#include "distsort.hpp" | |||
/* ================================== fullSort ================================== */ | |||
/* | |||
* | |||
*/ | |||
TEST(TdistCommonUT, fullSort_test1) { | |||
std::vector<uint8_t> ts_data = {3, 2, 1, 4, 5, 7, 8, 6}; | |||
std::vector<uint8_t> ts_expected = {1, 2, 3, 4, 5, 6, 7, 8}; | |||
bool ts_ascending = true; | |||
fullSort(ts_data, ts_ascending); | |||
EXPECT_EQ((ts_data == ts_expected), true); | |||
} | |||
TEST(TdistCommonUT, fullSort_test2) { | |||
std::vector<uint8_t> ts_data = {3, 2, 1, 4, 5, 7, 8, 6}; | |||
std::vector<uint8_t> ts_expected = {8, 7, 6, 5, 4, 3, 2, 1}; | |||
bool ts_ascending = false; | |||
fullSort(ts_data, ts_ascending); | |||
EXPECT_EQ((ts_data == ts_expected), true); | |||
} | |||
/* ================================== elbowSort ================================== */ | |||
TEST(TdistCommonUT, elbowSort_test1) { | |||
ShadowedVec_t<uint8_t> ts_data1(std::vector<uint8_t>{3, 2, 1, 4, 5, 5, 7, 8}); | |||
ShadowedVec_t<uint8_t> ts_data2(std::vector<uint8_t>{4, 5, 7, 8, 5, 3, 2, 1}); | |||
ShadowedVec_t<uint8_t> ts_data3(std::vector<uint8_t>{1, 2, 3, 4, 5, 5, 7, 8}); | |||
ShadowedVec_t<uint8_t> ts_data4(std::vector<uint8_t>{8, 7, 5, 5, 4, 3, 2, 1}); | |||
std::vector<uint8_t> ts_expected = {1, 2, 3, 4, 5, 5, 7, 8}; | |||
bool ts_ascending = true; | |||
elbowSort(ts_data1, ts_ascending); | |||
elbowSort(ts_data2, ts_ascending); | |||
elbowSort(ts_data3, ts_ascending); | |||
elbowSort(ts_data4, ts_ascending); | |||
EXPECT_EQ((ts_data1 == ts_expected), true); | |||
EXPECT_EQ((ts_data2 == ts_expected), true); | |||
EXPECT_EQ((ts_data3 == ts_expected), true); | |||
EXPECT_EQ((ts_data4 == ts_expected), true); | |||
} | |||
TEST(TdistCommonUT, elbowSort_test2) { | |||
ShadowedVec_t<uint8_t> ts_data1(std::vector<uint8_t>{3, 2, 1, 4, 5, 5, 7, 8}); | |||
ShadowedVec_t<uint8_t> ts_data2(std::vector<uint8_t>{4, 5, 7, 8, 5, 3, 2, 1}); | |||
ShadowedVec_t<uint8_t> ts_data3(std::vector<uint8_t>{1, 2, 3, 4, 5, 5, 7, 8}); | |||
ShadowedVec_t<uint8_t> ts_data4(std::vector<uint8_t>{8, 7, 5, 5, 4, 3, 2, 1}); | |||
std::vector<uint8_t> ts_expected = {8, 7, 5, 5, 4, 3, 2, 1}; | |||
bool ts_ascending = false; | |||
elbowSort(ts_data1, ts_ascending); | |||
elbowSort(ts_data2, ts_ascending); | |||
elbowSort(ts_data3, ts_ascending); | |||
elbowSort(ts_data4, ts_ascending); | |||
EXPECT_EQ((ts_data1 == ts_expected), true); | |||
EXPECT_EQ((ts_data2 == ts_expected), true); | |||
EXPECT_EQ((ts_data3 == ts_expected), true); | |||
EXPECT_EQ((ts_data4 == ts_expected), true); | |||
} | |||
TEST(TdistCommonUT, elbowSort_test3) { | |||
ShadowedVec_t<uint8_t> ts_data(std::vector<uint8_t>{8, 7, 5, 5, 4, 3, 2, 1}); | |||
std::vector<uint8_t> ts_expected_asc = {1, 2, 3, 4, 5, 5, 7, 8}; | |||
std::vector<uint8_t> ts_expected_des = {8, 7, 5, 5, 4, 3, 2, 1}; | |||
// Check alternation for active-shadow vector inside Buffer and elbow algorithm | |||
elbowSort(ts_data, true); | |||
EXPECT_EQ((ts_data == ts_expected_asc), true); | |||
elbowSort(ts_data, false); | |||
EXPECT_EQ((ts_data == ts_expected_des), true); | |||
elbowSort(ts_data, true); | |||
EXPECT_EQ((ts_data == ts_expected_asc), true); | |||
elbowSort(ts_data, false); | |||
EXPECT_EQ((ts_data == ts_expected_des), true); | |||
} | |||
#if 0 | |||
TEST(TdistBubbletonic_UT, distBubbletonic_test1) { | |||
AllData_t ts_Data { | |||
ShadowedVec_t (8), ShadowedVec_t (8) | |||
}; | |||
std::srand(unsigned(std::time(nullptr))); | |||
for (auto& v : ts_Data) { | |||
std::generate(v.begin(), v.end(), std::rand); | |||
} | |||
distBubbletonic(2, ts_Data); | |||
auto max = std::numeric_limits<ShadowedVec_t::value_type>::min(); | |||
for (auto& v : ts_Data) { | |||
EXPECT_EQ((max <= v[0]), true); | |||
EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | |||
max = v.back(); | |||
} | |||
} | |||
TEST(TdistBubbletonic_UT, distBubbletonic_test2) { | |||
AllData_t ts_Data { | |||
ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8) | |||
}; | |||
std::srand(unsigned(std::time(nullptr))); | |||
for (auto& v : ts_Data) { | |||
std::generate(v.begin(), v.end(), std::rand); | |||
} | |||
distBubbletonic(4, ts_Data); | |||
auto max = std::numeric_limits<ShadowedVec_t::value_type>::min(); | |||
for (auto& v : ts_Data) { | |||
EXPECT_EQ((max <= v[0]), true); | |||
EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | |||
max = v.back(); | |||
} | |||
} | |||
TEST(TdistBubbletonic_UT, distBubbletonic_test3) { | |||
AllData_t ts_Data { | |||
ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32), | |||
ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32) | |||
}; | |||
std::srand(unsigned(std::time(nullptr))); | |||
for (auto& v : ts_Data) { | |||
std::generate(v.begin(), v.end(), std::rand); | |||
} | |||
distBubbletonic(8, ts_Data); | |||
auto max = std::numeric_limits<ShadowedVec_t::value_type>::min(); | |||
for (auto& v : ts_Data) { | |||
EXPECT_EQ((max <= v[0]), true); | |||
EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | |||
max = v.back(); | |||
} | |||
} | |||
#endif |