/** * \file * \brief Utilities header * * \author * Christos Choutouridis AEM:8997 * */ #ifndef UTILS_HPP_ #define UTILS_HPP_ #include #include #include #include #include //#include #include "config.h" /* * MPI_ dispatcher mechanism */ template struct MPI_TypeMapper { }; template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_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_CHAR; } }; template <> struct MPI_TypeMapper{ static MPI_Datatype getType() { return MPI_UNSIGNED_SHORT; } }; template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_UNSIGNED; } }; 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_TypeMapper { static MPI_Datatype getType() { return MPI_FLOAT; } }; template <> struct MPI_TypeMapper { 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 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 [char***] POINTER to main's argv argument */ void init(int* argc, char*** argv) { // Initialize the MPI environment 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; 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(size_value); rank_ = static_cast(rank_value); // Get the name of the processor char processor_name[MPI_MAX_PROCESSOR_NAME]; int 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 send_data [std::vector] Reference to local data to send * @param recv_data [std::vector] Reference to buffer to receive data from partner * @param partner [mpi_id_t] The partner for the exchange * @param tag [int] The tag to use for the MPI communication */ template void exchange(const std::vector& send_data, std::vector& recv_data, ID_t partner, int tag) { using namespace std::string_literals; MPI_Datatype datatype = MPI_TypeMapper::getType(); int send_count = static_cast(send_data.size()); 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 )) != MPI_SUCCESS) mpi_throw(err, "(MPI) MPI_Sendrecv() - "); } // Accessors [[nodiscard]] ID_t rank() const noexcept { return rank_; } [[nodiscard]] ID_t size() const noexcept { return size_; } [[nodiscard]] const std::string& name() const noexcept { return name_; } // Mutators ID_t rank(ID_t rank) noexcept { return rank_ = rank; } ID_t size(ID_t size) noexcept { return size_ = size; } std::string& name(const std::string& name) noexcept { return name_ = 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 msg 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'); } private: 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 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; // Default constructor ShadowedVec_t() = default; // Constructor from an std::vector explicit ShadowedVec_t(const std::vector& vec) : North(vec), South(), active(north) { South.resize(North.size()); } explicit ShadowedVec_t(std::vector&& vec) : North(std::move(vec)), South(), active(north) { South.resize(North.size()); } // Copy assignment operator ShadowedVec_t& operator=(const ShadowedVec_t& other) { if (this != &other) { // Avoid self-assignment North = other.North; South = other.South; active = other.active; } return *this; } // Move assignment operator ShadowedVec_t& operator=(ShadowedVec_t&& other) noexcept { if (this != &other) { // Avoid self-assignment North = std::move(other.North); South = std::move(other.South); active = other.active; // There is no need to zero out other since it is valid but in a non-defined state } return *this; } // Type accessors std::vector& getActive() { return (active == north) ? North : South; } std::vector& getShadow() { return (active == north) ? South : North; } const std::vector& getActive() const { return (active == north) ? North : South; } const std::vector& getShadow() const { return (active == north) ? South : North; } // Swap vectors void switch_active() { active = (active == north) ? south : north; } // Dispatch vector functionality 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(); } Value_t& back() { return getActive().back(); } const Value_t& front() const { return getActive().front(); } 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); } // 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& other) { return getActive() == other; } bool operator!= (const std::vector& other) { return getActive() != other; } private: std::vector North{}; //!< Actual buffer to be used either as active or shadow std::vector 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; 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. //! We provide logging via << operator template Log &operator<<(T &&t) { if (config.verbose) { if (line_) { std::cout << "[Log]: " << t; line_ = false; } else std::cout << t; } return *this; } // overload for special end line handling Log &operator<<(Endl e) { (void) e; if (config.verbose) { std::cout << '\n'; line_ = true; } return *this; } private: bool line_{true}; }; extern Log logger; /*! * A small timing utility based on chrono. */ struct Timing { using Tpoint = std::chrono::steady_clock::time_point; using Tduration = std::chrono::microseconds; using microseconds = std::chrono::microseconds; using milliseconds = std::chrono::milliseconds; using seconds = std::chrono::seconds; //! tool to mark the starting point Tpoint start() noexcept { return mark_ = std::chrono::steady_clock::now(); } //! tool to mark the ending point Tpoint stop() noexcept { Tpoint now = std::chrono::steady_clock::now(); duration_ += dt(now, mark_); return now; } Tduration dt(Tpoint t2, Tpoint t1) noexcept { return std::chrono::duration_cast(t2 - t1); } //! tool to print the time interval void print_duration(const char *what, mpi_id_t rank) noexcept { if (std::chrono::duration_cast(duration_).count() < 10000) std::cout << "[Timing] (Rank " << rank << ") " << what << ": " << std::to_string(std::chrono::duration_cast(duration_).count()) << " [usec]\n"; else if (std::chrono::duration_cast(duration_).count() < 10000) std::cout << "[Timing] (Rank " << rank << ") " << what << ": " << std::to_string(std::chrono::duration_cast(duration_).count()) << " [msec]\n"; else std::cout << "[Timing] (Rank " << rank << ") " << what << ": " << std::to_string(std::chrono::duration_cast(duration_).count()) << " [sec]\n"; } private: Tpoint mark_{}; Tduration duration_{}; }; /*! * Utility high level function like to forward a function call and measure * the excecution time */ #define timeCall(Tim, Func, ...) \ Tim.start(); \ Func(__VA_ARGS__); \ Tim.stop(); \ #endif /* UTILS_HPP_ */