AUTH's THMMY "Parallel and distributed systems" course assignments.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 
 

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