AUTH's THMMY "Parallel and distributed systems" course assignments.
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

pirms 2 nedēļām
pirms 2 nedēļām
pirms 2 nedēļām
pirms 2 nedēļām
pirms 2 nedēļām
pirms 2 nedēļām
pirms 2 nedēļām
pirms 2 nedēļām
pirms 2 nedēļām
pirms 2 nedēļām
pirms 2 nedēļām
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  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. * Min-Max statistics data for exchange optimization
  19. * @tparam Value_t The underlying data type of the sequence data
  20. */
  21. template <typename Value_t>
  22. struct Stat_t {
  23. using value_type = Value_t; //!< meta-export the type
  24. Value_t min{}; //!< The minimum value of the sequence
  25. Value_t max{}; //!< The maximum value of the sequence
  26. };
  27. //! Application data selection alias
  28. using distStat_t = Stat_t<distValue_t>;
  29. extern distStat_t localStat, remoteStat; // Make stats public
  30. /*
  31. * MPI_<type> dispatcher mechanism
  32. */
  33. template <typename T> struct MPI_TypeMapper { };
  34. template <> struct MPI_TypeMapper<char> { static MPI_Datatype getType() { return MPI_CHAR; } };
  35. template <> struct MPI_TypeMapper<short> { static MPI_Datatype getType() { return MPI_SHORT; } };
  36. template <> struct MPI_TypeMapper<int> { static MPI_Datatype getType() { return MPI_INT; } };
  37. template <> struct MPI_TypeMapper<long> { static MPI_Datatype getType() { return MPI_LONG; } };
  38. template <> struct MPI_TypeMapper<long long> { static MPI_Datatype getType() { return MPI_LONG_LONG; } };
  39. template <> struct MPI_TypeMapper<unsigned char> { static MPI_Datatype getType() { return MPI_UNSIGNED_CHAR; } };
  40. template <> struct MPI_TypeMapper<unsigned short>{ static MPI_Datatype getType() { return MPI_UNSIGNED_SHORT; } };
  41. template <> struct MPI_TypeMapper<unsigned int> { static MPI_Datatype getType() { return MPI_UNSIGNED; } };
  42. template <> struct MPI_TypeMapper<unsigned long> { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG; } };
  43. template <> struct MPI_TypeMapper<unsigned long long> { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG_LONG; } };
  44. template <> struct MPI_TypeMapper<float> { static MPI_Datatype getType() { return MPI_FLOAT; } };
  45. template <> struct MPI_TypeMapper<double> { static MPI_Datatype getType() { return MPI_DOUBLE; } };
  46. /*!
  47. * MPI wrapper type to provide MPI functionality and RAII to MPI as a resource
  48. *
  49. * @tparam TID The MPI type for process id [default: int]
  50. */
  51. template<typename TID = int>
  52. struct MPI_t {
  53. using ID_t = TID; // Export TID type (currently int defined by the standard)
  54. /*!
  55. * Initializes the MPI environment, must called from each process
  56. *
  57. * @param argc [int*] POINTER to main's argc argument
  58. * @param argv [char***] POINTER to main's argv argument
  59. */
  60. void init(int* argc, char*** argv) {
  61. // Initialize the MPI environment
  62. int err;
  63. if ((err = MPI_Init(argc, argv)) != MPI_SUCCESS)
  64. mpi_throw(err, "(MPI) MPI_Init() - ");
  65. initialized_ = true;
  66. // Get the number of processes
  67. int size_value, rank_value;
  68. if ((err = MPI_Comm_size(MPI_COMM_WORLD, &size_value)) != MPI_SUCCESS)
  69. mpi_throw(err, "(MPI) MPI_Comm_size() - ");
  70. if ((err = MPI_Comm_rank(MPI_COMM_WORLD, &rank_value)) != MPI_SUCCESS)
  71. mpi_throw(err, "(MPI) MPI_Comm_rank() - ");
  72. size_ = static_cast<ID_t>(size_value);
  73. rank_ = static_cast<ID_t>(rank_value);
  74. // Get the name of the processor
  75. char processor_name[MPI_MAX_PROCESSOR_NAME];
  76. int name_len;
  77. if ((err = MPI_Get_processor_name(processor_name, &name_len)) != MPI_SUCCESS)
  78. mpi_throw(err, "(MPI) MPI_Get_processor_name() - ");
  79. name_ = std::string (processor_name, name_len);
  80. }
  81. /*!
  82. * Exchange data with partner as part of the sorting network of both bubbletonic or bitonic
  83. * sorting algorithms.
  84. *
  85. * This function matches a transmit and a receive in order for fully exchanged data between
  86. * current node and partner.
  87. *
  88. * @tparam T The inner valur type used in buffer
  89. *
  90. * @param ldata [std::vector<T>] Reference to local data to send
  91. * @param rdata [std::vector<T>] Reference to buffer to receive data from partner
  92. * @param partner [mpi_id_t] The partner for the exchange
  93. * @param tag [int] The tag to use for the MPI communication
  94. */
  95. template<typename T>
  96. void exchange_data(const std::vector<T>& ldata, std::vector<T>& rdata, ID_t partner, int tag) {
  97. if (tag < 0)
  98. throw std::runtime_error("(MPI) exchange_data() [tag] - Out of bound");
  99. MPI_Datatype datatype = MPI_TypeMapper<T>::getType();
  100. int count = static_cast<int>(ldata.size());
  101. MPI_Status status;
  102. int err;
  103. if ((err = MPI_Sendrecv(
  104. ldata.data(), count, datatype, partner, tag,
  105. rdata.data(), count, datatype, partner, tag,
  106. MPI_COMM_WORLD, &status
  107. )) != MPI_SUCCESS)
  108. mpi_throw(err, "(MPI) MPI_Sendrecv() [data] - ");
  109. }
  110. /*!
  111. * Exchange a data object with partner as part of the sorting network of both bubbletonic
  112. * or bitonic sorting algorithms.
  113. *
  114. * This function matches a transmit and a receive in order for fully exchanged the data object
  115. * between current node and partner.
  116. *
  117. * @tparam T The object type
  118. *
  119. * @param local [const T&] Reference to the local object to send
  120. * @param remote [T&] Reference to the object to receive data from partner
  121. * @param partner [mpi_id_t] The partner for the exchange
  122. * @param tag [int] The tag to use for the MPI communication
  123. */
  124. template<typename T>
  125. void exchange_it(const T& local, T& remote, ID_t partner, int tag) {
  126. if (tag < 0)
  127. throw std::runtime_error("(MPI) exchange_it() [tag] - Out of bound");
  128. MPI_Status status;
  129. int err;
  130. if ((err = MPI_Sendrecv(
  131. &local, sizeof(T), MPI_BYTE, partner, tag,
  132. &remote, sizeof(T), MPI_BYTE, partner, tag,
  133. MPI_COMM_WORLD, &status
  134. )) != MPI_SUCCESS)
  135. mpi_throw(err, "(MPI) MPI_Sendrecv() [item] - ");
  136. }
  137. // Accessors
  138. [[nodiscard]] ID_t rank() const noexcept { return rank_; }
  139. [[nodiscard]] ID_t size() const noexcept { return size_; }
  140. [[nodiscard]] const std::string& name() const noexcept { return name_; }
  141. // Mutators
  142. ID_t rank(ID_t rank) noexcept { return rank_ = rank; }
  143. ID_t size(ID_t size) noexcept { return size_ = size; }
  144. std::string& name(const std::string& name) noexcept { return name_ = name; }
  145. /*!
  146. * Finalized the MPI
  147. */
  148. void finalize() {
  149. // Finalize the MPI environment
  150. initialized_ = false;
  151. MPI_Finalize();
  152. }
  153. //! RAII MPI finalization
  154. ~MPI_t() {
  155. // Finalize the MPI environment even on unexpected errors
  156. if (initialized_)
  157. MPI_Finalize();
  158. }
  159. // Local functionality
  160. private:
  161. /*!
  162. * Throw exception helper. It bundles the prefix msg with the MPI error string retrieved by
  163. * MPI API.
  164. *
  165. * @param err The MPI error code
  166. * @param prefixMsg The prefix text for the exception error message
  167. */
  168. void mpi_throw(int err, const char* prefixMsg) {
  169. char err_msg[MPI_MAX_ERROR_STRING];
  170. int msg_len;
  171. MPI_Error_string(err, err_msg, &msg_len);
  172. throw std::runtime_error(prefixMsg + std::string (err_msg) + '\n');
  173. }
  174. private:
  175. ID_t rank_{}; //!< MPI rank of the process
  176. ID_t size_{}; //!< MPI total size of the execution
  177. std::string name_{}; //!< The name of the local machine
  178. bool initialized_{}; //!< RAII helper flag
  179. };
  180. /*
  181. * Exported data types
  182. */
  183. extern MPI_t<> mpi;
  184. using mpi_id_t = MPI_t<>::ID_t;
  185. /*!
  186. * @brief A std::vector wrapper with 2 vectors, an active and a shadow.
  187. *
  188. * This type exposes the standard vector
  189. * functionality of the active vector. The shadow can be used when we need to use the vector as mutable
  190. * data in algorithms that can not support "in-place" editing (like elbow-sort for example)
  191. *
  192. * @tparam Value_t the underlying data type of the vectors
  193. */
  194. template <typename Value_t>
  195. struct ShadowedVec_t {
  196. // STL requirements
  197. using value_type = Value_t;
  198. using iterator = typename std::vector<Value_t>::iterator;
  199. using const_iterator = typename std::vector<Value_t>::const_iterator;
  200. using size_type = typename std::vector<Value_t>::size_type;
  201. // Default constructor
  202. ShadowedVec_t() = default;
  203. // Constructor from an std::vector
  204. explicit ShadowedVec_t(const std::vector<Value_t>& vec)
  205. : North(vec), South(), active(north) {
  206. South.resize(North.size());
  207. }
  208. explicit ShadowedVec_t(std::vector<Value_t>&& vec)
  209. : North(std::move(vec)), South(), active(north) {
  210. South.resize(North.size());
  211. }
  212. // Copy assignment operator
  213. ShadowedVec_t& operator=(const ShadowedVec_t& other) {
  214. if (this != &other) { // Avoid self-assignment
  215. North = other.North;
  216. South = other.South;
  217. active = other.active;
  218. }
  219. return *this;
  220. }
  221. // Move assignment operator
  222. ShadowedVec_t& operator=(ShadowedVec_t&& other) noexcept {
  223. if (this != &other) { // Avoid self-assignment
  224. North = std::move(other.North);
  225. South = std::move(other.South);
  226. active = other.active;
  227. // There is no need to zero out other since it is valid but in a non-defined state
  228. }
  229. return *this;
  230. }
  231. // Type accessors
  232. std::vector<Value_t>& getActive() { return (active == north) ? North : South; }
  233. std::vector<Value_t>& getShadow() { return (active == north) ? South : North; }
  234. const std::vector<Value_t>& getActive() const { return (active == north) ? North : South; }
  235. const std::vector<Value_t>& getShadow() const { return (active == north) ? South : North; }
  236. // Swap vectors
  237. void switch_active() { active = (active == north) ? south : north; }
  238. // Dispatch vector functionality to active vector
  239. Value_t& operator[](size_type index) { return getActive()[index]; }
  240. const Value_t& operator[](size_type index) const { return getActive()[index]; }
  241. Value_t& at(size_type index) { return getActive().at(index); }
  242. const Value_t& at(size_type index) const { return getActive().at(index); }
  243. void push_back(const Value_t& value) { getActive().push_back(value); }
  244. void push_back(Value_t&& value) { getActive().push_back(std::move(value)); }
  245. void pop_back() { getActive().pop_back(); }
  246. Value_t& front() { return getActive().front(); }
  247. Value_t& back() { return getActive().back(); }
  248. const Value_t& front() const { return getActive().front(); }
  249. const Value_t& back() const { return getActive().back(); }
  250. iterator begin() { return getActive().begin(); }
  251. const_iterator begin() const { return getActive().begin(); }
  252. iterator end() { return getActive().end(); }
  253. const_iterator end() const { return getActive().end(); }
  254. size_type size() const { return getActive().size(); }
  255. void resize(size_t new_size) {
  256. North.resize(new_size);
  257. South.resize(new_size);
  258. }
  259. void reserve(size_t new_capacity) {
  260. North.reserve(new_capacity);
  261. South.reserve(new_capacity);
  262. }
  263. [[nodiscard]] size_t capacity() const { return getActive().capacity(); }
  264. [[nodiscard]] bool empty() const { return getActive().empty(); }
  265. void clear() { getActive().clear(); }
  266. void swap(std::vector<Value_t>& other) { getActive().swap(other); }
  267. // Comparisons
  268. bool operator== (const ShadowedVec_t& other) { return getActive() == other.getActive(); }
  269. bool operator!= (const ShadowedVec_t& other) { return getActive() != other.getActive(); }
  270. bool operator== (const std::vector<value_type>& other) { return getActive() == other; }
  271. bool operator!= (const std::vector<value_type>& other) { return getActive() != other; }
  272. private:
  273. std::vector<Value_t> North{}; //!< Actual buffer to be used either as active or shadow
  274. std::vector<Value_t> South{}; //!< Actual buffer to be used either as active or shadow
  275. enum {
  276. north, south
  277. } active{north}; //!< Flag to select between North and South buffer
  278. };
  279. /*
  280. * Exported data types
  281. */
  282. using distBuffer_t = ShadowedVec_t<distValue_t>;
  283. extern distBuffer_t Data;
  284. /*!
  285. * A Logger for entire program.
  286. */
  287. struct Log {
  288. struct Endl {} endl; //!< a tag object to to use it as a new line request.
  289. //! We provide logging via << operator
  290. template<typename T>
  291. Log &operator<<(T &&t) {
  292. if (config.verbose) {
  293. if (line_) {
  294. std::cout << "[Log]: " << t;
  295. line_ = false;
  296. } else
  297. std::cout << t;
  298. }
  299. return *this;
  300. }
  301. // overload for special end line handling
  302. Log &operator<<(Endl e) {
  303. (void) e;
  304. if (config.verbose) {
  305. std::cout << '\n';
  306. line_ = true;
  307. }
  308. return *this;
  309. }
  310. private:
  311. bool line_{true};
  312. };
  313. extern Log logger;
  314. /*!
  315. * A small timing utility based on chrono.
  316. */
  317. struct Timing {
  318. using Tpoint = std::chrono::steady_clock::time_point;
  319. using Tduration = std::chrono::microseconds;
  320. using microseconds = std::chrono::microseconds;
  321. using milliseconds = std::chrono::milliseconds;
  322. using seconds = std::chrono::seconds;
  323. //! tool to mark the starting point
  324. Tpoint start() noexcept { return mark_ = std::chrono::steady_clock::now(); }
  325. //! tool to mark the ending point
  326. Tpoint stop() noexcept {
  327. Tpoint now = std::chrono::steady_clock::now();
  328. duration_ += dt(now, mark_);
  329. return now;
  330. }
  331. //! A duration calculation utility
  332. static Tduration dt(Tpoint t2, Tpoint t1) noexcept {
  333. return std::chrono::duration_cast<Tduration>(t2 - t1);
  334. }
  335. //! Tool to print the time interval
  336. void print_duration(const char *what, mpi_id_t rank) noexcept {
  337. if (std::chrono::duration_cast<microseconds>(duration_).count() < 10000)
  338. std::cout << "[Timing] (Rank " << rank << ") " << what << ": "
  339. << std::to_string(std::chrono::duration_cast<microseconds>(duration_).count()) << " [usec]\n";
  340. else if (std::chrono::duration_cast<milliseconds>(duration_).count() < 10000)
  341. std::cout << "[Timing] (Rank " << rank << ") " << what << ": "
  342. << std::to_string(std::chrono::duration_cast<milliseconds>(duration_).count()) << " [msec]\n";
  343. else
  344. std::cout << "[Timing] (Rank " << rank << ") " << what << ": "
  345. << std::to_string(std::chrono::duration_cast<seconds>(duration_).count()) << " [sec]\n";
  346. }
  347. private:
  348. Tpoint mark_{};
  349. Tduration duration_{};
  350. };
  351. /*!
  352. * Utility "high level function"-like macro to forward a function call
  353. * and accumulate the execution time to the corresponding timing object.
  354. *
  355. * @param Tim The Timing object [Needs to have methods start() and stop()]
  356. * @param Func The function name
  357. * @param ... The arguments to pass to function (the preprocessor way)
  358. */
  359. #define timeCall(Tim, Func, ...) \
  360. Tim.start(); \
  361. Func(__VA_ARGS__); \
  362. Tim.stop(); \
  363. #endif /* UTILS_HPP_ */