@@ -24,7 +24,18 @@ | |||||
#define CODE_VERSION BITONIC | #define CODE_VERSION BITONIC | ||||
#endif | #endif | ||||
// Value type selection | |||||
/*! | |||||
* Value type selection | |||||
* | |||||
* We support the following compiler types or the <cstdint> that translate to them: | |||||
* char - unsigned char | |||||
* short - unsigned short | |||||
* int - unsigned int | |||||
* long - unsigned long | |||||
* long long - unsigned long long | |||||
* float | |||||
* double | |||||
*/ | |||||
using distValue_t = uint32_t; | using distValue_t = uint32_t; | ||||
/*! | /*! | ||||
@@ -267,26 +267,26 @@ void minmax(RangeT& local, const RangeT& remote, bool keepSmall) noexcept { | |||||
* @param Processes [mpi_id_t] The total number of MPI processes | * @param Processes [mpi_id_t] The total number of MPI processes | ||||
*/ | */ | ||||
template<typename ShadowedT> | template<typename ShadowedT> | ||||
void distBubbletonic(ShadowedT& data, mpi_id_t Processes) { | |||||
void distBubbletonic(ShadowedT& data, mpi_id_t Processes, mpi_id_t rank) { | |||||
// Initially sort to create a half part of a bitonic sequence | // Initially sort to create a half part of a bitonic sequence | ||||
fullSort(data, ascending<SortMode::Bubbletonic>(mpi.rank(), 0)); | |||||
fullSort(data, ascending<SortMode::Bubbletonic>(rank, 0)); | |||||
// Sort network (O(N) iterations) | // Sort network (O(N) iterations) | ||||
for (size_t step = 0; step < static_cast<size_t>(Processes-1); ++step) { | |||||
for (size_t step = 0; step < static_cast<size_t>(Processes); ++step) { | |||||
// Find out exchange configuration | // 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) && | |||||
auto part = partner<SortMode::Bubbletonic>(rank, step); | |||||
auto ks = keepSmall<SortMode::Bubbletonic>(rank, part, Processes); | |||||
if ( isActive(rank, Processes) && | |||||
isActive(part, Processes) ) { | isActive(part, Processes) ) { | ||||
// Exchange with partner, keep nim-or-max and sort - O(N) | // Exchange with partner, keep nim-or-max and sort - O(N) | ||||
mpi.exchange(part, data.getActive(), data.getShadow(), step); | mpi.exchange(part, data.getActive(), data.getShadow(), step); | ||||
minmax(data.getActive(), data.getShadow(), ks); | minmax(data.getActive(), data.getShadow(), ks); | ||||
elbowSort(data, ascending<SortMode::Bubbletonic>(mpi.rank(), Processes)); | |||||
elbowSort(data, ascending<SortMode::Bubbletonic>(rank, Processes)); | |||||
} | } | ||||
} | } | ||||
// Invert if the node was descending. | // Invert if the node was descending. | ||||
if (!ascending<SortMode::Bubbletonic>(mpi.rank(), 0)) | |||||
if (!ascending<SortMode::Bubbletonic>(rank, 0)) | |||||
elbowSort(data, true); | elbowSort(data, true); | ||||
} | } | ||||
@@ -304,9 +304,9 @@ void distBubbletonic(ShadowedT& data, mpi_id_t Processes) { | |||||
* @param Processes [mpi_id_t] The total number of MPI processes | * @param Processes [mpi_id_t] The total number of MPI processes | ||||
*/ | */ | ||||
template<typename ShadowedT> | template<typename ShadowedT> | ||||
void distBitonic(ShadowedT& data, mpi_id_t Processes) { | |||||
void distBitonic(ShadowedT& data, mpi_id_t Processes, mpi_id_t rank) { | |||||
// Initially sort to create a half part of a bitonic sequence | // Initially sort to create a half part of a bitonic sequence | ||||
fullSort(data, ascending<SortMode::Bitonic>(mpi.rank(), 0)); | |||||
fullSort(data, ascending<SortMode::Bitonic>(rank, 0)); | |||||
// Run through sort network using elbow-sort ( O(LogN * LogN) iterations ) | // Run through sort network using elbow-sort ( O(LogN * LogN) iterations ) | ||||
auto p = static_cast<uint32_t>(std::log2(Processes)); | auto p = static_cast<uint32_t>(std::log2(Processes)); | ||||
@@ -314,14 +314,14 @@ void distBitonic(ShadowedT& data, mpi_id_t Processes) { | |||||
for (size_t step = depth; step > 0;) { | for (size_t step = depth; step > 0;) { | ||||
--step; | --step; | ||||
// Find out exchange configuration | // Find out exchange configuration | ||||
auto part = partner<SortMode::Bitonic>(mpi.rank(), step); | |||||
auto ks = keepSmall<SortMode::Bitonic>(mpi.rank(), part, depth); | |||||
auto part = partner<SortMode::Bitonic>(rank, step); | |||||
auto ks = keepSmall<SortMode::Bitonic>(rank, part, depth); | |||||
// Exchange with partner, keep nim-or-max | // Exchange with partner, keep nim-or-max | ||||
mpi.exchange(part, data.getActive(), data.getShadow(), (depth << 8) | step); | mpi.exchange(part, data.getActive(), data.getShadow(), (depth << 8) | step); | ||||
minmax(data.getActive(), data.getShadow(), ks); | minmax(data.getActive(), data.getShadow(), ks); | ||||
} | } | ||||
// sort - O(N) | // sort - O(N) | ||||
elbowSort (data, ascending<SortMode::Bitonic>(mpi.rank(), depth)); | |||||
elbowSort (data, ascending<SortMode::Bitonic>(rank, depth)); | |||||
} | } | ||||
} | } | ||||
@@ -15,68 +15,94 @@ | |||||
#include <unistd.h> | #include <unistd.h> | ||||
#include <mpi.h> | #include <mpi.h> | ||||
//#include "matrix.hpp" | |||||
#include "config.h" | #include "config.h" | ||||
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; } }; | |||||
/* | |||||
* MPI_<type> dispatcher mechanism | |||||
*/ | |||||
template <typename T> struct MPI_TypeMapper { }; | |||||
template <> struct MPI_TypeMapper<char> { static MPI_Datatype getType() { return MPI_CHAR; } }; | |||||
template <> struct MPI_TypeMapper<short> { static MPI_Datatype getType() { return MPI_SHORT; } }; | 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<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> { 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<long long> { static MPI_Datatype getType() { return MPI_LONG_LONG; } }; | ||||
template <> struct MPI_TypeMapper<unsigned char> { static MPI_Datatype getType() { return MPI_UNSIGNED_CHAR; } }; | |||||
template <> struct MPI_TypeMapper<unsigned short>{ static MPI_Datatype getType() { return MPI_UNSIGNED_SHORT; } }; | 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 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> { 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<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<float> { static MPI_Datatype getType() { return MPI_FLOAT; } }; | ||||
template <> struct MPI_TypeMapper<double> { static MPI_Datatype getType() { return MPI_DOUBLE; } }; | template <> struct MPI_TypeMapper<double> { static MPI_Datatype getType() { return MPI_DOUBLE; } }; | ||||
/*! | |||||
* MPI wrapper type to provide MPI functionality and RAII to MPI as a resource | |||||
* | |||||
* @tparam TID The MPI type for process id [default: int] | |||||
*/ | |||||
template<typename TID = int> | template<typename TID = int> | ||||
struct MPI_t { | struct MPI_t { | ||||
using ID_t = TID; // Export TID type (currently int defined by the standard) | using ID_t = TID; // Export TID type (currently int defined by the standard) | ||||
/*! | |||||
* Initializes the MPI environment, must called from each process | |||||
* | |||||
* @param argc [int*] POINTER to main's argc argument | |||||
* @param argv [car***] POINTER to main's argv argument | |||||
*/ | |||||
void init(int *argc, char ***argv) { | void init(int *argc, char ***argv) { | ||||
// Initialize the MPI environment | // Initialize the MPI environment | ||||
MPI_Init(argc, argv); | |||||
int err; | |||||
if ((err = MPI_Init(argc, argv)) != MPI_SUCCESS) | |||||
mpi_throw(err, "(MPI) MPI_Init() - "); | |||||
initialized_ = true; | initialized_ = true; | ||||
// Get the number of processes | // Get the number of processes | ||||
int size_value, rank_value; | int size_value, rank_value; | ||||
MPI_Comm_size(MPI_COMM_WORLD, &size_value); | |||||
MPI_Comm_rank(MPI_COMM_WORLD, &rank_value); | |||||
if ((err = MPI_Comm_size(MPI_COMM_WORLD, &size_value)) != MPI_SUCCESS) | |||||
mpi_throw(err, "(MPI) MPI_Comm_size() - "); | |||||
if ((err = MPI_Comm_rank(MPI_COMM_WORLD, &rank_value)) != MPI_SUCCESS) | |||||
mpi_throw(err, "(MPI) MPI_Comm_rank() - "); | |||||
size_ = static_cast<ID_t>(size_value); | size_ = static_cast<ID_t>(size_value); | ||||
rank_ = static_cast<ID_t>(rank_value); | rank_ = static_cast<ID_t>(rank_value); | ||||
// Get the name of the processor | // Get the name of the processor | ||||
char processor_name[MPI_MAX_PROCESSOR_NAME]; | char processor_name[MPI_MAX_PROCESSOR_NAME]; | ||||
int name_len; | int name_len; | ||||
MPI_Get_processor_name(processor_name, &name_len); | |||||
if ((err = MPI_Get_processor_name(processor_name, &name_len)) != MPI_SUCCESS) | |||||
mpi_throw(err, "(MPI) MPI_Get_processor_name() - "); | |||||
name_ = std::string (processor_name, name_len); | name_ = std::string (processor_name, name_len); | ||||
} | } | ||||
/*! | |||||
* Exchange data with partner as part of the sorting network of both bubbletonic or bitonic | |||||
* sorting algorithms. | |||||
* | |||||
* This function matches a transmit and a receive in order for fully exchanged data between | |||||
* current node and partner. | |||||
* | |||||
* @tparam T The inner valur type used in buffer | |||||
* | |||||
* @param partner [mpi_id_t] The partner for the exchange | |||||
* @param send_data [std::vector<T>] Reference to local data to send | |||||
* @param recv_data [std::vector<T>] Reference to buffer to receive data from partner | |||||
* @param tag [int] The tag to use for the MPI communication | |||||
*/ | |||||
template<typename T> | template<typename T> | ||||
void exchange(ID_t partner, const std::vector<T>& send_data, std::vector<T>& recv_data, int tag) { | void exchange(ID_t partner, const std::vector<T>& send_data, std::vector<T>& recv_data, int tag) { | ||||
using namespace std::string_literals; | using namespace std::string_literals; | ||||
MPI_Status status; | |||||
MPI_Datatype datatype = MPI_TypeMapper<T>::getType(); | MPI_Datatype datatype = MPI_TypeMapper<T>::getType(); | ||||
int send_count = static_cast<int>(send_data.size()); | int send_count = static_cast<int>(send_data.size()); | ||||
int err = MPI_Sendrecv( | |||||
MPI_Status status; | |||||
int err; | |||||
if ((err = MPI_Sendrecv( | |||||
send_data.data(), send_count, datatype, partner, tag, | send_data.data(), send_count, datatype, partner, tag, | ||||
recv_data.data(), send_count, datatype, partner, tag, | recv_data.data(), send_count, datatype, partner, tag, | ||||
MPI_COMM_WORLD, &status | MPI_COMM_WORLD, &status | ||||
); | |||||
if (err != MPI_SUCCESS) { | |||||
char err_msg[MPI_MAX_ERROR_STRING]; | |||||
int msg_len; | |||||
MPI_Error_string(err, err_msg, &msg_len); | |||||
throw std::runtime_error("(MPI) MPI_Sendrecv() - " + std::string (err_msg) + '\n'); | |||||
} | |||||
)) != MPI_SUCCESS) | |||||
mpi_throw(err, "(MPI) MPI_Sendrecv() - "); | |||||
} | } | ||||
// Accessors | // Accessors | ||||
@@ -84,26 +110,65 @@ struct MPI_t { | |||||
[[nodiscard]] ID_t size() const noexcept { return size_; } | [[nodiscard]] ID_t size() const noexcept { return size_; } | ||||
[[nodiscard]] const std::string& name() const noexcept { return name_; } | [[nodiscard]] const std::string& name() const noexcept { return name_; } | ||||
/*! | |||||
* Finalized the MPI | |||||
*/ | |||||
void finalize() { | void finalize() { | ||||
// Finalize the MPI environment | // Finalize the MPI environment | ||||
initialized_ = false; | initialized_ = false; | ||||
MPI_Finalize(); | MPI_Finalize(); | ||||
} | } | ||||
//! RAII MPI finalization | |||||
~MPI_t() { | ~MPI_t() { | ||||
// Finalize the MPI environment even on unexpected errors | // Finalize the MPI environment even on unexpected errors | ||||
if (initialized_) | if (initialized_) | ||||
MPI_Finalize(); | MPI_Finalize(); | ||||
} | } | ||||
// Local functionality | |||||
private: | |||||
/*! | |||||
* Throw exception helper. It bundles the prefix with the MPI error string retrieved by | |||||
* MPI API. | |||||
* | |||||
* @param err The MPI error code | |||||
* @param prefixMsg The prefix text for the exception error message | |||||
*/ | |||||
void mpi_throw(int err, const char* prefixMsg) { | |||||
char err_msg[MPI_MAX_ERROR_STRING]; | |||||
int msg_len; | |||||
MPI_Error_string(err, err_msg, &msg_len); | |||||
throw std::runtime_error(prefixMsg + std::string (err_msg) + '\n'); | |||||
} | |||||
#if !defined TESTING | |||||
private: | private: | ||||
ID_t rank_{}; | |||||
ID_t size_{}; | |||||
std::string name_{}; | |||||
bool initialized_{}; | |||||
#else | |||||
public: | |||||
#endif | |||||
ID_t rank_{}; //!< MPI rank of the process | |||||
ID_t size_{}; //!< MPI total size of the execution | |||||
std::string name_{}; //!< The name of the local machine | |||||
bool initialized_{}; //!< RAII helper flag | |||||
}; | }; | ||||
/* | |||||
* Exported data types | |||||
*/ | |||||
extern MPI_t<> mpi; | extern MPI_t<> mpi; | ||||
using mpi_id_t = MPI_t<>::ID_t; | using mpi_id_t = MPI_t<>::ID_t; | ||||
/*! | |||||
* A std::vector wrapper with 2 vectors, an active and a shadow. This type exposes the standard vector | |||||
* functionality of the active vector. The shadow can be used when we need to use the vector as mutable | |||||
* data in algorithms that can not support "in-place" editing (like elbow-sort for example) | |||||
* | |||||
* @tparam Value_t the inner data type of the vectors | |||||
*/ | |||||
template <typename Value_t> | template <typename Value_t> | ||||
struct ShadowedVec_t { | struct ShadowedVec_t { | ||||
// STL requirements | // STL requirements | ||||
@@ -148,7 +213,18 @@ struct ShadowedVec_t { | |||||
return *this; | return *this; | ||||
} | } | ||||
// Dispatch to active vector | |||||
// Type accessors | |||||
const std::vector<Value_t>& getNorth() const { return North; } | |||||
const std::vector<Value_t>& getSouth() const { return South; } | |||||
std::vector<Value_t>& getActive() { return (active == north) ? North : South; } | |||||
std::vector<Value_t>& getShadow() { return (active == north) ? South : North; } | |||||
const std::vector<Value_t>& getActive() const { return (active == north) ? North : South; } | |||||
const std::vector<Value_t>& getShadow() const { return (active == north) ? South : North; } | |||||
// Switching vectors | |||||
void switch_active() { active = (active == north) ? south : north; } | |||||
// Dispatch to active vector functionality | |||||
Value_t& operator[](size_type index) { return getActive()[index]; } | Value_t& operator[](size_type index) { return getActive()[index]; } | ||||
const Value_t& operator[](size_type index) const { return getActive()[index]; } | const Value_t& operator[](size_type index) const { return getActive()[index]; } | ||||
@@ -156,12 +232,12 @@ struct ShadowedVec_t { | |||||
const Value_t& at(size_type index) const { return getActive().at(index); } | const Value_t& at(size_type index) const { return getActive().at(index); } | ||||
void push_back(const Value_t& value) { getActive().push_back(value); } | void push_back(const Value_t& value) { getActive().push_back(value); } | ||||
void push_back(Value_t&& value) { getActive().push_back(std::move(value)); } | |||||
void pop_back() { getActive().pop_back(); } | |||||
void push_back(Value_t&& value) { getActive().push_back(std::move(value)); } | |||||
void pop_back() { getActive().pop_back(); } | |||||
Value_t& front() { return getActive().front(); } | Value_t& front() { return getActive().front(); } | ||||
Value_t& back() { return getActive().back(); } | |||||
const Value_t& front() const { return getActive().front(); } | const Value_t& front() const { return getActive().front(); } | ||||
Value_t& back() { return getActive().back(); } | |||||
const Value_t& back() const { return getActive().back(); } | |||||
const Value_t& back() const { return getActive().back(); } | |||||
iterator begin() { return getActive().begin(); } | iterator begin() { return getActive().begin(); } | ||||
const_iterator begin() const { return getActive().begin(); } | const_iterator begin() const { return getActive().begin(); } | ||||
@@ -182,46 +258,20 @@ struct ShadowedVec_t { | |||||
[[nodiscard]] bool empty() const { return getActive().empty(); } | [[nodiscard]] bool empty() const { return getActive().empty(); } | ||||
void clear() { getActive().clear(); } | void clear() { getActive().clear(); } | ||||
void swap(std::vector<Value_t>& other) { getActive().swap(other); } | void swap(std::vector<Value_t>& other) { getActive().swap(other); } | ||||
// Switching vectors | |||||
void switch_active() { active = (active == north) ? south : north; } | |||||
// Accessors | |||||
const std::vector<Value_t>& getNorth() const { return North; } | |||||
const std::vector<Value_t>& getSouth() const { return South; } | |||||
std::vector<Value_t>& getActive() { | |||||
return (active == north) ? North : South; | |||||
} | |||||
const std::vector<Value_t>& getActive() const { | |||||
return (active == north) ? North : South; | |||||
} | |||||
std::vector<Value_t>& getShadow() { | |||||
return (active == north) ? South : North; | |||||
} | |||||
const std::vector<Value_t>& getShadow() const { | |||||
return (active == north) ? South : North; | |||||
} | |||||
// Comparisons | // 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; | |||||
} | |||||
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: | private: | ||||
std::vector<Value_t> North{}; | |||||
std::vector<Value_t> South{}; | |||||
enum { north, south } active{north}; | |||||
std::vector<Value_t> North{}; //!< Actual buffer to be used either as active or shadow | |||||
std::vector<Value_t> South{}; //!< Actual buffer to be used either as active or shadow | |||||
enum { | |||||
north, south | |||||
} active{north}; //!< Flag to select between North and South buffer | |||||
}; | }; | ||||
using distBuffer_t = ShadowedVec_t<distValue_t>; | using distBuffer_t = ShadowedVec_t<distValue_t>; | ||||
@@ -136,9 +136,9 @@ int main(int argc, char* argv[]) try { | |||||
logger << "Starting distributed sorting ... "; | logger << "Starting distributed sorting ... "; | ||||
timer.start(); | timer.start(); | ||||
#if CODE_VERSION == BUBBLETONIC | #if CODE_VERSION == BUBBLETONIC | ||||
distBubbletonic(Data, mpi.size()); | |||||
distBubbletonic(Data, mpi.size(), mpi.rank()); | |||||
#else | #else | ||||
distBitonic (Data, mpi.size()); | |||||
distBitonic (Data, mpi.size(), mpi.rank()); | |||||
#endif | #endif | ||||
timer.stop(); | timer.stop(); | ||||
if (mpi.rank() == 0) | if (mpi.rank() == 0) | ||||
@@ -328,67 +328,3 @@ TEST(TdistBitonic_UT, keepsmall_test7) { | |||||
EXPECT_EQ(ts_expected[node], keepSmall<SortMode::Bitonic>(node, ts_partner[node], ts_depth)); | EXPECT_EQ(ts_expected[node], keepSmall<SortMode::Bitonic>(node, ts_partner[node], ts_depth)); | ||||
} | } | ||||
} | } | ||||
#if 0 | |||||
TEST(TdistBitonic_UT, distBitonic_test1) { | |||||
AllData_t ts_Data { | |||||
ShadowedVec_t (8), ShadowedVec_t (8) | |||||
}; | |||||
(unsigned(std::time(nullptr))); | |||||
for (auto | |||||
std::srand& v : ts_Data) { | |||||
std::generate(v.begin(), v.end(), std::rand); | |||||
} | |||||
distBitonic(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(TdistBitonic_UT, distBitonic_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); | |||||
} | |||||
distBitonic(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(TdistBitonic_UT, distBitonic_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); | |||||
} | |||||
distBitonic(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 |
@@ -142,69 +142,3 @@ TEST(TdistBubbletonic_UT, isActive_test2) { | |||||
EXPECT_EQ(isActive(8, 8), false); | EXPECT_EQ(isActive(8, 8), false); | ||||
EXPECT_EQ(isActive(9, 8), false); | EXPECT_EQ(isActive(9, 8), false); | ||||
} | } | ||||
#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 |
@@ -91,66 +91,3 @@ TEST(TdistCommonUT, elbowSort_test3) { | |||||
EXPECT_EQ((ts_data == ts_expected_des), true); | 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 |
@@ -0,0 +1,205 @@ | |||||
/** | |||||
* \file | |||||
* \brief PDS HW2 tests | |||||
* | |||||
* \author | |||||
* Christos Choutouridis AEM:8997 | |||||
* <cchoutou@ece.auth.gr> | |||||
*/ | |||||
#include <gtest/gtest.h> | |||||
#include <mpi.h> | |||||
#include <random> | |||||
#include "distsort.hpp" | |||||
MPI_t<> ts_mpi; | |||||
// Mersenne seeded from hw if possible. range: [type_min, type_max] | |||||
std::random_device rd; | |||||
std::mt19937 gen(rd()); | |||||
class TMPIdistSort : public ::testing::Test { | |||||
protected: | |||||
static void SetUpTestSuite() { | |||||
int argc = 0; | |||||
char** argv = nullptr; | |||||
MPI_Init(&argc, &argv); | |||||
int rank, size; | |||||
MPI_Comm_rank(MPI_COMM_WORLD, &rank); | |||||
MPI_Comm_size(MPI_COMM_WORLD, &size); | |||||
ts_mpi.rank_ = rank; | |||||
ts_mpi.size_ = size; | |||||
} | |||||
static void TearDownTestSuite() { | |||||
MPI_Finalize(); | |||||
} | |||||
}; | |||||
/* | |||||
* To run thiese test execute: | |||||
* mpirun -np <N> ./bit/tests | |||||
*/ | |||||
TEST_F(TMPIdistSort, distBubbletonic_test1) { | |||||
// Create and fill vector | |||||
using tsValue_t = uint8_t; // Test parameters | |||||
size_t ts_buffer_size = 16; | |||||
ShadowedVec_t<tsValue_t> ts_Data; | |||||
std::uniform_int_distribution<tsValue_t > dis( | |||||
std::numeric_limits<tsValue_t>::min(), | |||||
std::numeric_limits<tsValue_t>::max() | |||||
); | |||||
ts_Data.resize(ts_buffer_size); | |||||
std::generate(ts_Data.begin(), ts_Data.end(), [&]() { return dis(gen); }); | |||||
// Execute function under test in all processes | |||||
distBubbletonic(ts_Data, ts_mpi.size(), ts_mpi.rank()); | |||||
// Local min and max | |||||
auto local_min = *std::min_element(ts_Data.begin(), ts_Data.end()); | |||||
auto local_max = *std::max_element(ts_Data.begin(), ts_Data.end()); | |||||
// Gather min/max to rank 0 | |||||
std::vector<tsValue_t> global_mins(ts_mpi.size()); | |||||
std::vector<tsValue_t> global_maxes(ts_mpi.size()); | |||||
MPI_Datatype datatype = MPI_TypeMapper<tsValue_t>::getType(); | |||||
MPI_Gather(&local_min, 1, datatype, global_mins.data(), 1, datatype, 0, MPI_COMM_WORLD); | |||||
MPI_Gather(&local_max, 1, datatype, global_maxes.data(), 1, datatype, 0, MPI_COMM_WORLD); | |||||
// Check results | |||||
EXPECT_EQ(std::is_sorted(ts_Data.begin(), ts_Data.end()), true); | |||||
if (ts_mpi.rank() == 0) { | |||||
for (size_t i = 1; i < global_mins.size(); ++i) { | |||||
EXPECT_LE(global_maxes[i - 1], global_mins[i]); | |||||
} | |||||
} | |||||
} | |||||
/* | |||||
* To run thiese test execute: | |||||
* mpirun -np <N> ./bit/tests | |||||
*/ | |||||
TEST_F(TMPIdistSort, distBubbletonic_test2) { | |||||
// Create and fill vector | |||||
using tsValue_t = uint32_t; // Test parameters | |||||
size_t ts_buffer_size = 1 << 16; | |||||
ShadowedVec_t<tsValue_t> ts_Data; | |||||
std::uniform_int_distribution<tsValue_t > dis( | |||||
std::numeric_limits<tsValue_t>::min(), | |||||
std::numeric_limits<tsValue_t>::max() | |||||
); | |||||
ts_Data.resize(ts_buffer_size); | |||||
std::generate(ts_Data.begin(), ts_Data.end(), [&]() { return dis(gen); }); | |||||
// Execute function under test in all processes | |||||
distBubbletonic(ts_Data, ts_mpi.size(), ts_mpi.rank()); | |||||
// Local min and max | |||||
auto local_min = *std::min_element(ts_Data.begin(), ts_Data.end()); | |||||
auto local_max = *std::max_element(ts_Data.begin(), ts_Data.end()); | |||||
// Gather min/max to rank 0 | |||||
std::vector<tsValue_t> global_mins(ts_mpi.size()); | |||||
std::vector<tsValue_t> global_maxes(ts_mpi.size()); | |||||
MPI_Datatype datatype = MPI_TypeMapper<tsValue_t>::getType(); | |||||
MPI_Gather(&local_min, 1, datatype, global_mins.data(), 1, datatype, 0, MPI_COMM_WORLD); | |||||
MPI_Gather(&local_max, 1, datatype, global_maxes.data(), 1, datatype, 0, MPI_COMM_WORLD); | |||||
// Check results | |||||
EXPECT_EQ(std::is_sorted(ts_Data.begin(), ts_Data.end()), true); | |||||
if (ts_mpi.rank() == 0) { | |||||
for (size_t i = 1; i < global_mins.size(); ++i) { | |||||
EXPECT_LE(global_maxes[i - 1], global_mins[i]); | |||||
} | |||||
} | |||||
} | |||||
/* | |||||
* To run thiese test execute: | |||||
* mpirun -np <N> ./bit/tests | |||||
*/ | |||||
TEST_F(TMPIdistSort, distBitonic_test1) { | |||||
// Create and fill vector | |||||
using tsValue_t = uint8_t; // Test parameters | |||||
size_t ts_buffer_size = 16; | |||||
ShadowedVec_t<tsValue_t> ts_Data; | |||||
std::uniform_int_distribution<tsValue_t > dis( | |||||
std::numeric_limits<tsValue_t>::min(), | |||||
std::numeric_limits<tsValue_t>::max() | |||||
); | |||||
ts_Data.resize(ts_buffer_size); | |||||
std::generate(ts_Data.begin(), ts_Data.end(), [&]() { return dis(gen); }); | |||||
// Execute function under test in all processes | |||||
distBitonic(ts_Data, ts_mpi.size(), ts_mpi.rank()); | |||||
// Local min and max | |||||
auto local_min = *std::min_element(ts_Data.begin(), ts_Data.end()); | |||||
auto local_max = *std::max_element(ts_Data.begin(), ts_Data.end()); | |||||
// Gather min/max to rank 0 | |||||
std::vector<tsValue_t> global_mins(ts_mpi.size()); | |||||
std::vector<tsValue_t> global_maxes(ts_mpi.size()); | |||||
MPI_Datatype datatype = MPI_TypeMapper<tsValue_t>::getType(); | |||||
MPI_Gather(&local_min, 1, datatype, global_mins.data(), 1, datatype, 0, MPI_COMM_WORLD); | |||||
MPI_Gather(&local_max, 1, datatype, global_maxes.data(), 1, datatype, 0, MPI_COMM_WORLD); | |||||
// Check results | |||||
EXPECT_EQ(std::is_sorted(ts_Data.begin(), ts_Data.end()), true); | |||||
if (ts_mpi.rank() == 0) { | |||||
for (size_t i = 1; i < global_mins.size(); ++i) { | |||||
EXPECT_LE(global_maxes[i - 1], global_mins[i]); | |||||
} | |||||
} | |||||
} | |||||
/* | |||||
* To run thiese test execute: | |||||
* mpirun -np <N> ./bit/tests | |||||
*/ | |||||
TEST_F(TMPIdistSort, distBitonic_test2) { | |||||
// Create and fill vector | |||||
using tsValue_t = uint32_t; // Test parameters | |||||
size_t ts_buffer_size = 1 << 16; | |||||
ShadowedVec_t<tsValue_t> ts_Data; | |||||
std::uniform_int_distribution<tsValue_t > dis( | |||||
std::numeric_limits<tsValue_t>::min(), | |||||
std::numeric_limits<tsValue_t>::max() | |||||
); | |||||
ts_Data.resize(ts_buffer_size); | |||||
std::generate(ts_Data.begin(), ts_Data.end(), [&]() { return dis(gen); }); | |||||
// Execute function under test in all processes | |||||
distBitonic(ts_Data, ts_mpi.size(), ts_mpi.rank()); | |||||
// Local min and max | |||||
auto local_min = *std::min_element(ts_Data.begin(), ts_Data.end()); | |||||
auto local_max = *std::max_element(ts_Data.begin(), ts_Data.end()); | |||||
// Gather min/max to rank 0 | |||||
std::vector<tsValue_t> global_mins(ts_mpi.size()); | |||||
std::vector<tsValue_t> global_maxes(ts_mpi.size()); | |||||
MPI_Datatype datatype = MPI_TypeMapper<tsValue_t>::getType(); | |||||
MPI_Gather(&local_min, 1, datatype, global_mins.data(), 1, datatype, 0, MPI_COMM_WORLD); | |||||
MPI_Gather(&local_max, 1, datatype, global_maxes.data(), 1, datatype, 0, MPI_COMM_WORLD); | |||||
// Check results | |||||
EXPECT_EQ(std::is_sorted(ts_Data.begin(), ts_Data.end()), true); | |||||
if (ts_mpi.rank() == 0) { | |||||
for (size_t i = 1; i < global_mins.size(); ++i) { | |||||
EXPECT_LE(global_maxes[i - 1], global_mins[i]); | |||||
} | |||||
} | |||||
} | |||||