389 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			389 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /**
 | |
|  * \file
 | |
|  * \brief   Utilities header
 | |
|  *
 | |
|  * \author
 | |
|  *    Christos Choutouridis AEM:8997
 | |
|  *    <cchoutou@ece.auth.gr>
 | |
|  */
 | |
| #ifndef UTILS_HPP_
 | |
| #define UTILS_HPP_
 | |
| 
 | |
| #include <vector>
 | |
| #include <iostream>
 | |
| #include <chrono>
 | |
| #include <unistd.h>
 | |
| #include <mpi.h>
 | |
| //#include <functional>
 | |
| 
 | |
| #include "config.h"
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * 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  [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<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;
 | |
|         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<T>]    Reference to local data to send
 | |
|      * @param recv_data [std::vector<T>]    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<typename T>
 | |
|     void exchange(const std::vector<T>& send_data, std::vector<T>& recv_data, ID_t partner, int tag) {
 | |
|         using namespace std::string_literals;
 | |
| 
 | |
|         MPI_Datatype datatype = MPI_TypeMapper<T>::getType();
 | |
|         int send_count = static_cast<int>(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 <typename Value_t>
 | |
| struct ShadowedVec_t {
 | |
|     // STL requirements
 | |
|     using value_type     = Value_t;
 | |
|     using iterator       = typename std::vector<Value_t>::iterator;
 | |
|     using const_iterator = typename std::vector<Value_t>::const_iterator;
 | |
|     using size_type      = typename std::vector<Value_t>::size_type;
 | |
| 
 | |
|     // Default constructor
 | |
|     ShadowedVec_t() = default;
 | |
| 
 | |
|     // Constructor from an std::vector
 | |
|     explicit ShadowedVec_t(const std::vector<Value_t>& vec)
 | |
|             : North(vec), South(), active(north) {
 | |
|         South.resize(North.size());
 | |
|     }
 | |
| 
 | |
|     explicit ShadowedVec_t(std::vector<Value_t>&& vec)
 | |
|             : North(std::move(vec)), South(), active(north) {
 | |
|         South.resize(North.size());
 | |
|     }
 | |
| 
 | |
|     // Copy assignment operator
 | |
|     ShadowedVec_t& operator=(const ShadowedVec_t& other) {
 | |
|         if (this != &other) { // Avoid self-assignment
 | |
|             North = other.North;
 | |
|             South = other.South;
 | |
|             active = other.active;
 | |
|         }
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
|     // Move assignment operator
 | |
|     ShadowedVec_t& operator=(ShadowedVec_t&& other) noexcept {
 | |
|         if (this != &other) { // Avoid self-assignment
 | |
|             North = std::move(other.North);
 | |
|             South = std::move(other.South);
 | |
|             active = other.active;
 | |
| 
 | |
|             // There is no need to zero out other since it is valid but in a non-defined state
 | |
|         }
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
|     // Type accessors
 | |
|     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; }
 | |
| 
 | |
|     // 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<Value_t>& 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<value_type>& other) { return getActive() == other; }
 | |
|     bool operator!= (const std::vector<value_type>& other) { return getActive() != other; }
 | |
| 
 | |
| private:
 | |
|     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>;
 | |
| 
 | |
| 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<typename T>
 | |
|     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<Tduration>(t2 - t1);
 | |
|     }
 | |
| 
 | |
|     //! tool to print the time interval
 | |
|     void print_duration(const char *what, mpi_id_t rank) noexcept {
 | |
|         if (std::chrono::duration_cast<microseconds>(duration_).count() < 10000)
 | |
|             std::cout << "[Timing] (Rank " << rank << ") " << what << ": "
 | |
|                       << std::to_string(std::chrono::duration_cast<microseconds>(duration_).count()) << " [usec]\n";
 | |
|         else if (std::chrono::duration_cast<milliseconds>(duration_).count() < 10000)
 | |
|             std::cout << "[Timing] (Rank " << rank << ") " << what << ": "
 | |
|                       << std::to_string(std::chrono::duration_cast<milliseconds>(duration_).count()) << " [msec]\n";
 | |
|         else
 | |
|             std::cout << "[Timing] (Rank " << rank << ") " << what << ": "
 | |
|                       << std::to_string(std::chrono::duration_cast<seconds>(duration_).count()) << " [sec]\n";
 | |
| 
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     Tpoint mark_{};
 | |
|     Tduration duration_{};
 | |
| };
 | |
| 
 | |
| /*!
 | |
|  * Utility high level function to forward a function call to std::invoke and measure
 | |
|  * the excecution time
 | |
|  *
 | |
|  * @tparam Func     The function type
 | |
|  * @tparam Args     The argument
 | |
|  * @param func
 | |
|  * @param args
 | |
|  * @return
 | |
|  */
 | |
| 
 | |
| 
 | |
| #define timeCall(Tim, Func, ...)    \
 | |
| Tim.start();                        \
 | |
| Func(__VA_ARGS__);                  \
 | |
| Tim.stop();                         \
 | |
| 
 | |
| 
 | |
| //template <typename Ret, typename Func, typename... Args>
 | |
| //auto timeCall_r(Ret& ret, Func&& func, Args&&... args) {
 | |
| //    Timing timer;
 | |
| //
 | |
| //    timer.start();
 | |
| //    ret = std::invoke(std::forward<Func>(func), std::forward<Args>(args)...);
 | |
| //    timer.stop();
 | |
| //
 | |
| //    return timer.dt();
 | |
| //}
 | |
| 
 | |
| #endif /* UTILS_HPP_ */
 |