From 4d1d7502aadd4057de689acd166c62e5c1a6bf99 Mon Sep 17 00:00:00 2001 From: Christos Choutouridis Date: Mon, 30 Dec 2024 14:23:13 +0200 Subject: [PATCH] HW2: (WIP) Checkpoint with code re-arrangement and elbowSort --- homework_2/include/config.h | 11 +- homework_2/include/distbitonic.hpp | 73 --- homework_2/include/distsort.hpp | 288 +++++++++ homework_2/include/impl.hpp | 14 - homework_2/include/matrix.hpp | 804 -------------------------- homework_2/include/utils.hpp | 121 +++- homework_2/src/distbitonic.cpp | 212 ------- homework_2/src/distsort.cpp | 31 + homework_2/src/main.cpp | 114 ++-- homework_2/test/tests_Bitonic.cpp | 51 +- homework_2/test/tests_Bubbletonic.cpp | 19 +- 11 files changed, 545 insertions(+), 1193 deletions(-) delete mode 100644 homework_2/include/distbitonic.hpp create mode 100644 homework_2/include/distsort.hpp delete mode 100644 homework_2/include/impl.hpp delete mode 100644 homework_2/include/matrix.hpp delete mode 100644 homework_2/src/distbitonic.cpp create mode 100644 homework_2/src/distsort.cpp diff --git a/homework_2/include/config.h b/homework_2/include/config.h index 3d7c74d..68bb2e6 100644 --- a/homework_2/include/config.h +++ b/homework_2/include/config.h @@ -10,8 +10,7 @@ #ifndef CONFIG_H_ #define CONFIG_H_ -#include -#include +#include /* * Defines for different version of the exercise @@ -25,13 +24,17 @@ #define CODE_VERSION BITONIC #endif +// Value type selection +using distValue_t = uint8_t; /*! * Session option for each invocation of the executable */ struct session_t { - bool timing{false}; - bool verbose{false}; //!< Flag to enable verbose output to stdout + size_t arraySize{0}; + bool ndebug{false}; + bool timing{false}; + bool verbose{false}; //!< Flag to enable verbose output to stdout }; extern session_t session; diff --git a/homework_2/include/distbitonic.hpp b/homework_2/include/distbitonic.hpp deleted file mode 100644 index 98eba1a..0000000 --- a/homework_2/include/distbitonic.hpp +++ /dev/null @@ -1,73 +0,0 @@ -/*! - * \file - * \brief Distributed bitonic implementation header - * - * \author - * Christos Choutouridis AEM:8997 - * - */ - -#ifndef DISTBITONIC_H_ -#define DISTBITONIC_H_ - -#include -#include "utils.hpp" - - -/*! - * Enumerator for the different versions of the sorting method - */ -enum class SortMode { - Bubbletonic, //!< The v0.5 of the algorithm where we use a bubble-sort like approach - Bitonic //!< The v1.0 of the algorithm where we use the bitonic data-exchange approach -}; - -using Data_t = std::vector; -using AllData_t = std::vector; - -/* - * ============================== Sort utilities ============================== - */ - -/*! - * The primary function template of ascending(). It is DISABLED since , it is explicitly specialized - * for each of the \c SortMode - */ -template bool ascending(mpi_id_t, [[maybe_unused]] size_t) noexcept = delete; -template <> bool ascending(mpi_id_t node, [[maybe_unused]] size_t depth) noexcept; -template <> bool ascending(mpi_id_t node, size_t depth) noexcept; - -/*! - * The primary function template of partner(). It is DISABLED since , it is explicitly specialized - * for each of the \c SortMode - */ -template mpi_id_t partner(mpi_id_t, size_t) noexcept = delete; -template <> mpi_id_t partner(mpi_id_t node, size_t step) noexcept; -template <> mpi_id_t partner(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 bool keepSmall(mpi_id_t, mpi_id_t, [[maybe_unused]] size_t) noexcept = delete; -template<> bool keepSmall(mpi_id_t node, mpi_id_t partner, [[maybe_unused]] size_t depth) noexcept; -template<> bool keepSmall(mpi_id_t node, mpi_id_t partner, size_t depth) noexcept; - -bool isActive(mpi_id_t node, mpi_id_t nodes) noexcept; - -/* - * ============================== Data utilities ============================== - */ -void exchange(mpi_id_t node, mpi_id_t partner); -void minmax(AllData_t& data, mpi_id_t node, mpi_id_t partner, bool keepsmall); - -/* - * ============================== Sort algorithms ============================== - */ -void bubbletonic_network(AllData_t& data, mpi_id_t nodes); -void distBubbletonic(mpi_id_t P, AllData_t& data); - -void bitonic_network(AllData_t& data, mpi_id_t nodes, mpi_id_t depth); -void distBitonic(mpi_id_t P, AllData_t& data); - -#endif //DISTBITONIC_H_ diff --git a/homework_2/include/distsort.hpp b/homework_2/include/distsort.hpp new file mode 100644 index 0000000..abcc804 --- /dev/null +++ b/homework_2/include/distsort.hpp @@ -0,0 +1,288 @@ +/*! + * \file + * \brief Distributed sort implementation header + * + * \author + * Christos Choutouridis AEM:8997 + * + */ + +#ifndef DISTBITONIC_H_ +#define DISTBITONIC_H_ + +#include +#include +#include +#include +#if !defined DEBUG +#define NDEBUG +#endif +#include + +#include "utils.hpp" +#include "config.h" + + +/*! + * Enumerator for the different versions of the sorting method + */ +enum class SortMode { + Bubbletonic, //!< The v0.5 of the algorithm where we use a bubble-sort like approach + Bitonic //!< The v1.0 of the algorithm where we use the bitonic data-exchange approach +}; + +/* + * ============================== Sort utilities ============================== + */ + +/*! + * The primary function template of ascending(). It is DISABLED since , it is explicitly specialized + * for each of the \c SortMode + */ +template inline bool ascending(mpi_id_t, [[maybe_unused]] size_t) noexcept = delete; + +/*! + * Returns the ascending or descending configuration of the node's sequence based on + * the current node (MPI process) and the depth of the sorting network + * + * @param node The current node (MPI process) + * @return True if we need ascending configuration, false otherwise + */ +template <> inline +bool ascending(mpi_id_t node, [[maybe_unused]] size_t depth) noexcept { + return (node % 2) == 0; +} + +/*! + * Returns the ascending or descending configuration of the node's sequence based on + * the current node (MPI process) and the depth of the sorting network + * + * @param node The current node (MPI process) + * @param depth The total depth of the sorting network (same for each step for a given network) + * + * @return True if we need ascending configuration, false otherwise + */ +template <> inline +bool ascending(mpi_id_t node, size_t depth) noexcept { + return !(node & (1 << depth)); +} + +/*! + * The primary function template of partner(). It is DISABLED since , it is explicitly specialized + * for each of the \c SortMode + */ +template inline mpi_id_t partner(mpi_id_t, size_t) noexcept = delete; + +/*! + * Returns the node's partner for data exchange during the sorting network iterations + * of Bubbletonic + * + * @param node The current node + * @param step The step of the sorting network + * @return The node id of the partner for data exchange + */ +template <> inline +mpi_id_t partner(mpi_id_t node, size_t step) noexcept { + //return (node % 2 == step % 2) ? node + 1 : node - 1; + return (((node+step) % 2) == 0) ? node + 1 : node - 1; +} + +/*! + * Returns the node's partner for data exchange during the sorting network iterations + * of Bitonic + * + * @param node The current node + * @param step The step of the sorting network + * @return The node id of the partner for data exchange + */ +template <> inline +mpi_id_t partner(mpi_id_t node, size_t step) noexcept { + return (node ^ (1 << step)); +} + + +/*! + * The primary function template of keepSmall(). It is DISABLED since , it is explicitly specialized + * for each of the \c SortMode + */ +template inline bool keepSmall(mpi_id_t, mpi_id_t, [[maybe_unused]] size_t) noexcept = delete; + +/*! + * Predicate to check if a node keeps the small numbers during the bubbletonic sort network exchange. + * + * @param node The node for which we check + * @param partner The partner of the data exchange + * @return True if the node should keep the small values, false otherwise + */ +template <> inline +bool keepSmall(mpi_id_t node, mpi_id_t partner, [[maybe_unused]] size_t depth) noexcept { + assert(node != partner); + return (node < partner); +} + +/*! + * Predicate to check if a node keeps the small numbers during the bitonic sort network exchange. + * + * @param node The node for which we check + * @param partner The partner of the data exchange + * @param depth The total depth of the sorting network (same for each step for a given network) + * @return True if the node should keep the small values, false otherwise + */ +template <> inline +bool keepSmall(mpi_id_t node, mpi_id_t partner, size_t depth) noexcept { + assert(node != partner); + return ascending(node, depth) == (node < partner); +} + +/*! + * Predicate to check if the node is active in the current iteration of the bubbletonic + * sort exchange. + * + * @param node The node to check + * @param nodes The total number of nodes + * @return True if the node is active, false otherwise + */ +bool isActive(mpi_id_t node, size_t nodes) noexcept; + +/* + * ============================== Data utilities ============================== + */ + +/*! + * + * @tparam RangeT + * @param data + * @param ascending + */ +template +void fullSort(RangeT& data, bool ascending) { + // Use introsort from stdlib++ here, unless ... + if (ascending) + std::sort(data.begin(), data.end(), std::less<>()); + else + std::sort(data.begin(), data.end(), std::greater<>()); +} + +/*! + * + * @tparam ShadowedT + * @tparam CompT + * @param data + * @param comp + */ +template +void elbowSortCore(ShadowedT& data, CompT comp) { + size_t N = data.size(); + auto active = data.getActive(); + auto shadow = data.getShadow(); + size_t left = std::distance( + active.begin(), + std::min_element(active.begin(), active.end()) + ); + size_t right = (left == N-1) ? 0 : left + 1; + + for (size_t i = 0 ; i +void elbowSort(ShadowedT& data, bool ascending) { + if (ascending) + elbowSortCore(data, std::less<>()); + else + elbowSortCore(data, std::greater<>()); +} + +/*! + * + * @tparam RangeT + * @param local + * @param remote + * @param keepsmall + */ +template +void minmax(RangeT& local, RangeT& remote, bool keepsmall) { + using value_t = typename RangeT::value_type; + std::transform( + local.begin(), local.end(), + remote.begin(), + local.begin(), + [keepsmall](const value_t& a, const value_t& b){ + return (keepsmall) ? std::min(a, b) : std::max(a, b); + }); +} + +/* + * ============================== Sort algorithms ============================== + */ + +/*! + * + * @tparam ShadowedT + * @param data + * @param Processes + */ +template +void distBubbletonic(ShadowedT& data, mpi_id_t Processes) { + // Initially sort to create a half part of a bitonic sequence + fullSort(data, ascending(mpi.rank(), 0)); + + // Sort network + for (size_t step = 0; step < Processes-1; ++step) { + auto part = partner(mpi.rank(), step); + auto ks = keepSmall(mpi.rank(), part, Processes); + if (isActive(mpi.rank(), Processes)) { + mpi.exchange(part, data.getActive(), data.getShadow(), step); + minmax(data.getActive(), data.getShadow(), ks); + elbowSort(data, ascending(mpi.rank(), Processes)); + } + } + + if (!ascending(mpi.rank(), 0)) + elbowSort(data, true); + +} + + +/*! + * + * @tparam ShadowedT + * @param data + * @param Processes + */ +template +void distBitonic(ShadowedT& data, mpi_id_t Processes) { + auto p = static_cast(std::log2(Processes)); + + // Initially sort to create a half part of a bitonic sequence + fullSort(data, ascending(mpi.rank(), 0)); + + // Run through sort network using elbow-sort + for (size_t depth = 1; depth <= p; ++depth) { + for (size_t step = depth; step > 0;) { + --step; + auto part = partner(mpi.rank(), step); + auto ks = keepSmall(mpi.rank(), part, depth); + mpi.exchange(part, data.getActive(), data.getShadow(), (depth << 8) | step); + minmax(data.getActive(), data.getShadow(), ks); + } + elbowSort (data, ascending(mpi.rank(), depth)); + } +} + +#endif //DISTBITONIC_H_ diff --git a/homework_2/include/impl.hpp b/homework_2/include/impl.hpp deleted file mode 100644 index 7209cc6..0000000 --- a/homework_2/include/impl.hpp +++ /dev/null @@ -1,14 +0,0 @@ -/*! - * \file - * \brief The distributed bitonic implementation header - * - * \author - * Christos Choutouridis AEM:8997 - * - */ - -#ifndef IMPL_H_ -#define IMPL_H_ - - -#endif //IMPL_H_ diff --git a/homework_2/include/matrix.hpp b/homework_2/include/matrix.hpp deleted file mode 100644 index 33b3266..0000000 --- a/homework_2/include/matrix.hpp +++ /dev/null @@ -1,804 +0,0 @@ -/** - * \file matrix.hpp - * \brief A matrix abstraction implementation - * - * \author - * Christos Choutouridis AEM:8997 - * - */ -#ifndef MATRIX_HPP_ -#define MATRIX_HPP_ - -#include -#include -#include -#include -#include - -namespace mtx { - -using std::size_t; - -/* - * Small helper to strip types - */ -template -struct remove_cvref { - typedef std::remove_cv_t> type; -}; -template -using remove_cvref_t = typename remove_cvref::type; - -/*! - * Enumerator to denote the storage type of the array to use. - */ -enum class MatrixType { - DENSE, /*!< Matrix is dense */ - SPARSE, /*!< Matrix is sparse */ -}; - -/*! - * Enumerator to denote the storage type of the array to use. - */ -enum class MatrixOrder { - COLMAJOR, /*!< Matrix is column major */ - ROWMAJOR, /*!< Matrix is row major */ -}; - -/* - * Forward type declarations - */ - -template struct MatCol; -template struct MatRow; -template struct MatVal; - - /*! - * A 2-D matrix functionality over a 1-D array - * - * This is a very thin abstraction layer over a native array. - * This is tested using compiler explorer and our template produce - * almost identical assembly. - * - * The penalty hit we have is due to the fact that we use a one dimension array - * and we have to calculate the actual position from an (i,j) pair. - * The use of 1D array was our intention from the beginning, so the penalty - * was pretty much unavoidable. - * - * \tparam DataType The underling data type of the array - * \tparam IndexType The underling type for the index variables and sizes - * \tparam Type The storage type of the array - * \arg FULL For full matrix - * \arg SYMMETRIC For symmetric matrix (we use only the lower part) - */ -template -struct Matrix { - - using dataType = DataType; //!< meta:export of underling data type - using indexType = IndexType; //!< meta:export of underling index type - static constexpr MatrixOrder matrixOrder = Order; //!< meta:export of array order - static constexpr MatrixType matrixType = Type; //!< meta:export of array type - static constexpr bool symmetric = Symmetric; //!< meta:export symmetric flag - - /*! - * \name Obj lifetime - */ - //! @{ - - //! Construct an empty matrix with dimensions rows x columns - Matrix(IndexType rows = IndexType{}, IndexType columns = IndexType{}) noexcept - : vector_storage_(capacity(rows, columns)), - raw_storage_(nullptr), - use_vector_(true), - rows_(rows), - cols_(columns) { - data_ = vector_storage_.data(); - } - - //! Construct a matrix by copying existing data with dimensions rows x columns - Matrix(DataType* data, IndexType major_start, IndexType major_length, IndexType minor_length) noexcept - : vector_storage_(), - raw_storage_ (data + major_start * minor_length), - use_vector_ (false) { - if constexpr (Order == MatrixOrder::ROWMAJOR) { - rows_ = major_length; - cols_ = minor_length; - } - else { - rows_ = minor_length; - cols_ = major_length; - } - data_ = raw_storage_; - } - - //! Construct a matrix using an initializer list - Matrix(IndexType rows, IndexType columns, std::initializer_list list) - : vector_storage_(list), - raw_storage_(nullptr), - use_vector_(true), - rows_(rows), - cols_(columns) { - if (list.size() != capacity(rows, columns)) { - throw std::invalid_argument("Matrix initializer list size does not match matrix dimensions."); - } - data_ = vector_storage_.data(); - } - - //! move ctor - Matrix(Matrix&& m) noexcept { moves(std::move(m)); } - //! move - Matrix& operator=(Matrix&& m) noexcept { moves(std::move(m)); return *this; } - Matrix(const Matrix& m) = delete; //!< No copy ctor - Matrix& operator=(const Matrix& m) = delete; //!< No copy - //Matrix(const Matrix& m); - //Matrix& operator=(const Matrix& m) { copy(m); } - - //! @} - - //! \name Data exposure - //! @{ - - - //! Get/Set the size of each dimension - IndexType rows() const noexcept { return rows_; } - IndexType columns() const noexcept { return cols_; } - - //! Get the interface size of the Matrix (what appears to be the size) - IndexType size() const { - return rows_ * cols_; - } - //! Set the interface size of the Matrix (what appears to be the size) - IndexType resize(IndexType rows, IndexType columns) { - if (use_vector_) { - rows_ = rows; - cols_ = columns; - vector_storage_.resize(capacity(rows_, cols_)); - data_ = vector_storage_.data(); - } - return capacity(rows_, cols_); - } - - //! Actual memory capacity of the symmetric matrix - static constexpr IndexType capacity(IndexType M, IndexType N) { - if constexpr (Symmetric) - return (M+1)*N/2; - else - return M*N; - } - - /* - * virtual 2D accessors - */ - DataType get (IndexType i, IndexType j) { - if constexpr (Symmetric) { - auto T = [](size_t i)->size_t { return i*(i+1)/2; }; // Triangular number of i - if constexpr (Order == MatrixOrder::COLMAJOR) { - // In column major we use the lower triangle of the matrix - if (i>=j) return data_[j*rows_ - T(j) + i]; // Lower, use our notation - else return data_[i*rows_ - T(i) + j]; // Upper, use opposite index - } - else { - // In row major we use the upper triangle of the matrix - if (i<=j) return data_[i*cols_ - T(i) + j]; // Upper, use our notation - else return data_[j*cols_ - T(j) + i]; // Lower, use opposite index - } - } - else { - if constexpr (Order == MatrixOrder::COLMAJOR) - return data_[i + j*rows_]; - else - return data_[i*cols_ + j]; - } - } - - /*! - * \fn DataType set(DataType, IndexType, IndexType) - * \param v - * \param i - * \param j - * \return - */ - DataType set (DataType v, IndexType i, IndexType j) { - if constexpr (Symmetric) { - auto T = [](size_t i)->size_t { return i*(i+1)/2; }; // Triangular number of i - if constexpr (Order == MatrixOrder::COLMAJOR) { - // In column major we use the lower triangle of the matrix - if (i>=j) return data_[j*rows_ - T(j) + i] = v; // Lower, use our notation - else return data_[i*rows_ - T(i) + j] = v; // Upper, use opposite index - } - else { - // In row major we use the upper triangle of the matrix - if (i<=j) return data_[i*cols_ - T(i) + j] = v; // Upper, use our notation - else return data_[j*cols_ - T(j) + i] = v; // Lower, use opposite index - } - } - else { - if constexpr (Order == MatrixOrder::COLMAJOR) - return data_[i + j*rows_] = v; - else - return data_[i*cols_ + j] = v; - } - } -// DataType operator()(IndexType i, IndexType j) { return get(i, j); } - /*! - * Return a proxy MatVal object with read and write capabilities. - * @param i The row number - * @param j The column number - * @return tHE MatVal object - */ - MatVal operator()(IndexType i, IndexType j) noexcept { - return MatVal(this, get(i, j), i, j); - } - - // a basic serial iterator support - DataType* data() noexcept { return data_; } - DataType* begin() noexcept { return data_; } - const DataType* begin() const noexcept { return data_; } - DataType* end() noexcept { return data_ + capacity(rows_, cols_); } - const DataType* end() const noexcept { return data_ + capacity(rows_, cols_); } - -// IndexType begin_idx() noexcept { return 0; } -// IndexType end_idx() noexcept { return capacity(rows_, cols_); } - - const DataType* data() const noexcept { return data_; } - const IndexType begin_idx() const noexcept { return 0; } - const IndexType end_idx() const noexcept { return capacity(rows_, cols_); } - //! @} - - /*! - * \name Safe iteration API - * - * This api automates the iteration over the array based on - * MatrixType - */ - //! @{ - template - void for_each_in (IndexType begin, IndexType end, F&& lambda, Args&&... args) { - for (IndexType it=begin ; it(lambda)(std::forward(args)..., it); - } - } - //! @} - - // - void swap(Matrix& src) noexcept { - std::swap(vector_storage_, src.vector_storage_); - std::swap(raw_storage_, src.raw_storage_); - std::swap(data_, src.data_); - std::swap(use_vector_, src.use_vector_); - std::swap(rows_, src.rows_); - std::swap(cols_, src.cols_); - } - -private: - //! move helper - void moves(Matrix&& src) noexcept { - vector_storage_ = std::move(src.vector_storage_); - raw_storage_ = std::move(src.raw_storage_); - data_ = std::move(src.data_); - use_vector_ = std::move(src.use_vector_); - rows_ = std::move(src.rows_); - cols_ = std::move(src.cols_); - } - - // Storage - std::vector - vector_storage_; //!< Internal storage (if used). - DataType* raw_storage_; //!< External storage (if used). - DataType* data_; //!< Pointer to active storage. - bool use_vector_; //!< True if using vector storage, false for raw pointer. - IndexType rows_{}; //!< the virtual size of rows. - IndexType cols_{}; //!< the virtual size of columns. -}; - - -/** - * A simple sparse matrix specialization. - * - * We use CSC format and provide get/set functionalities for each (i,j) item - * on the matrix. We also provide a () overload using a proxy MatVal object. - * This way the user can: - * \code - * auto v = A(3,4); - * A(3, 4) = 7; - * \endcode - * - * We also provide getCol() and getRow() functions witch return a viewer/iterator to rows and - * columns of the matrix. In the case of a symmetric matrix instead of a row we return the - * equivalent column. This way we gain speed due to CSC format nature. - * - * @tparam DataType The type for values - * @tparam IndexType The type for indexes - * @tparam Type The Matrix type (FULL or SYMMETRIC) - */ -template -struct Matrix { - - using dataType = DataType; //!< meta:export of underling data type - using indexType = IndexType; //!< meta:export of underling index type - static constexpr MatrixOrder matrixOrder = Order; //!< meta:export of array order - static constexpr MatrixType matrixType = MatrixType::SPARSE; //!< meta:export of array type - static constexpr bool symmetric = Symmetric; //!< meta:export symmetric flag - - friend struct MatCol; - friend struct MatRow; - friend struct MatVal; - - /*! - * \name Obj lifetime - */ - //! @{ - - //! Default ctor with optional memory allocations - Matrix(IndexType n=IndexType{}) noexcept: - values{}, - rows{}, - col_ptr((n)? n+1:2, IndexType{}), - N(n), - NNZ(0) { } - - //! A ctor using csc array data - Matrix(IndexType n, IndexType nnz, const IndexType* row, const IndexType* col) noexcept: - values(nnz, 1), - rows(row, row+nnz), - col_ptr(col, col+n+1), - N(n), - NNZ(nnz) { } - - //! ctor using csc array data with value array - Matrix(IndexType n, IndexType nnz, const DataType* v, const IndexType* row, const IndexType* col) noexcept: - values(v, v+nnz), - rows(row, row+nnz), - col_ptr(col, col+n+1), - N(n), - NNZ(nnz) { } - - //! ctor vectors of row/col and default value for values array - Matrix(IndexType n, IndexType nnz, const DataType v, - const std::vector& row, const std::vector& col) noexcept: - values(nnz, v), - rows (row), - col_ptr(col), - N(n), - NNZ(nnz) { } - - //! move ctor - Matrix(Matrix&& m) noexcept { moves(std::move(m)); } - //! move - Matrix& operator=(Matrix&& m) noexcept { moves(std::move(m)); return *this; } - Matrix(const Matrix& m) = delete; //!< make sure there are no copies - Matrix& operator=(const Matrix& m) = delete; //!< make sure there are no copies - //! @} - - //! \name Data exposure - //! @{ - - //! \return the dimension of the matrix - IndexType size() noexcept { return N; } - //! After construction size configuration tool - IndexType resize(IndexType n) { - col_ptr.resize(n+1); - return N = n; - } - //! \return the NNZ of the matrix - IndexType capacity() noexcept { return NNZ; } - //! After construction NNZ size configuration tool - IndexType capacity(IndexType nnz) noexcept { - values.reserve(nnz); - rows.reserve(nnz); - return NNZ; - } - // getters for row arrays of the struct (unused) - std::vector& getValues() noexcept { return values; } - std::vector& getRows() noexcept { return rows; } - std::vector& getCols() noexcept { return col_ptr; } - - /*! - * Return a proxy MatVal object with read and write capabilities. - * @param i The row number - * @param j The column number - * @return tHE MatVal object - */ - MatVal operator()(IndexType i, IndexType j) noexcept { - return MatVal(this, get(i, j), i, j); - } - - /*! - * A read item functionality using binary search to find the correct row - * - * @param i The row number - * @param j The column number - * @return The value of the item or DataType{} if is not present. - */ - DataType get(IndexType i, IndexType j) noexcept { - IndexType idx; bool found; - std::tie(idx, found) =find_idx(rows, col_ptr[j], col_ptr[j+1], i); - return (found) ? values[idx] : 0; - } - - /*! - * A write item functionality. - * - * First we search if the matrix has already a value in (i, j) position. - * If so we just change it to a new value. If not we add the item on the matrix. - * - * @note - * When change a value, we don't increase the NNZ value of the struct. We expect the user has already - * change the NNZ value to the right one using @see capacity() function. When adding a value we - * increase the NNZ. - * - * @param i The row number - * @param j The column number - * @return The new value of the item . - */ - DataType set(DataType v, IndexType i, IndexType j) { - IndexType idx; bool found; - std::tie(idx, found) = find_idx(rows, col_ptr[j], col_ptr[j+1], i); - if (found) - return values[idx] = v; // we don't change NNZ even if we write "0" - else { - values.insert(values.begin()+idx, v); - rows.insert(rows.begin()+idx, i); - std::transform(col_ptr.begin()+j+1, col_ptr.end(), col_ptr.begin()+j+1, [](IndexType it) { - return ++it; - }); - ++NNZ; // we increase the NNZ even if we write "0" - return v; - } - } - - /*! - * Get a view of a CSC column - * @param j The column to get - * @return The MatCol object @see MatCol - */ - MatCol getCol(IndexType j) noexcept { - return MatCol(this, col_ptr[j], col_ptr[j+1]); - } - - /*! - * Get a view of a CSC row - * - * In case of a SYMMETRIC matrix we can return a column instead. - * - * @param j The row to get - * @return On symmetric matrix MatCol otherwise a MatRow - */ - - MatCol getRow(IndexType i) noexcept { - if constexpr (Symmetric) - return getCol(i); - else - return MatRow(this, i); - } - - // values only iterator support - DataType* begin() noexcept { return values.begin(); } - DataType* end() noexcept { return values.end(); } - //! @} - - //! A small iteration helper - template - void for_each_in (IndexType begin, IndexType end, F&& lambda, Args&&... args) { - for (IndexType it=begin ; it(lambda)(std::forward(args)..., it); - } - } - -private: - /*! - * A small binary search implementation using index for begin-end instead of iterators. - * - * \param v Reference to vector to search - * \param begin The vector's index to begin - * \param end The vector's index to end - * \param match What to search - * \return An pair. - * index is the index of the item or end if not found - * status is true if found, false otherwise - */ - std::pair find_idx(const std::vector& v, IndexType begin, IndexType end, IndexType match) { - if (v.capacity() != 0 && begin < end) { - IndexType b = begin, e = end-1; - while (b <= e) { - IndexType m = (b+e)/2; - if (v[m] == match) return std::make_pair(m, true); - else if (b >= e) return std::make_pair(end, false); - else { - if (v[m] < match) b = m +1; - else e = m -1; - } - } - } - return std::make_pair(end, false); - } - - // move helper - void moves(Matrix&& src) noexcept { - values = std::move(src.values); - rows = std::move(src.rows); - col_ptr = std::move(src.col_ptr); - N = std::move(src.N); // redundant for primitives - NNZ = std::move(src.NNZ); // - } - //! \name Data - //! @{ - std::vector values {}; //!< vector to store the values of the matrix - std::vector rows{}; //!< vector to store the row information - std::vector col_ptr{1,0}; //!< vector to store the column pointers - IndexType N{0}; //!< The dimension of the matrix (square) - IndexType NNZ{0}; //!< The NNZ (capacity of the matrix) - //! @} -}; - - -/*! - * A view/iterator hybrid object for Matrix columns. - * - * This object provides access to a column of a Matrix. The public functionalities - * allow data access using indexes instead of iterators. We prefer indexes over iterators - * because we can apply the same index to different inner vector of Matrix without conversion. - * - * @tparam DataType - * @tparam IndexType - */ -template -struct MatCol { - using owner_t = MatrixType; - - using DataType = typename MatrixType::dataType; - using IndexType = typename MatrixType::indexType; - - /*! - * ctor using column pointers for begin-end. own is pointer to Matrix. - */ - MatCol(owner_t* own, const IndexType begin, const IndexType end) noexcept : - owner_(own), index_(begin), begin_(begin), end_(end) { - vindex_ = vIndexCalc(index_); - } - MatCol() = default; - MatCol(const MatCol&) = delete; //!< make sure there are no copies - MatCol& operator=(const MatCol&)= delete; //!< make sure there are no copies - MatCol(MatCol&&) = default; - MatCol& operator=(MatCol&&) = default; - - //! a simple dereference operator, like an iterator - DataType operator* () { - return get(); - } - //! Increment operator acts on index(), like an iterator - MatCol& operator++ () { advance(); return *this; } - MatCol& operator++ (int) { MatCol& p = *this; advance(); return p; } - - //! () operator acts as member access (like a view) - DataType operator()(IndexType x) { - return (x == index())? get() : DataType{}; - } - //! = operator acts as member assignment (like a view) - DataType operator= (DataType v) { return owner_->values[index_] = v; } - // iterator like handlers - // these return a virtual index value based on the items position on the full matrix - // but the move of the index is just a ++ away. - IndexType index() noexcept { return vindex_; } - const IndexType index() const noexcept { return vindex_; } - IndexType begin() noexcept { return vIndexCalc(begin_); } - const IndexType begin() const noexcept { return vIndexCalc(begin_); } - IndexType end() noexcept { return owner_->N; } - const IndexType end() const noexcept { return owner_->N; } - - /*! - * Multiplication operator - * - * We follow only the non-zero values and multiply only the common indexes. - * - * @tparam C Universal reference for the type right half site column - * - * @param c The right hand site matrix - * @return The value of the inner product of two vectors - * @note The time complexity is \$ O(nnz1+nnz2) \$. - * Where the nnz is the max NNZ elements of the column of the matrix - */ - template - DataType operator* (C&& c) { - static_assert(std::is_same, MatCol>(), ""); - DataType v{}; - while (index() != end() && c.index() != c.end()) { - if (index() < c.index()) advance(); // advance me - else if (index() > c.index()) ++c; // advance other - else { //index() == c.index() - v += get() * *c; // multiply and advance both - ++c; - advance(); - } - } - return v; - } - -private: - //! small tool to increase the index pointers to Matrix - void advance() noexcept { - ++index_; - vindex_ = vIndexCalc(index_); - } - //! tool to translate between col_ptr indexes and Matrix "virtual" full matrix indexes - IndexType vIndexCalc(IndexType idx) { - return (idx < end_) ? owner_->rows[idx] : end(); - } - //! small get tool - DataType get() { return owner_->values[index_]; } - - owner_t* owner_ {nullptr}; //!< Pointer to owner Matrix. MatCol is just a view - IndexType vindex_ {IndexType{}}; //!< Virtual index of full matrix - IndexType index_ {IndexType{}}; //!< index to Matrix::rows - IndexType begin_ {IndexType{}}; //!< beginning index of the column in Matrix::rows - IndexType end_ {IndexType{}}; //!< ending index of the column in Matrix::rows -}; - -/*! - * A view/iterator hybrid object for Matrix rows. - * - * This object provides access to a column of a Matrix. The public functionalities - * allow data access using indexes instead of iterators. We prefer indexes over iterators - * because we can apply the same index to different inner vector of Matrix without conversion. - * - * @tparam DataType - * @tparam IndexType - */ -template -struct MatRow { - using owner_t = MatrixType; - - using DataType = typename MatrixType::dataType; - using IndexType = typename MatrixType::indexType; - - /*! - * ctor using virtual full matrix row index. own is pointer to Matrix. - */ - MatRow(owner_t* own, const IndexType row) noexcept : - owner_(own), vindex_(IndexType{}), row_(row), index_(IndexType{}), - begin_(IndexType{}), end_(owner_->NNZ) { - // place begin - while(begin_ != end_ && owner_->rows[begin_] != row_) - ++begin_; - // place index_ and vindex_ - if (owner_->rows[index_] != row_) - advance(); - } - MatRow() = default; - MatRow(const MatRow&) = delete; //!< make sure there are no copies - MatRow& operator=(const MatRow&)= delete; //!< make sure there are no copies - MatRow(MatRow&&) = default; - MatRow& operator=(MatRow&&) = default; - - //! a simple dereference operator, like an iterator - DataType operator* () { - return get(); - } - //! Increment operator acts on index(), like an iterator - //! here the increment is a O(N) process. - MatRow& operator++ () { advance(); return *this; } - MatRow& operator++ (int) { MatRow& p = *this; advance(); return p; } - - //! () operator acts as member access (like a view) - DataType operator()(IndexType x) { - return (x == index())? get() : DataType{}; - } - //! = operator acts as member assignment (like a view) - DataType operator= (DataType v) { return owner_->values[index_] = v; } - // iterator like handlers - // these return a virtual index value based on the items position on the full matrix - // but the move of the index is just a ++ away. - IndexType index() noexcept { return vindex_; } - const IndexType index() const noexcept { return vindex_; } - IndexType begin() noexcept { return vIndexCalc(begin_); } - const IndexType begin() const noexcept { return vIndexCalc(begin_); } - IndexType end() noexcept { return owner_->N; } - const IndexType end() const noexcept { return owner_->N; } - - /*! - * Multiplication operator - * - * We follow only the non-zero values and multiply only the common indexes. - * - * @tparam C Universal reference for the type right half site column - * - * @param c The right hand site matrix - * @return The value of the inner product of two vectors - * @note The time complexity is \$ O(N+nnz2) \$ and way heavier the ColxCol multiplication. - * Where the nnz is the max NNZ elements of the column of the matrix - */ - template - DataType operator* (C&& c) { - static_assert(std::is_same, MatCol>(), ""); - DataType v{}; - while (index() != end() && c.index() != c.end()) { - if (index() < c.index()) advance(); // advance me - else if (index() > c.index()) ++c; // advance other - else { //index() == c.index() - v += get() * *c; // multiply and advance both - ++c; - advance(); - } - } - return v; - } -private: - //! small tool to increase the index pointers to Matrix matrix - //! We have to search the entire rows vector in Matrix to find the next - //! virtual row position. - //! time complexity O(N) - void advance() noexcept { - do - ++index_; - while(index_ != end_ && owner_->rows[index_] != row_); - vindex_ = vIndexCalc(index_); - } - //! tool to translate between col_ptr indexes and Matrix "virtual" full matrix indexes - IndexType vIndexCalc(IndexType idx) { - for(IndexType i =0 ; i<(owner_->N+1) ; ++i) - if (idx < owner_->col_ptr[i]) - return i-1; - return end(); - } - //! small get tool - DataType get() { return owner_->values[index_]; } - - owner_t* owner_ {nullptr}; //!< Pointer to owner Matrix. MatCol is just a view - IndexType vindex_ {IndexType{}}; //!< Virtual index of full matrix - IndexType row_ {IndexType{}}; //!< The virtual full matrix row of the object - IndexType index_ {IndexType{}}; //!< index to Matrix::rows - IndexType begin_ {IndexType{}}; //!< beginning index of the column in Matrix::rows - IndexType end_ {IndexType{}}; //!< ending index of the column in Matrix::rows -}; - -/*! - * A proxy Matrix value object/view. - * - * This object acts as proxy to provide read/write access to an Matrix item. - * - * @tparam DataType The type of the values of the Matrix matrix - * @tparam IndexType The type of the indexes of the Matrix matrix - */ -template -struct MatVal { - using owner_t = MatrixType; - - using DataType = typename MatrixType::dataType; - using IndexType = typename MatrixType::indexType; - - //!< ctor using all value-row-column data, plus a pointer to owner Matrix object - MatVal(owner_t* own, DataType v, IndexType i, IndexType j) : - owner_(own), v_(v), i_(i), j_(j) { } - MatVal() = default; - MatVal(const MatVal&) = delete; //!< make sure there are no copies - MatVal& operator=(const MatVal&) = delete; //!< make sure there are no copies - MatVal(MatVal&&) = default; - MatVal& operator=(MatVal&&) = default; - - //! Operator to return the DataType value implicitly - operator DataType() { return v_; } - //! Operator to write back to owner the assigned value - //! for ex: A(2,3) = 5; - MatVal& operator=(DataType v) { - v_ = v; - owner_->set(v_, i_, j_); - return *this; - } -private: - owner_t* owner_{nullptr}; //!< Pointer to owner Matrix. MatVal is just a view. - DataType v_{DataType{}}; //!< The value of the row-column pair (for speed) - IndexType i_{IndexType{}}; //!< The row - IndexType j_{IndexType{}}; //!< the column -}; - - -} // namespace mtx - - -#endif /* MATRIX_HPP_ */ diff --git a/homework_2/include/utils.hpp b/homework_2/include/utils.hpp index 5accd7b..437d404 100644 --- a/homework_2/include/utils.hpp +++ b/homework_2/include/utils.hpp @@ -1,5 +1,5 @@ /** - * \file utils.hpp + * \file * \brief Utilities header * * \author @@ -9,6 +9,7 @@ #ifndef UTILS_HPP_ #define UTILS_HPP_ +#include #include #include #include @@ -17,6 +18,18 @@ //#include "matrix.hpp" #include "config.h" +template struct MPI_TypeMapper; + +// Specializations for supported types +template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_CHAR; } }; +template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_UNSIGNED_CHAR; } }; +template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_SHORT; } }; +template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_INT; } }; +template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_LONG; } }; +template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_LONG_LONG; } }; +template <> struct MPI_TypeMapper{ static MPI_Datatype getType() { return MPI_UNSIGNED_SHORT; } }; +template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG; } }; +template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG_LONG; } }; template struct MPI_t { @@ -28,8 +41,10 @@ struct MPI_t { // Get the number of processes int size_value, rank_value; - size_ = static_cast(MPI_Comm_size(MPI_COMM_WORLD, &size_value)); - rank_ = static_cast(MPI_Comm_rank(MPI_COMM_WORLD, &rank_value)); + MPI_Comm_size(MPI_COMM_WORLD, &size_value); + MPI_Comm_rank(MPI_COMM_WORLD, &rank_value); + size_ = static_cast(size_value); + rank_ = static_cast(rank_value); // Get the name of the processor char processor_name[MPI_MAX_PROCESSOR_NAME]; @@ -43,18 +58,24 @@ struct MPI_t { MPI_Finalize(); } - bool exchange(ID_t partner, const void *send_data, void *recv_data, int data_count, MPI_Datatype datatype) { - bool ret = true; + template + void exchange(ID_t partner, const std::vector& send_data, std::vector& recv_data, int tag) { + using namespace std::string_literals; + MPI_Status status; - MPI_Sendrecv( - send_data, data_count, datatype, partner, 0, - recv_data, data_count, datatype, partner, 0, + MPI_Datatype datatype = MPI_TypeMapper::getType(); + int send_count = static_cast(send_data.size()); + int err = MPI_Sendrecv( + send_data.data(), send_count, datatype, partner, tag, + recv_data.data(), send_count, datatype, partner, tag, MPI_COMM_WORLD, &status ); - if (status.MPI_ERROR != MPI_SUCCESS) - ret = false; - - return ret; + 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'); + } } // Accessors @@ -71,12 +92,84 @@ private: extern MPI_t<> mpi; using mpi_id_t = MPI_t<>::ID_t; +template +struct ShadowedVec_t { + // STL requirements + using value_type = Value_t; + using iterator = typename std::vector::iterator; + using const_iterator = typename std::vector::const_iterator; + using size_type = typename std::vector::size_type; + + // Dispatch to active vector + Value_t& operator[](size_type index) { return getActive()[index]; } + const Value_t& operator[](size_type index) const { return getActive()[index]; } + + Value_t& at(size_type index) { 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(Value_t&& value) { getActive().push_back(std::move(value)); } + void pop_back() { getActive().pop_back(); } + Value_t& front() { 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(); } + + iterator begin() { return getActive().begin(); } + const_iterator begin() const { return getActive().begin(); } + iterator end() { return getActive().end(); } + const_iterator end() const { return getActive().end(); } + + size_type size() const { return getActive().size(); } + void resize(size_t new_size) { + North.resize(new_size); + South.resize(new_size); + } + + void reserve(size_t new_capacity) { + North.reserve(new_capacity); + South.reserve(new_capacity); + } + [[nodiscard]] size_t capacity() const { return getActive().capacity(); } + [[nodiscard]] bool empty() const { return getActive().empty(); } + + void clear() { getActive().clear(); } + + void swap(std::vector& other) { getActive().swap(other); } + + // Switching vectors + void switch_active() { active = (active == north) ? south : north; } + + // Accessors + const std::vector& getNorth() const { return North; } + const std::vector& getSouth() const { return South; } + std::vector& getActive() { + return (active == north) ? North : South; + } + const std::vector& getActive() const { + return (active == north) ? North : South; + } + std::vector& getShadow() { + return (active == north) ? South : North; + } + const std::vector& getShadow() const { + return (active == north) ? South : North; + } +private: + enum { north, south } active{north}; + std::vector North{}; + std::vector South{}; +}; + +using distBuffer_t = ShadowedVec_t; + +extern distBuffer_t Data; + /*! * A Logger for entire program. */ struct Log { - struct Endl { - } endl; //!< a tag object to to use it as a new line request. + struct Endl {} endl; //!< a tag object to to use it as a new line request. //! We provide logging via << operator template diff --git a/homework_2/src/distbitonic.cpp b/homework_2/src/distbitonic.cpp deleted file mode 100644 index c1af9b9..0000000 --- a/homework_2/src/distbitonic.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/*! - * \file - * \brief Distributed bitonic implementation. - * - * \author - * Christos Choutouridis AEM:8997 - * - */ - -#include -#include -#include -#if !defined DEBUG -#define NDEBUG -#endif -#include - -#include "distbitonic.hpp" - - - -/*! - * Returns the ascending or descending configuration of the node's sequence based on - * the current node (MPI process) and the depth of the sorting network - * - * @param node The current node (MPI process) - * @return True if we need ascending configuration, false otherwise - */ -template <> -bool ascending(mpi_id_t node, [[maybe_unused]] size_t depth) noexcept { - return (node % 2) == 0; -} - -/*! - * Returns the ascending or descending configuration of the node's sequence based on - * the current node (MPI process) and the depth of the sorting network - * - * @param node The current node (MPI process) - * @param depth The total depth of the sorting network (same for each step for a given network) - * - * @return True if we need ascending configuration, false otherwise - */ -template <> -bool ascending(mpi_id_t node, size_t depth) noexcept { - return !(node & (1 << depth)); -} - -/*! - * Returns the node's partner for data exchange during the sorting network iterations - * of Bubbletonic - * - * @param node The current node - * @param step The step of the sorting network - * @return The node id of the partner for data exchange - */ -template <> -mpi_id_t partner(mpi_id_t node, size_t step) noexcept { -// return (node % 2 == step % 2) ? node + 1 : node - 1; - return (((node+step) % 2) == 0) ? node + 1 : node - 1; -} - -/*! - * Returns the node's partner for data exchange during the sorting network iterations - * of Bitonic - * - * @param node The current node - * @param step The step of the sorting network - * @return The node id of the partner for data exchange - */ -template <> -mpi_id_t partner(mpi_id_t node, size_t step) noexcept { - return (node ^ (1 << step)); -} - - -/*! - * Predicate to check if a node keeps the small numbers during the bubbletonic sort network exchange. - * - * @param node The node for which we check - * @param partner The partner of the data exchange - * @return True if the node should keep the small values, false otherwise - */ -template <> -bool keepSmall(mpi_id_t node, mpi_id_t partner, [[maybe_unused]] size_t depth) noexcept { - assert(node != partner); - return (node < partner); -} - -/*! - * Predicate to check if a node keeps the small numbers during the bitonic sort network exchange. - * - * @param node The node for which we check - * @param partner The partner of the data exchange - * @param depth The total depth of the sorting network (same for each step for a given network) - * @return True if the node should keep the small values, false otherwise - */ -template <> -bool keepSmall(mpi_id_t node, mpi_id_t partner, size_t depth) noexcept { - assert(node != partner); - return ascending(node, depth) == (node < partner); -} - -/*! - * Predicate to check if the node is active in the current iteration of the bubbletonic - * sort exchange. - * - * @param node The node to check - * @param nodes The total number of nodes - * @return True if the node is active, false otherwise - */ -bool isActive(mpi_id_t node, mpi_id_t nodes) noexcept { - return (node >= 0) && (node < (nodes-1)); -} - - -void exchange(mpi_id_t node, mpi_id_t partner) { - assert(node != partner); -} - -void minmax(AllData_t& data, mpi_id_t node, mpi_id_t partner, bool keepsmall) { - for (size_t i = 0; i < data[node].size(); ++i) { - if (keepsmall && data[node][i] > data[partner][i]) - std::swap(data[node][i], data[partner][i]); - if (!keepsmall && data[node][i] < data[partner][i]) - std::swap(data[node][i], data[partner][i]); - } -} - - - - -void bubbletonic_network(AllData_t& data, mpi_id_t nodes, size_t depth) { - for (mpi_id_t node = 0 ; node < nodes ; ++node) { // Currently we do all nodes here! - auto part = partner(node, depth); - auto ks = keepSmall(node, part, 0); - if (isActive(node, nodes) && node < part) { - exchange(node, part); - minmax(data, node, part, ks); - // elbow-sort here - if (ascending(node, 0)) - std::sort(data[node].begin(), data[node].end(), std::less<>()); - else - std::sort(data[node].begin(), data[node].end(), std::greater<>()); - - if (ascending(part, 0)) - std::sort(data[part].begin(), data[part].end(), std::less<>()); - else - std::sort(data[part].begin(), data[part].end(), std::greater<>()); - } - } -} - -void distBubbletonic(mpi_id_t P, AllData_t& data) { - for (mpi_id_t node = 0 ; node < P ; ++node) { // Currently we do all nodes here! - // Initially sort to create the half part of a bitonic - if (ascending(node, 0)) - std::sort(data[node].begin(), data[node].end(), std::less<>()); - else - std::sort(data[node].begin(), data[node].end(), std::greater<>()); - } - - for (size_t depth = 0; depth < P-1; ++depth) { - bubbletonic_network(data, P, depth); - } - - // Invert the descending ones - for (mpi_id_t node = 0 ; node < P ; ++node) { // Currently we do all nodes here! - if (!ascending(node, 0)) - std::sort(data[node].begin(), data[node].end(), std::less<>()); - } -} - - -void bitonic_network(AllData_t& data, mpi_id_t nodes, size_t depth) { - for (size_t step = depth; step > 0;) { - --step; - for (mpi_id_t node = 0; node < nodes; ++node) { // Currently we do all nodes here! - auto part = partner(node, step); - auto ks = keepSmall(node, part, depth); - if (node < part) { - exchange(node, part); - minmax(data, node, part, ks); - } - } - } -} - -void distBitonic(mpi_id_t P, AllData_t& data) { - auto p = static_cast(std::log2(P)); - - for (mpi_id_t node = 0 ; node < P ; ++node) { // Currently we do all nodes here! - // Initially sort to create the half part of a bitonic - if (ascending(node, 0)) - std::sort(data[node].begin(), data[node].end(), std::less<>()); - else - std::sort(data[node].begin(), data[node].end(), std::greater<>()); - } - - // Run through sort network using elbow-sort - for (size_t depth = 1; depth <= p; ++depth) { - bitonic_network(data, P, depth); - - for (mpi_id_t node = 0 ; node < P ; ++node) { // Currently we do all nodes here! - // elbow-sort here - if (ascending(node, depth)) - std::sort(data[node].begin(), data[node].end(), std::less<>()); - else - std::sort(data[node].begin(), data[node].end(), std::greater<>()); - } - - } -} diff --git a/homework_2/src/distsort.cpp b/homework_2/src/distsort.cpp new file mode 100644 index 0000000..b3589b0 --- /dev/null +++ b/homework_2/src/distsort.cpp @@ -0,0 +1,31 @@ +/*! + * \file + * \brief Distributed sort implementation. + * + * \author + * Christos Choutouridis AEM:8997 + * + */ + +#if !defined DEBUG +#define NDEBUG +#endif +#include + +#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)); +} + diff --git a/homework_2/src/main.cpp b/homework_2/src/main.cpp index 4e98a49..2ac7089 100644 --- a/homework_2/src/main.cpp +++ b/homework_2/src/main.cpp @@ -9,15 +9,19 @@ #include #include +#include #include "utils.hpp" #include "config.h" +#include "distsort.hpp" // Global session data -session_t session; -MPI_t<> mpi; - +session_t session; +MPI_t<> mpi; +distBuffer_t Data; +Log logger; +Timing timer; /*! * A small command line argument parser @@ -30,29 +34,46 @@ bool get_options(int argc, char* argv[]){ for (int i=1 ; i [-v]\n"; + std::cout << "distbitonic/distbubbletonic - A distributed bitonic sort\n\n"; + std::cout << "distbitonic -q <> [--ndebug] [-v]\n"; + std::cout << "distbitonic -h\n"; + std::cout << "distbubbletonic -q <> [--ndebug] [-v]\n"; + std::cout << "distbubbletonic -h\n"; std::cout << '\n'; std::cout << "Options:\n\n"; + std::cout << " -q | --array-size \n"; + std::cout << " Selects the array size according to size = 2^q\n\n"; + std::cout << " --ndebug\n"; + std::cout << " Skip debug breakpoint when on debug build.\n\n"; + std::cout << " -t | --timing\n"; + std::cout << " Request timing measurements output to stdout.\n\n"; std::cout << " -v | --verbose\n"; std::cout << " Request a more verbose output to stdout.\n\n"; std::cout << " -h | --help\n"; std::cout << " Prints this and exit.\n\n"; std::cout << "Examples:\n\n"; - std::cout << " ...Example case...:\n"; - std::cout << " > distBitonic -x \n\n"; + std::cout << " mpirun -np 4 distbitonic -q 24\n"; + std::cout << " Runs distbitonic in 4 MPI processes with 2^24 array points each\n\n"; + std::cout << " mpirun -np 16 distbubbletonic -q 20\n"; + std::cout << " Runs distbubbletonic in 16 MPI processes with 2^20 array points each\n\n"; exit(0); } @@ -66,7 +87,6 @@ bool get_options(int argc, char* argv[]){ } - #if !defined TESTING int main(int argc, char* argv[]) try { // Initialize MPI environment @@ -76,34 +96,50 @@ int main(int argc, char* argv[]) try { if (!get_options(argc, argv)) exit(1); - #if defined DEBUG - /* - * In case of a debug build we will wait here until sleep_wait - * will reset via debugger. In order to do that the user must attach - * debugger to all processes. For example: - * $> mpirun -np 2 ./ - * $> ps aux | grep - * $> gdb - * $> gdb - */ - #if defined TESTING - volatile bool sleep_wait = false; - #else + logger << "MPI environment initialized." << + " Rank: " << mpi.rank() << + " Size: " << mpi.size() << + logger.endl; + +#if defined DEBUG +#if defined TESTING + /* + * In case of a debug build we will wait here until sleep_wait + * will reset via debugger. In order to do that the user must attach + * debugger to all processes. For example: + * $> mpirun -np 2 ./ + * $> ps aux | grep + * $> gdb + * $> gdb + */ + volatile bool sleep_wait = false; +#else volatile bool sleep_wait = true; - #endif - while (sleep_wait) +#endif + while (sleep_wait && !session.ndebug) sleep(1); +#endif + + logger << "Initialize local array of " << session.arraySize << " elements" << logger.endl; + std::srand(unsigned(std::time(nullptr))); + Data.resize(session.arraySize); + std::generate(Data.begin(), Data.end(), std::rand); + + if (mpi.rank() == 0) + logger << "Starting distributed sorting ... "; + timer.start(); + #if CODE_VERSION == BUBBLETONIC + distBubbletonic(Data, mpi.size()); + #else + distBitonic (Data, mpi.size()); #endif + timer.stop(); + if (mpi.rank() == 0) + logger << " Done." << logger.endl; + std::string timeMsg = "rank " + std::to_string(mpi.rank()); + timer.print_dt(timeMsg.c_str()); - // Print off a hello world message -// std::cout << "Hello world from processor: " << mpi.processor_name -// << " rank " << mpi.world_rank -// << " out of " << mpi.world_size << " processors\n"; - - -// distBitonic (2, Data); -// distBitonic (4, Data); - + std::cout << "[Data]: Rank " << mpi.rank() << ": [" << (int)Data.front() << " .. " << (int)Data.back() << "]" << std::endl; mpi.finalize(); return 0; } diff --git a/homework_2/test/tests_Bitonic.cpp b/homework_2/test/tests_Bitonic.cpp index 0b9b42b..75641c5 100644 --- a/homework_2/test/tests_Bitonic.cpp +++ b/homework_2/test/tests_Bitonic.cpp @@ -11,7 +11,7 @@ #include // rand/srand #include // rand/srand -#include "distbitonic.hpp" +#include "distsort.hpp" @@ -245,8 +245,8 @@ TEST(TdistBitonic_UT, keepsmall_test1) { */ TEST(TdistBitonic_UT, keepsmall_test2) { size_t ts_depth = 1; - mpi_id_t ts_partner[] = {1, 0, 3, 2, 5, 4, 7, 6}; - bool ts_expected[] = {1, 0, 0, 1, 1, 0, 0, 1}; + mpi_id_t ts_partner[] = {1, 0, 3, 2, 5, 4, 7, 6}; + bool ts_expected[] = {1, 0, 0, 1, 1, 0, 0, 1}; for (mpi_id_t node = 0 ; node < 8 ; ++node ) { EXPECT_EQ(ts_expected[node], keepSmall(node, ts_partner[node], ts_depth)); @@ -260,8 +260,8 @@ TEST(TdistBitonic_UT, keepsmall_test2) { */ TEST(TdistBitonic_UT, keepsmall_test3) { size_t ts_depth = 2; - mpi_id_t ts_partner[] = {2, 3, 0, 1, 6, 7, 4, 5}; - bool ts_expected[] = {1, 1, 0, 0, 0, 0, 1, 1}; + mpi_id_t ts_partner[] = {2, 3, 0, 1, 6, 7, 4, 5}; + bool ts_expected[] = {1, 1, 0, 0, 0, 0, 1, 1}; for (mpi_id_t node = 0 ; node < 8 ; ++node ) { EXPECT_EQ(ts_expected[node], keepSmall(node, ts_partner[node], ts_depth)); @@ -275,8 +275,8 @@ TEST(TdistBitonic_UT, keepsmall_test3) { */ TEST(TdistBitonic_UT, keepsmall_test4) { size_t ts_depth = 2; - mpi_id_t ts_partner[] = {1, 0, 3, 2, 5, 4, 7, 6}; - bool ts_expected[] = {1, 0, 1, 0, 0, 1, 0, 1}; + mpi_id_t ts_partner[] = {1, 0, 3, 2, 5, 4, 7, 6}; + bool ts_expected[] = {1, 0, 1, 0, 0, 1, 0, 1}; for (mpi_id_t node = 0 ; node < 8 ; ++node ) { EXPECT_EQ(ts_expected[node], keepSmall(node, ts_partner[node], ts_depth)); @@ -290,8 +290,8 @@ TEST(TdistBitonic_UT, keepsmall_test4) { */ TEST(TdistBitonic_UT, keepsmall_test5) { size_t ts_depth = 3; - mpi_id_t ts_partner[] = {4, 5, 6, 7, 0, 1, 2, 3}; - bool ts_expected[] = {1, 1, 1, 1, 0, 0, 0, 0}; + mpi_id_t ts_partner[] = {4, 5, 6, 7, 0, 1, 2, 3}; + bool ts_expected[] = {1, 1, 1, 1, 0, 0, 0, 0}; for (mpi_id_t node = 0 ; node < 8 ; ++node ) { EXPECT_EQ(ts_expected[node], keepSmall(node, ts_partner[node], ts_depth)); @@ -305,8 +305,8 @@ TEST(TdistBitonic_UT, keepsmall_test5) { */ TEST(TdistBitonic_UT, keepsmall_test6) { size_t ts_depth = 3; - mpi_id_t ts_partner[] = {2, 3, 0, 1, 6, 7, 4, 5}; - bool ts_expected[] = {1, 1, 0, 0, 1, 1, 0, 0}; + mpi_id_t ts_partner[] = {2, 3, 0, 1, 6, 7, 4, 5}; + bool ts_expected[] = {1, 1, 0, 0, 1, 1, 0, 0}; for (mpi_id_t node = 0 ; node < 8 ; ++node ) { EXPECT_EQ(ts_expected[node], keepSmall(node, ts_partner[node], ts_depth)); @@ -320,27 +320,28 @@ TEST(TdistBitonic_UT, keepsmall_test6) { */ TEST(TdistBitonic_UT, keepsmall_test7) { size_t ts_depth = 3; - mpi_id_t ts_partner[] = {1, 0, 3, 2, 5, 4, 7, 6}; - bool ts_expected[] = {1, 0, 1, 0, 1, 0, 1, 0}; + mpi_id_t ts_partner[] = {1, 0, 3, 2, 5, 4, 7, 6}; + bool ts_expected[] = {1, 0, 1, 0, 1, 0, 1, 0}; for (mpi_id_t node = 0 ; node < 8 ; ++node ) { EXPECT_EQ(ts_expected[node], keepSmall(node, ts_partner[node], ts_depth)); } } +#if 0 TEST(TdistBitonic_UT, distBitonic_test1) { AllData_t ts_Data { - Data_t (8), Data_t (8) + ShadowedVec_t (8), ShadowedVec_t (8) }; - - std::srand(unsigned(std::time(nullptr))); - for (auto& v : ts_Data) { +(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::min(); + auto max = std::numeric_limits::min(); for (auto& v : ts_Data) { EXPECT_EQ((max <= v[0]), true); EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); @@ -350,7 +351,7 @@ TEST(TdistBitonic_UT, distBitonic_test1) { TEST(TdistBitonic_UT, distBitonic_test2) { AllData_t ts_Data { - Data_t (8), Data_t (8), Data_t (8), Data_t (8) + ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8) }; std::srand(unsigned(std::time(nullptr))); @@ -360,7 +361,7 @@ TEST(TdistBitonic_UT, distBitonic_test2) { distBitonic(4, ts_Data); - auto max = std::numeric_limits::min(); + auto max = std::numeric_limits::min(); for (auto& v : ts_Data) { EXPECT_EQ((max <= v[0]), true); EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); @@ -370,8 +371,8 @@ TEST(TdistBitonic_UT, distBitonic_test2) { TEST(TdistBitonic_UT, distBitonic_test3) { AllData_t ts_Data { - Data_t (32), Data_t (32), Data_t (32), Data_t (32), - Data_t (32), Data_t (32), Data_t (32), Data_t (32) + 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))); @@ -381,10 +382,12 @@ TEST(TdistBitonic_UT, distBitonic_test3) { distBitonic(8, ts_Data); - auto max = std::numeric_limits::min(); + auto max = std::numeric_limits::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(); } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/homework_2/test/tests_Bubbletonic.cpp b/homework_2/test/tests_Bubbletonic.cpp index 2489c4a..931c7f3 100644 --- a/homework_2/test/tests_Bubbletonic.cpp +++ b/homework_2/test/tests_Bubbletonic.cpp @@ -11,7 +11,7 @@ #include // rand/srand #include // rand/srand -#include "distbitonic.hpp" +#include "distsort.hpp" @@ -120,10 +120,10 @@ TEST(TdistBubbletonic_UT, keepsmall_test2) { } - +#if 0 TEST(TdistBubbletonic_UT, distBubbletonic_test1) { AllData_t ts_Data { - Data_t (8), Data_t (8) + ShadowedVec_t (8), ShadowedVec_t (8) }; std::srand(unsigned(std::time(nullptr))); @@ -133,7 +133,7 @@ TEST(TdistBubbletonic_UT, distBubbletonic_test1) { distBubbletonic(2, ts_Data); - auto max = std::numeric_limits::min(); + auto max = std::numeric_limits::min(); for (auto& v : ts_Data) { EXPECT_EQ((max <= v[0]), true); EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); @@ -144,7 +144,7 @@ TEST(TdistBubbletonic_UT, distBubbletonic_test1) { TEST(TdistBubbletonic_UT, distBubbletonic_test2) { AllData_t ts_Data { - Data_t (8), Data_t (8), Data_t (8), Data_t (8) + ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8) }; std::srand(unsigned(std::time(nullptr))); @@ -154,7 +154,7 @@ TEST(TdistBubbletonic_UT, distBubbletonic_test2) { distBubbletonic(4, ts_Data); - auto max = std::numeric_limits::min(); + auto max = std::numeric_limits::min(); for (auto& v : ts_Data) { EXPECT_EQ((max <= v[0]), true); EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); @@ -164,8 +164,8 @@ TEST(TdistBubbletonic_UT, distBubbletonic_test2) { TEST(TdistBubbletonic_UT, distBubbletonic_test3) { AllData_t ts_Data { - Data_t (32), Data_t (32), Data_t (32), Data_t (32), - Data_t (32), Data_t (32), Data_t (32), Data_t (32) + 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))); @@ -175,10 +175,11 @@ TEST(TdistBubbletonic_UT, distBubbletonic_test3) { distBubbletonic(8, ts_Data); - auto max = std::numeric_limits::min(); + auto max = std::numeric_limits::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 \ No newline at end of file