AUTH's THMMY "Parallel and distributed systems" course assignments.
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

422 lines
15 KiB

  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. if (size_ > static_cast<ID_t>(MAX_MPI_SIZE))
  62. throw std::runtime_error(
  63. "(MPI) size - Not supported number of nodes [over " + std::to_string(MAX_MPI_SIZE) + "]\n"
  64. );
  65. // Get the name of the processor
  66. char processor_name[MPI_MAX_PROCESSOR_NAME];
  67. int name_len;
  68. if ((err = MPI_Get_processor_name(processor_name, &name_len)) != MPI_SUCCESS)
  69. mpi_throw(err, "(MPI) MPI_Get_processor_name() - ");
  70. name_ = std::string (processor_name, name_len);
  71. }
  72. /*!
  73. * Initiate a data exchange data with partner using non-blocking Isend-Irecv, as part of the
  74. * sorting network of both bubbletonic or bitonic sorting algorithms.
  75. *
  76. * This function matches a transmit and a receive in order for fully exchanged data between
  77. * current node and partner.
  78. * @note
  79. * This call MUST paired with exchange_wait() for each MPI_t object.
  80. * Calling 2 consecutive exchange_start() for the same MPI_t object is undefined.
  81. *
  82. * @tparam ValueT The underlying value type used in buffers
  83. *
  84. * @param ldata [const ValueT*] Pointer to local data to send
  85. * @param rdata [ValueT*] Pointer to buffer to receive data from partner
  86. * @param count [size_t] The number of data to exchange
  87. * @param partner [mpi_id_t] The partner for the exchange
  88. * @param tag [int] The tag to use for the MPI communication
  89. */
  90. template<typename ValueT>
  91. void exchange_start(const ValueT* ldata, ValueT* rdata, size_t count, ID_t partner, int tag) {
  92. if (tag < 0)
  93. throw std::runtime_error("(MPI) exchange_data() [tag] - Out of bound");
  94. MPI_Datatype datatype = MPI_TypeMapper<ValueT>::getType();
  95. int err;
  96. err = MPI_Isend(ldata, count, datatype, partner, tag, MPI_COMM_WORLD, &handle_tx);
  97. if (err != MPI_SUCCESS)
  98. mpi_throw(err, "(MPI) MPI_Isend() - ");
  99. err = MPI_Irecv(rdata, count, datatype, partner, tag, MPI_COMM_WORLD, &handle_rx);
  100. if (err != MPI_SUCCESS)
  101. mpi_throw(err, "(MPI) MPI_Irecv() - ");
  102. }
  103. /*!
  104. * Block wait for the completion of the previously called exchange_start()
  105. *
  106. * @note
  107. * This call MUST paired with exchange_start() for each MPI_t object.
  108. * Calling 2 consecutive exchange_wait() for the same MPI_t object is undefined.
  109. */
  110. void exchange_wait() {
  111. MPI_Status status;
  112. int err;
  113. if ((err = MPI_Wait(&handle_tx, &status)) != MPI_SUCCESS)
  114. mpi_throw(err, "(MPI) MPI_Wait() [send] - ");
  115. if ((err = MPI_Wait(&handle_rx, &status)) != MPI_SUCCESS)
  116. mpi_throw(err, "(MPI) MPI_Wait() [recv] - ");
  117. }
  118. // Accessors
  119. [[nodiscard]] ID_t rank() const noexcept { return rank_; }
  120. [[nodiscard]] ID_t size() const noexcept { return size_; }
  121. [[nodiscard]] const std::string& name() const noexcept { return name_; }
  122. // Mutators
  123. ID_t rank(ID_t rank) noexcept { return rank_ = rank; }
  124. ID_t size(ID_t size) noexcept { return size_ = size; }
  125. std::string& name(const std::string& name) noexcept { return name_ = name; }
  126. /*!
  127. * Finalized the MPI
  128. */
  129. void finalize() {
  130. // Finalize the MPI environment
  131. initialized_ = false;
  132. MPI_Finalize();
  133. }
  134. //! RAII MPI finalization
  135. ~MPI_t() {
  136. // Finalize the MPI environment even on unexpected errors
  137. if (initialized_)
  138. MPI_Finalize();
  139. }
  140. // Local functionality
  141. private:
  142. /*!
  143. * Throw exception helper. It bundles the prefix msg with the MPI error string retrieved by
  144. * MPI API.
  145. *
  146. * @param err The MPI error code
  147. * @param prefixMsg The prefix text for the exception error message
  148. */
  149. void mpi_throw(int err, const char* prefixMsg) {
  150. char err_msg[MPI_MAX_ERROR_STRING];
  151. int msg_len;
  152. MPI_Error_string(err, err_msg, &msg_len);
  153. throw std::runtime_error(prefixMsg + std::string (err_msg) + '\n');
  154. }
  155. private:
  156. ID_t rank_{}; //!< MPI rank of the process
  157. ID_t size_{}; //!< MPI total size of the execution
  158. std::string name_{}; //!< The name of the local machine
  159. bool initialized_{}; //!< RAII helper flag
  160. MPI_Request handle_tx{}; //!< MPI async exchange handler for Transmission
  161. MPI_Request handle_rx{}; //!< MPI async exchange handler for Receptions
  162. };
  163. /*
  164. * Exported data types
  165. */
  166. extern MPI_t<> mpi;
  167. using mpi_id_t = MPI_t<>::ID_t;
  168. /*!
  169. * @brief A std::vector wrapper with 2 vectors, an active and a shadow.
  170. *
  171. * This type exposes the standard vector
  172. * functionality of the active vector. The shadow can be used when we need to use the vector as mutable
  173. * data in algorithms that can not support "in-place" editing (like elbow-sort for example)
  174. *
  175. * @tparam Value_t the underlying data type of the vectors
  176. */
  177. template <typename Value_t>
  178. struct ShadowedVec_t {
  179. // STL requirements
  180. using value_type = Value_t;
  181. using iterator = typename std::vector<Value_t>::iterator;
  182. using const_iterator = typename std::vector<Value_t>::const_iterator;
  183. using size_type = typename std::vector<Value_t>::size_type;
  184. // Default constructor
  185. ShadowedVec_t() = default;
  186. // Constructor from an std::vector
  187. explicit ShadowedVec_t(const std::vector<Value_t>& vec)
  188. : North(vec), South(), active(north) {
  189. South.resize(North.size());
  190. }
  191. explicit ShadowedVec_t(std::vector<Value_t>&& vec)
  192. : North(std::move(vec)), South(), active(north) {
  193. South.resize(North.size());
  194. }
  195. // Copy assignment operator
  196. ShadowedVec_t& operator=(const ShadowedVec_t& other) {
  197. if (this != &other) { // Avoid self-assignment
  198. North = other.North;
  199. South = other.South;
  200. active = other.active;
  201. }
  202. return *this;
  203. }
  204. // Move assignment operator
  205. ShadowedVec_t& operator=(ShadowedVec_t&& other) noexcept {
  206. if (this != &other) { // Avoid self-assignment
  207. North = std::move(other.North);
  208. South = std::move(other.South);
  209. active = other.active;
  210. // There is no need to zero out other since it is valid but in a non-defined state
  211. }
  212. return *this;
  213. }
  214. // Type accessors
  215. std::vector<Value_t>& getActive() { return (active == north) ? North : South; }
  216. std::vector<Value_t>& getShadow() { return (active == north) ? South : North; }
  217. const std::vector<Value_t>& getActive() const { return (active == north) ? North : South; }
  218. const std::vector<Value_t>& getShadow() const { return (active == north) ? South : North; }
  219. // Swap vectors
  220. void switch_active() { active = (active == north) ? south : north; }
  221. // Dispatch vector functionality to active vector
  222. Value_t& operator[](size_type index) { return getActive()[index]; }
  223. const Value_t& operator[](size_type index) const { return getActive()[index]; }
  224. Value_t& at(size_type index) { return getActive().at(index); }
  225. const Value_t& at(size_type index) const { return getActive().at(index); }
  226. void push_back(const Value_t& value) { getActive().push_back(value); }
  227. void push_back(Value_t&& value) { getActive().push_back(std::move(value)); }
  228. void pop_back() { getActive().pop_back(); }
  229. Value_t& front() { return getActive().front(); }
  230. Value_t& back() { return getActive().back(); }
  231. const Value_t& front() const { return getActive().front(); }
  232. const Value_t& back() const { return getActive().back(); }
  233. iterator begin() { return getActive().begin(); }
  234. const_iterator begin() const { return getActive().begin(); }
  235. iterator end() { return getActive().end(); }
  236. const_iterator end() const { return getActive().end(); }
  237. size_type size() const { return getActive().size(); }
  238. void resize(size_t new_size) {
  239. North.resize(new_size);
  240. South.resize(new_size);
  241. }
  242. void reserve(size_t new_capacity) {
  243. North.reserve(new_capacity);
  244. South.reserve(new_capacity);
  245. }
  246. [[nodiscard]] size_t capacity() const { return getActive().capacity(); }
  247. [[nodiscard]] bool empty() const { return getActive().empty(); }
  248. void clear() { getActive().clear(); }
  249. void swap(std::vector<Value_t>& other) { getActive().swap(other); }
  250. // Comparisons
  251. bool operator== (const ShadowedVec_t& other) { return getActive() == other.getActive(); }
  252. bool operator!= (const ShadowedVec_t& other) { return getActive() != other.getActive(); }
  253. bool operator== (const std::vector<value_type>& other) { return getActive() == other; }
  254. bool operator!= (const std::vector<value_type>& other) { return getActive() != other; }
  255. private:
  256. std::vector<Value_t> North{}; //!< Actual buffer to be used either as active or shadow
  257. std::vector<Value_t> South{}; //!< Actual buffer to be used either as active or shadow
  258. enum {
  259. north, south
  260. } active{north}; //!< Flag to select between North and South buffer
  261. };
  262. /*
  263. * Exported data types
  264. */
  265. using distBuffer_t = ShadowedVec_t<distValue_t>;
  266. extern distBuffer_t Data;
  267. /*!
  268. * A Logger for entire program.
  269. */
  270. struct Log {
  271. struct Endl {} endl; //!< a tag object to to use it as a new line request.
  272. //! We provide logging via << operator
  273. template<typename T>
  274. Log &operator<<(T &&t) {
  275. if (config.verbose) {
  276. if (line_) {
  277. std::cout << "[Log]: " << t;
  278. line_ = false;
  279. } else
  280. std::cout << t;
  281. }
  282. return *this;
  283. }
  284. // overload for special end line handling
  285. Log &operator<<(Endl e) {
  286. (void) e;
  287. if (config.verbose) {
  288. std::cout << '\n';
  289. line_ = true;
  290. }
  291. return *this;
  292. }
  293. private:
  294. bool line_{true};
  295. };
  296. extern Log logger;
  297. /*!
  298. * A small timing utility based on chrono.
  299. */
  300. struct Timing {
  301. using Tpoint = std::chrono::steady_clock::time_point;
  302. using Tduration = std::chrono::microseconds;
  303. using microseconds = std::chrono::microseconds;
  304. using milliseconds = std::chrono::milliseconds;
  305. using seconds = std::chrono::seconds;
  306. //! tool to mark the starting point
  307. Tpoint start() noexcept { return mark_ = std::chrono::steady_clock::now(); }
  308. //! tool to mark the ending point
  309. Tpoint stop() noexcept {
  310. Tpoint now = std::chrono::steady_clock::now();
  311. duration_ += dt(now, mark_);
  312. return now;
  313. }
  314. //! A duration calculation utility
  315. static Tduration dt(Tpoint t2, Tpoint t1) noexcept {
  316. return std::chrono::duration_cast<Tduration>(t2 - t1);
  317. }
  318. //! Tool to print the time interval
  319. void print_duration(const char *what, mpi_id_t rank) noexcept {
  320. if (std::chrono::duration_cast<microseconds>(duration_).count() < 10000)
  321. std::cout << "[Timing] (Rank " << rank << ") " << what << ": "
  322. << std::to_string(std::chrono::duration_cast<microseconds>(duration_).count()) << " [usec]\n";
  323. else if (std::chrono::duration_cast<milliseconds>(duration_).count() < 10000)
  324. std::cout << "[Timing] (Rank " << rank << ") " << what << ": "
  325. << std::to_string(std::chrono::duration_cast<milliseconds>(duration_).count()) << " [msec]\n";
  326. else {
  327. char stime[26]; // fit ulong
  328. auto sec = std::chrono::duration_cast<seconds>(duration_).count();
  329. auto msec = (std::chrono::duration_cast<milliseconds>(duration_).count() % 1000) / 10; // keep 2 digit
  330. std::sprintf(stime, "%ld.%1ld", sec, msec);
  331. std::cout << "[Timing] (Rank " << rank << ") " << what << ": " << stime << " [sec]\n";
  332. }
  333. }
  334. private:
  335. Tpoint mark_{};
  336. Tduration duration_{};
  337. };
  338. /*!
  339. * Utility "high level function"-like macro to forward a function call
  340. * and accumulate the execution time to the corresponding timing object.
  341. *
  342. * @param Tim The Timing object [Needs to have methods start() and stop()]
  343. * @param Func The function name
  344. * @param ... The arguments to pass to function (the preprocessor way)
  345. */
  346. #define timeCall(Tim, Func, ...) \
  347. Tim.start(); \
  348. Func(__VA_ARGS__); \
  349. Tim.stop(); \
  350. /*!
  351. * A utility to check if a number is power of two
  352. *
  353. * @tparam Integral The integral type of the number to check
  354. * @param x The number to check
  355. * @return True if it is power of 2, false otherwise
  356. */
  357. template <typename Integral>
  358. constexpr inline bool isPowerOfTwo(Integral x) noexcept {
  359. return (!(x & (x - 1)) && x);
  360. }
  361. #endif /* UTILS_HPP_ */