Year 3 parallel coursework

parallel.c 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. #include <stdio.h>
  2. #include <stdbool.h>
  3. #include <stdlib.h>
  4. #include <time.h>
  5. #include <pthread.h>
  6. #include <math.h>
  7. #include <string.h>
  8. static pthread_barrier_t sync_barrier;
  9. bool solved;
  10. bool allow_print;
  11. // Struct containing all the useful information for a thread
  12. typedef struct thread_data {
  13. unsigned long start_row;
  14. unsigned long end_row;
  15. double **array;
  16. unsigned long dimension;
  17. double precision;
  18. bool finished;
  19. } THREAD_DATA_T;
  20. void printSquare(double **array, unsigned long dimension) {
  21. unsigned long i, j;
  22. for (i = 0; i < dimension; i++) {
  23. for (j = 0; j < dimension; j++) printf("%f ", array[i][j]);
  24. printf("\n");
  25. }
  26. }
  27. long double time_seconds(struct timespec begin, struct timespec end) {
  28. long long sec_diff = end.tv_sec - begin.tv_sec;
  29. long long nsec_diff = end.tv_nsec - begin.tv_nsec;
  30. long long as_ns = sec_diff * 1000000000L + nsec_diff;
  31. return (long double) as_ns / 1000000000.0;
  32. }
  33. void solveArray(double **array, unsigned long dimension, double precision,
  34. unsigned long start, unsigned long end) {
  35. unsigned long i, j;
  36. for (i = start; i < end; i++) {
  37. for (j = 1; j < dimension - 1; j++) {
  38. // no need to gain locks on any of the array elements as we are the
  39. // only one writing to them
  40. // and whether we get the old or new values of an element
  41. // doesn't matter
  42. double previous = array[i][j];
  43. double avg = (array[i][j - 1] + array[i][j + 1] + array[i - 1][j] +
  44. array[i + 1][j]) / 4.0;
  45. array[i][j] = avg;
  46. // only do the actual check if we haven't already failed
  47. if (solved == true && fabs(avg - previous) > precision) {
  48. // dont bother getting a lock here because the only other value
  49. // it will be set to
  50. // is false and we don't care if it gets messed up
  51. solved = false;
  52. }
  53. }
  54. }
  55. }
  56. void *bootstrap_thread(void *arg) {
  57. THREAD_DATA_T *td = (THREAD_DATA_T *) arg;
  58. // Each thread will do a single iteration over the array before waiting for
  59. // every thread to finish
  60. // and then waiting for the main thread to check if things are solved
  61. while (1) {
  62. if (td->finished) {
  63. // this is only here to stop static code analysis
  64. // whining about infinite loops
  65. return NULL;
  66. }
  67. solveArray(td->array, td->dimension, td->precision, td->start_row,
  68. td->end_row);
  69. // first barrier waits for all threads to finish
  70. pthread_barrier_wait(&sync_barrier);
  71. // wait for main thread to check completion
  72. pthread_barrier_wait(&sync_barrier);
  73. }
  74. return NULL;
  75. }
  76. double **constructBigArray(double top, double bottom, double left, double right,
  77. long unsigned int n) {
  78. // here lies exciting memory allocation and array initialisation, boring..
  79. long unsigned int i, j, k;
  80. double **arr = (double **) malloc(n * sizeof(double *));
  81. for (k = 0; k < n; k++) {
  82. arr[k] = (double *) malloc(n * sizeof(double));
  83. }
  84. for (i = 0; i < n; i++) {
  85. for (j = 0; j < n; j++) {
  86. if (i == 0) {
  87. if (j == 0 || j == n - 1) arr[i][j] = 0;
  88. else arr[i][j] = top;
  89. } else if (i == n - 1) {
  90. if (j == 0 || j == n - 1) arr[i][j] = 0;
  91. else arr[i][j] = bottom;
  92. } else if (j == 0) {
  93. arr[i][j] = left;
  94. } else if (j == n - 1) {
  95. arr[i][j] = right;
  96. } else {
  97. arr[i][j] = 0;
  98. }
  99. }
  100. }
  101. return arr;
  102. }
  103. // CHANGE ME to set thread count
  104. #define NUM_THREADS 10
  105. // struct to hold some argument data
  106. typedef struct arg_data {
  107. double precision;
  108. unsigned long dimension;
  109. double top_start;
  110. double bottom_start;
  111. double left_start;
  112. double right_start;
  113. } ARG_DATA_T;
  114. // helper function for reading in arguments
  115. int argsContain(const char *val, char **arr, int size) {
  116. int i;
  117. for (i = 0; i < size; i++) {
  118. if (strcmp(arr[i], val) == 0)
  119. return i;
  120. }
  121. return -1;
  122. }
  123. // take a look at argv and print out usage or take in some arguments
  124. ARG_DATA_T *parseArgs(int argc, char **argv) {
  125. int tmp;
  126. ARG_DATA_T *args = (ARG_DATA_T *) malloc(sizeof(ARG_DATA_T));
  127. allow_print = true;
  128. if ((tmp = argsContain("-p", argv, argc)) >= 0 ||
  129. (tmp = argsContain("--precision", argv, argc)) >= 0) {
  130. args->precision = strtod(argv[tmp + 1], NULL);
  131. } else {
  132. args->precision = 0.00001;
  133. }
  134. if ((tmp = argsContain("-d", argv, argc)) >= 0 ||
  135. (tmp = argsContain("--dimension", argv, argc)) >= 0) {
  136. args->dimension = (unsigned long) strtol(argv[tmp + 1], NULL, 10);
  137. } else {
  138. args->dimension = 10;
  139. }
  140. if ((tmp = argsContain("-t", argv, argc)) >= 0 ||
  141. (tmp = argsContain("--top", argv, argc)) >= 0) {
  142. args->top_start = strtod(argv[tmp + 1], NULL);
  143. } else {
  144. args->top_start = 1;
  145. }
  146. if ((tmp = argsContain("-l", argv, argc)) >= 0 ||
  147. (tmp = argsContain("--left", argv, argc)) >= 0) {
  148. args->left_start = strtod(argv[tmp + 1], NULL);
  149. } else {
  150. args->left_start = 2;
  151. }
  152. if ((tmp = argsContain("-r", argv, argc)) >= 0 ||
  153. (tmp = argsContain("--right", argv, argc)) >= 0) {
  154. args->right_start = strtod(argv[tmp + 1], NULL);
  155. } else {
  156. args->right_start = 4;
  157. }
  158. if ((tmp = argsContain("-b", argv, argc)) >= 0 ||
  159. (tmp = argsContain("--bottom", argv, argc)) >= 0) {
  160. args->bottom_start = strtod(argv[tmp + 1], NULL);
  161. } else {
  162. args->bottom_start = 3;
  163. }
  164. if ((tmp = argsContain("-q", argv, argc)) > 0 ||
  165. (tmp = argsContain("--quiet", argv, argc)) >= 0) {
  166. allow_print = false;
  167. }
  168. if (argsContain("-h", argv, argc) >= 0 ||
  169. argsContain("--help", argv, argc) >= 0) {
  170. printf("Usage: %s [-p N] [-d N] [-t N] [-l N] [-r N] [-b N]\n",
  171. argv[0]);
  172. printf("Constructs a 2D array of doubles, with values from arguments "
  173. "of size dimension^2 and performs relaxation method"
  174. " to given precision.\n");
  175. printf("Options:\n");
  176. printf("-p/--precision: A double value to specify precision. "
  177. "Default: 0.0001\n");
  178. printf("-d/--dimension: A long value to specify width and height of "
  179. "grid. Default: 10\n");
  180. printf("-t/--top: A double indicating the values along the top of the "
  181. "array. Default: 1.0\n");
  182. printf("-l/--left: A double indicating the values along the left side"
  183. " of the array. Default: 2.0\n");
  184. printf("-b/--bottom: A double indicating the values along the bottom "
  185. "of the array. Default: 3.0\n");
  186. printf("-r/--right: A double indicating the values along the right "
  187. "side of the array. Default: 4.0\n");
  188. printf("-q/--quiet: Disables all printing except the result.\n");
  189. exit(0);
  190. }
  191. return args;
  192. }
  193. // this function is used to return the number of rows a given thread will work
  194. // on
  195. // for an amount of rows
  196. unsigned long
  197. spread(unsigned long group, unsigned long totalGroups, unsigned long n) {
  198. if (totalGroups % n == 0) return totalGroups / n;
  199. else {
  200. unsigned long tmp = (totalGroups / n);
  201. if (totalGroups % n >= group) return tmp + 1;
  202. else return tmp;
  203. }
  204. }
  205. // helper function as not everyone defines the MIN macro
  206. unsigned long mini(unsigned long a, unsigned long b) {
  207. if (a < b) {
  208. return a;
  209. } else {
  210. return b;
  211. }
  212. }
  213. int main(int argc, char **argv) {
  214. int rc;
  215. pthread_t thread[NUM_THREADS];
  216. THREAD_DATA_T *td[NUM_THREADS];
  217. ARG_DATA_T *args;
  218. struct timespec begin, end;
  219. args = parseArgs(argc, argv);
  220. // the barrier should be at least the array size, subtract 2, as we won't
  221. // use all threads if there are not enough single rows to share out
  222. pthread_barrier_init(&sync_barrier, NULL,
  223. (unsigned int) mini(args->dimension - 2, NUM_THREADS));
  224. double **array = constructBigArray(args->top_start, args->bottom_start,
  225. args->left_start, args->right_start,
  226. args->dimension);
  227. // use clock_gettime to measure performance as it has best resolution
  228. clock_gettime(CLOCK_MONOTONIC, &begin);
  229. td[0] = (THREAD_DATA_T *) malloc(sizeof(THREAD_DATA_T));
  230. solved = true;
  231. // create NUM_THREADS-1 pthreads, starting from 1 - because the main thread
  232. // is one of our threads
  233. unsigned long rowCounter = 1 + spread(1, args->dimension - 2, NUM_THREADS);
  234. for (unsigned long i = 1; i < NUM_THREADS; i++) {
  235. // divides up the array into rows, so that each thread is operating on
  236. // chunks of contiguous memory
  237. unsigned long start_row, end_row;
  238. start_row = rowCounter;
  239. rowCounter += spread(i + 1, args->dimension - 2, NUM_THREADS);
  240. end_row = rowCounter;
  241. td[i] = (THREAD_DATA_T *) malloc(sizeof(THREAD_DATA_T));
  242. td[i]->start_row = start_row;
  243. td[i]->end_row = end_row;
  244. td[i]->array = array;
  245. td[i]->dimension = args->dimension;
  246. td[i]->precision = args->precision;
  247. td[i]->finished = false;
  248. if (end_row - start_row > 0) { // only create threads that will do work
  249. if ((rc = pthread_create(&thread[i], NULL, bootstrap_thread,
  250. (void *) td[i]))) {
  251. printf("failed to create threads: %d\n", rc);
  252. return 1;
  253. }
  254. }
  255. }
  256. while (1) {
  257. //main thread should also be solving
  258. solveArray(array, args->dimension, args->precision, 1,
  259. 1 + spread(1, args->dimension - 2, NUM_THREADS));
  260. pthread_barrier_wait(&sync_barrier);
  261. if (solved) {
  262. clock_gettime(CLOCK_MONOTONIC, &end);
  263. if (allow_print) printf("took me %Lfs\n", time_seconds(begin, end));
  264. for (int i = 1; i < NUM_THREADS; i++) {
  265. td[i]->finished = true;
  266. }
  267. if (allow_print) printf("====== SOLVED =====\n");
  268. printSquare(array, args->dimension);
  269. return 0;
  270. }
  271. solved = true; // make sure to reset this
  272. pthread_barrier_wait(&sync_barrier);
  273. }
  274. }