@@ -24,7 +24,18 @@ | |||
#define CODE_VERSION BITONIC | |||
#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; | |||
/*! | |||
@@ -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 | |||
*/ | |||
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 | |||
fullSort(data, ascending<SortMode::Bubbletonic>(mpi.rank(), 0)); | |||
fullSort(data, ascending<SortMode::Bubbletonic>(rank, 0)); | |||
// 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 | |||
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) ) { | |||
// 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)); | |||
elbowSort(data, ascending<SortMode::Bubbletonic>(rank, Processes)); | |||
} | |||
} | |||
// Invert if the node was descending. | |||
if (!ascending<SortMode::Bubbletonic>(mpi.rank(), 0)) | |||
if (!ascending<SortMode::Bubbletonic>(rank, 0)) | |||
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 | |||
*/ | |||
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 | |||
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 ) | |||
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;) { | |||
--step; | |||
// 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 | |||
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)); | |||
elbowSort (data, ascending<SortMode::Bitonic>(rank, depth)); | |||
} | |||
} | |||
@@ -15,68 +15,94 @@ | |||
#include <unistd.h> | |||
#include <mpi.h> | |||
//#include "matrix.hpp" | |||
#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<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 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 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; } }; | |||
/*! | |||
* 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> | |||
struct MPI_t { | |||
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) { | |||
// 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; | |||
// Get the number of processes | |||
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); | |||
rank_ = static_cast<ID_t>(rank_value); | |||
// Get the name of the processor | |||
char processor_name[MPI_MAX_PROCESSOR_NAME]; | |||
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); | |||
} | |||
/*! | |||
* 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> | |||
void exchange(ID_t partner, const std::vector<T>& send_data, std::vector<T>& recv_data, int tag) { | |||
using namespace std::string_literals; | |||
MPI_Status status; | |||
MPI_Datatype datatype = MPI_TypeMapper<T>::getType(); | |||
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, | |||
recv_data.data(), send_count, datatype, partner, tag, | |||
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 | |||
@@ -84,26 +110,65 @@ struct MPI_t { | |||
[[nodiscard]] ID_t size() const noexcept { return size_; } | |||
[[nodiscard]] const std::string& name() const noexcept { return name_; } | |||
/*! | |||
* Finalized the MPI | |||
*/ | |||
void finalize() { | |||
// Finalize the MPI environment | |||
initialized_ = false; | |||
MPI_Finalize(); | |||
} | |||
//! RAII MPI finalization | |||
~MPI_t() { | |||
// Finalize the MPI environment even on unexpected errors | |||
if (initialized_) | |||
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: | |||
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; | |||
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> | |||
struct ShadowedVec_t { | |||
// STL requirements | |||
@@ -148,7 +213,18 @@ struct ShadowedVec_t { | |||
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]; } | |||
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); } | |||
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& back() { return getActive().back(); } | |||
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(); } | |||
const_iterator begin() const { return getActive().begin(); } | |||
@@ -182,46 +258,20 @@ struct ShadowedVec_t { | |||
[[nodiscard]] bool empty() const { return getActive().empty(); } | |||
void clear() { getActive().clear(); } | |||
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 | |||
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: | |||
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>; | |||
@@ -136,9 +136,9 @@ int main(int argc, char* argv[]) try { | |||
logger << "Starting distributed sorting ... "; | |||
timer.start(); | |||
#if CODE_VERSION == BUBBLETONIC | |||
distBubbletonic(Data, mpi.size()); | |||
distBubbletonic(Data, mpi.size(), mpi.rank()); | |||
#else | |||
distBitonic (Data, mpi.size()); | |||
distBitonic (Data, mpi.size(), mpi.rank()); | |||
#endif | |||
timer.stop(); | |||
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)); | |||
} | |||
} | |||
#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(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); | |||
} | |||
#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]); | |||
} | |||
} | |||
} | |||