AUTH's THMMY "Parallel and distributed systems" course assignments.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

utils.hpp 13 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. /**
  2. * \file
  3. * \brief Utilities header
  4. *
  5. * \author
  6. * Christos Choutouridis AEM:8997
  7. * <cchoutou@ece.auth.gr>
  8. */
  9. #ifndef UTILS_HPP_
  10. #define UTILS_HPP_
  11. #include <vector>
  12. #include <iostream>
  13. #include <chrono>
  14. #include <unistd.h>
  15. #include <mpi.h>
  16. #include "config.h"
  17. /*
  18. * MPI_<type> dispatcher mechanism
  19. */
  20. template <typename T> struct MPI_TypeMapper { };
  21. template <> struct MPI_TypeMapper<char> { static MPI_Datatype getType() { return MPI_CHAR; } };
  22. template <> struct MPI_TypeMapper<short> { static MPI_Datatype getType() { return MPI_SHORT; } };
  23. template <> struct MPI_TypeMapper<int> { static MPI_Datatype getType() { return MPI_INT; } };
  24. template <> struct MPI_TypeMapper<long> { static MPI_Datatype getType() { return MPI_LONG; } };
  25. template <> struct MPI_TypeMapper<long long> { static MPI_Datatype getType() { return MPI_LONG_LONG; } };
  26. template <> struct MPI_TypeMapper<unsigned char> { static MPI_Datatype getType() { return MPI_UNSIGNED_CHAR; } };
  27. template <> struct MPI_TypeMapper<unsigned short>{ static MPI_Datatype getType() { return MPI_UNSIGNED_SHORT; } };
  28. template <> struct MPI_TypeMapper<unsigned int> { static MPI_Datatype getType() { return MPI_UNSIGNED; } };
  29. template <> struct MPI_TypeMapper<unsigned long> { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG; } };
  30. template <> struct MPI_TypeMapper<unsigned long long> { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG_LONG; } };
  31. template <> struct MPI_TypeMapper<float> { static MPI_Datatype getType() { return MPI_FLOAT; } };
  32. template <> struct MPI_TypeMapper<double> { static MPI_Datatype getType() { return MPI_DOUBLE; } };
  33. /*!
  34. * MPI wrapper type to provide MPI functionality and RAII to MPI as a resource
  35. *
  36. * @tparam TID The MPI type for process id [default: int]
  37. */
  38. template<typename TID = int>
  39. struct MPI_t {
  40. using ID_t = TID; // Export TID type (currently int defined by the standard)
  41. /*!
  42. * Initializes the MPI environment, must called from each process
  43. *
  44. * @param argc [int*] POINTER to main's argc argument
  45. * @param argv [char***] POINTER to main's argv argument
  46. */
  47. void init(int* argc, char*** argv) {
  48. // Initialize the MPI environment
  49. int err;
  50. if ((err = MPI_Init(argc, argv)) != MPI_SUCCESS)
  51. mpi_throw(err, "(MPI) MPI_Init() - ");
  52. initialized_ = true;
  53. // Get the number of processes
  54. int size_value, rank_value;
  55. if ((err = MPI_Comm_size(MPI_COMM_WORLD, &size_value)) != MPI_SUCCESS)
  56. mpi_throw(err, "(MPI) MPI_Comm_size() - ");
  57. if ((err = MPI_Comm_rank(MPI_COMM_WORLD, &rank_value)) != MPI_SUCCESS)
  58. mpi_throw(err, "(MPI) MPI_Comm_rank() - ");
  59. size_ = static_cast<ID_t>(size_value);
  60. rank_ = static_cast<ID_t>(rank_value);
  61. // Get the name of the processor
  62. char processor_name[MPI_MAX_PROCESSOR_NAME];
  63. int name_len;
  64. if ((err = MPI_Get_processor_name(processor_name, &name_len)) != MPI_SUCCESS)
  65. mpi_throw(err, "(MPI) MPI_Get_processor_name() - ");
  66. name_ = std::string (processor_name, name_len);
  67. }
  68. /*!
  69. * Exchange data with partner as part of the sorting network of both bubbletonic or bitonic
  70. * sorting algorithms.
  71. *
  72. * This function matches a transmit and a receive in order for fully exchanged data between
  73. * current node and partner.
  74. *
  75. * @tparam T The inner valur type used in buffer
  76. *
  77. * @param send_data [std::vector<T>] Reference to local data to send
  78. * @param recv_data [std::vector<T>] Reference to buffer to receive data from partner
  79. * @param partner [mpi_id_t] The partner for the exchange
  80. * @param tag [int] The tag to use for the MPI communication
  81. */
  82. template<typename T>
  83. void exchange(const std::vector<T>& send_data, std::vector<T>& recv_data, ID_t partner, int tag) {
  84. using namespace std::string_literals;
  85. MPI_Datatype datatype = MPI_TypeMapper<T>::getType();
  86. int send_count = static_cast<int>(send_data.size());
  87. MPI_Status status;
  88. int err;
  89. if ((err = MPI_Sendrecv(
  90. send_data.data(), send_count, datatype, partner, tag,
  91. recv_data.data(), send_count, datatype, partner, tag,
  92. MPI_COMM_WORLD, &status
  93. )) != MPI_SUCCESS)
  94. mpi_throw(err, "(MPI) MPI_Sendrecv() - ");
  95. }
  96. // Accessors
  97. [[nodiscard]] ID_t rank() const noexcept { return rank_; }
  98. [[nodiscard]] ID_t size() const noexcept { return size_; }
  99. [[nodiscard]] const std::string& name() const noexcept { return name_; }
  100. // Mutators
  101. ID_t rank(ID_t rank) noexcept { return rank_ = rank; }
  102. ID_t size(ID_t size) noexcept { return size_ = size; }
  103. std::string& name(const std::string& name) noexcept { return name_ = name; }
  104. /*!
  105. * Finalized the MPI
  106. */
  107. void finalize() {
  108. // Finalize the MPI environment
  109. initialized_ = false;
  110. MPI_Finalize();
  111. }
  112. //! RAII MPI finalization
  113. ~MPI_t() {
  114. // Finalize the MPI environment even on unexpected errors
  115. if (initialized_)
  116. MPI_Finalize();
  117. }
  118. // Local functionality
  119. private:
  120. /*!
  121. * Throw exception helper. It bundles the prefix msg with the MPI error string retrieved by
  122. * MPI API.
  123. *
  124. * @param err The MPI error code
  125. * @param prefixMsg The prefix text for the exception error message
  126. */
  127. void mpi_throw(int err, const char* prefixMsg) {
  128. char err_msg[MPI_MAX_ERROR_STRING];
  129. int msg_len;
  130. MPI_Error_string(err, err_msg, &msg_len);
  131. throw std::runtime_error(prefixMsg + std::string (err_msg) + '\n');
  132. }
  133. private:
  134. ID_t rank_{}; //!< MPI rank of the process
  135. ID_t size_{}; //!< MPI total size of the execution
  136. std::string name_{}; //!< The name of the local machine
  137. bool initialized_{}; //!< RAII helper flag
  138. };
  139. /*
  140. * Exported data types
  141. */
  142. extern MPI_t<> mpi;
  143. using mpi_id_t = MPI_t<>::ID_t;
  144. /*!
  145. * A std::vector wrapper with 2 vectors, an active and a shadow. This type exposes the standard vector
  146. * functionality of the active vector. The shadow can be used when we need to use the vector as mutable
  147. * data in algorithms that can not support "in-place" editing (like elbow-sort for example)
  148. *
  149. * @tparam Value_t the inner data type of the vectors
  150. */
  151. template <typename Value_t>
  152. struct ShadowedVec_t {
  153. // STL requirements
  154. using value_type = Value_t;
  155. using iterator = typename std::vector<Value_t>::iterator;
  156. using const_iterator = typename std::vector<Value_t>::const_iterator;
  157. using size_type = typename std::vector<Value_t>::size_type;
  158. // Default constructor
  159. ShadowedVec_t() = default;
  160. // Constructor from an std::vector
  161. explicit ShadowedVec_t(const std::vector<Value_t>& vec)
  162. : North(vec), South(), active(north) {
  163. South.resize(North.size());
  164. }
  165. explicit ShadowedVec_t(std::vector<Value_t>&& vec)
  166. : North(std::move(vec)), South(), active(north) {
  167. South.resize(North.size());
  168. }
  169. // Copy assignment operator
  170. ShadowedVec_t& operator=(const ShadowedVec_t& other) {
  171. if (this != &other) { // Avoid self-assignment
  172. North = other.North;
  173. South = other.South;
  174. active = other.active;
  175. }
  176. return *this;
  177. }
  178. // Move assignment operator
  179. ShadowedVec_t& operator=(ShadowedVec_t&& other) noexcept {
  180. if (this != &other) { // Avoid self-assignment
  181. North = std::move(other.North);
  182. South = std::move(other.South);
  183. active = other.active;
  184. // There is no need to zero out other since it is valid but in a non-defined state
  185. }
  186. return *this;
  187. }
  188. // Type accessors
  189. std::vector<Value_t>& getActive() { return (active == north) ? North : South; }
  190. std::vector<Value_t>& getShadow() { return (active == north) ? South : North; }
  191. const std::vector<Value_t>& getActive() const { return (active == north) ? North : South; }
  192. const std::vector<Value_t>& getShadow() const { return (active == north) ? South : North; }
  193. // Swap vectors
  194. void switch_active() { active = (active == north) ? south : north; }
  195. // Dispatch vector functionality to active vector
  196. Value_t& operator[](size_type index) { return getActive()[index]; }
  197. const Value_t& operator[](size_type index) const { return getActive()[index]; }
  198. Value_t& at(size_type index) { return getActive().at(index); }
  199. const Value_t& at(size_type index) const { return getActive().at(index); }
  200. void push_back(const Value_t& value) { getActive().push_back(value); }
  201. void push_back(Value_t&& value) { getActive().push_back(std::move(value)); }
  202. void pop_back() { getActive().pop_back(); }
  203. Value_t& front() { return getActive().front(); }
  204. Value_t& back() { return getActive().back(); }
  205. const Value_t& front() const { return getActive().front(); }
  206. const Value_t& back() const { return getActive().back(); }
  207. iterator begin() { return getActive().begin(); }
  208. const_iterator begin() const { return getActive().begin(); }
  209. iterator end() { return getActive().end(); }
  210. const_iterator end() const { return getActive().end(); }
  211. size_type size() const { return getActive().size(); }
  212. void resize(size_t new_size) {
  213. North.resize(new_size);
  214. South.resize(new_size);
  215. }
  216. void reserve(size_t new_capacity) {
  217. North.reserve(new_capacity);
  218. South.reserve(new_capacity);
  219. }
  220. [[nodiscard]] size_t capacity() const { return getActive().capacity(); }
  221. [[nodiscard]] bool empty() const { return getActive().empty(); }
  222. void clear() { getActive().clear(); }
  223. void swap(std::vector<Value_t>& other) { getActive().swap(other); }
  224. // Comparisons
  225. bool operator== (const ShadowedVec_t& other) { return getActive() == other.getActive(); }
  226. bool operator!= (const ShadowedVec_t& other) { return getActive() != other.getActive(); }
  227. bool operator== (const std::vector<value_type>& other) { return getActive() == other; }
  228. bool operator!= (const std::vector<value_type>& other) { return getActive() != other; }
  229. private:
  230. std::vector<Value_t> North{}; //!< Actual buffer to be used either as active or shadow
  231. std::vector<Value_t> South{}; //!< Actual buffer to be used either as active or shadow
  232. enum {
  233. north, south
  234. } active{north}; //!< Flag to select between North and South buffer
  235. };
  236. using distBuffer_t = ShadowedVec_t<distValue_t>;
  237. extern distBuffer_t Data;
  238. /*!
  239. * A Logger for entire program.
  240. */
  241. struct Log {
  242. struct Endl {} endl; //!< a tag object to to use it as a new line request.
  243. //! We provide logging via << operator
  244. template<typename T>
  245. Log &operator<<(T &&t) {
  246. if (session.verbose) {
  247. if (line_) {
  248. std::cout << "[Log]: " << t;
  249. line_ = false;
  250. } else
  251. std::cout << t;
  252. }
  253. return *this;
  254. }
  255. // overload for special end line handling
  256. Log &operator<<(Endl e) {
  257. (void) e;
  258. if (session.verbose) {
  259. std::cout << '\n';
  260. line_ = true;
  261. }
  262. return *this;
  263. }
  264. private:
  265. bool line_{true};
  266. };
  267. extern Log logger;
  268. /*!
  269. * A small timing utility based on chrono.
  270. */
  271. struct Timing {
  272. using Tpoint = std::chrono::steady_clock::time_point;
  273. using microseconds = std::chrono::microseconds;
  274. using milliseconds = std::chrono::milliseconds;
  275. using seconds = std::chrono::seconds;
  276. //! tool to mark the starting point
  277. Tpoint start() noexcept { return start_ = std::chrono::steady_clock::now(); }
  278. //! tool to mark the ending point
  279. Tpoint stop() noexcept { return stop_ = std::chrono::steady_clock::now(); }
  280. auto dt() noexcept {
  281. return std::chrono::duration_cast<std::chrono::microseconds>(stop_ - start_).count();
  282. }
  283. //! tool to print the time interval
  284. void print_dt(const char *what) noexcept {
  285. if (session.timing) {
  286. auto t = stop_ - start_;
  287. if (std::chrono::duration_cast<microseconds>(t).count() < 10000)
  288. std::cout << "[Timing]: " << what << ": "
  289. << std::to_string(std::chrono::duration_cast<microseconds>(t).count()) << " [usec]\n";
  290. else if (std::chrono::duration_cast<milliseconds>(t).count() < 10000)
  291. std::cout << "[Timing]: " << what << ": "
  292. << std::to_string(std::chrono::duration_cast<milliseconds>(t).count()) << " [msec]\n";
  293. else
  294. std::cout << "[Timing]: " << what << ": "
  295. << std::to_string(std::chrono::duration_cast<seconds>(t).count()) << " [sec]\n";
  296. }
  297. }
  298. private:
  299. Tpoint start_;
  300. Tpoint stop_;
  301. };
  302. #endif /* UTILS_HPP_ */