From 03d8401d5fa9522989e463ce1098738ee3b48b79 Mon Sep 17 00:00:00 2001 From: violette Date: Wed, 10 Apr 2024 16:37:23 -0400 Subject: [PATCH 1/5] MPI: max time --- .gitignore | 1 + src/ift630_sts3 | Bin 16056 -> 0 bytes src/ift630_sts3d | Bin 16056 -> 0 bytes src/main.c | 310 +++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 263 insertions(+), 48 deletions(-) delete mode 100755 src/ift630_sts3 delete mode 100755 src/ift630_sts3d diff --git a/.gitignore b/.gitignore index 378eac2..1bc4e0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ build +.vimspector.json diff --git a/src/ift630_sts3 b/src/ift630_sts3 deleted file mode 100755 index 8da96fa9717df1e0fd23f43146b647cf43c923b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16056 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 J1_m(#`T^ATs7n9< diff --git a/src/ift630_sts3d b/src/ift630_sts3d deleted file mode 100755 index 8da96fa9717df1e0fd23f43146b647cf43c923b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16056 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 J1_m(#`T^ATs7n9< diff --git a/src/main.c b/src/main.c index 68df94a..282a6d1 100644 --- a/src/main.c +++ b/src/main.c @@ -1,59 +1,273 @@ -/* - "Hello World" MPI Test Program -*/ -#include -#include -#include +#define _XOPEN_SOURCE 700 +#include + #include +#include +#include +#include +#include -int main(int argc, char** argv) { - // Initialize the MPI environment - MPI_Init(NULL, NULL); +enum HEADER { + TRIP_ID = 0, + ARRIVAL_TIME, + DEPARTURE_TIME, + STOP_ID, + STOP_SEQUENCE, + STOP_HEADSIGN, + PICKUP_TYPE, + DROP_OFF_TYPE, + SHAPE_DIST_TRAVELED, + TIMEPOINT, + END +}; - int world_size, world_rank; - MPI_Comm_size(MPI_COMM_WORLD, &world_size); - MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); +#define DELIM ',' +#define OVERLAP 100 +#define STOP_FILE "./stop_times.txt" - // Get the name of the processor - char processor_name[MPI_MAX_PROCESSOR_NAME]; - int name_len; - MPI_Get_processor_name(processor_name, &name_len); +int do_map(char *, size_t); +void do_reduce(size_t); +int parprocess(MPI_File *, const int, const int); - // Print off a hello world message - printf("Hello world from processor %s, rank %d out of %d processors\n", - processor_name, world_rank, world_size); +int +main(int argc, char **argv) +{ + // Initialize the MPI environment + MPI_Init(NULL, NULL); + int world_size, world_rank; + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); + int localmax = 0, globalmax = 0; - // We are assuming at least 2 processes for this task - if (world_size < 2) { - fprintf(stderr, "World size must be greater than 1 for %s\n", argv[0]); - MPI_Abort(MPI_COMM_WORLD, 1); - } - int number; - if (world_rank == 0) { - // If we are rank 0, set the number to -1 and send it to process 1 - number = -1; - MPI_Send( - /* data = */ &number, - /* count = */ 1, - /* datatype = */ MPI_INT, - /* destination = */ 1, - /* tag = */ 0, - /* communicator = */ MPI_COMM_WORLD); - } else if (world_rank == 1) { - MPI_Recv( - /* data = */ &number, - /* count = */ 1, - /* datatype = */ MPI_INT, - /* source = */ 0, - /* tag = */ 0, - /* communicator = */ MPI_COMM_WORLD, - /* status = */ MPI_STATUS_IGNORE); - printf("Process 1 received number %d from process 0\n", number); - } + // Get the name of the processor + char processor_name[MPI_MAX_PROCESSOR_NAME]; + int name_len; + MPI_Get_processor_name(processor_name, &name_len); - // Finalize the MPI environment. - MPI_Finalize(); + // Print off a hello world message + printf("Hello world from processor %s, rank %d out of %d processors\n", + processor_name, world_rank, world_size); + + int number, size; + ssize_t err; + MPI_File in; + + if ((err = MPI_File_open(MPI_COMM_WORLD, STOP_FILE, MPI_MODE_RDONLY, + MPI_INFO_NULL, &in))) { + fprintf(stderr, "%s: Couldn't open file %s\n", argv[0], + STOP_FILE); + exit(-1); + } + MPI_Comm_size(MPI_COMM_WORLD, &size); + + localmax = parprocess(&in, world_rank, world_size); + + MPI_Reduce(&localmax, &globalmax, 1, MPI_INT, MPI_MAX, 0, + MPI_COMM_WORLD); + if (world_rank == 0) + printf("globalmax: %d\n", globalmax); + + MPI_File_close(&in); + MPI_Finalize(); + exit(0); } +time_t +substr_time(struct tm a, struct tm b) +{ + return (a.tm_hour * 3600 + a.tm_min * 60 + a.tm_sec) - + (b.tm_hour * 3600 + b.tm_min * 60 + b.tm_sec); +} + +char * +get_word(char *lines, size_t x, size_t y, size_t num_attr, size_t max_attr) +{ + size_t offset = (x * (num_attr * max_attr) + y * (max_attr)); + return lines + offset; +} + +void +fill_lines(char *chunk, size_t num_char, size_t num_lines, size_t num_attr, + size_t max_attr, char *lines) +{ + size_t attr_pos = 0, line_pos = 0, word_pos = 0; + for (size_t k = 0; k < num_char; ++k) { + if (chunk[k] == DELIM) { + // go to next attrib + char *word = get_word(lines, line_pos, attr_pos, + num_attr, max_attr); + memcpy(word, chunk + k - word_pos, word_pos); + word[word_pos] = '\0'; + ++attr_pos; + word_pos = 0; + } + else if (chunk[k] == '\n') { + ++line_pos; + attr_pos = 0; + word_pos = 0; + } + else if (chunk[k] == '\r') {} + else { + ++word_pos; + } + } +} + +void +get_lines_info(char *chunk, size_t num_char, size_t *max_attr_size, + size_t *num_lines) +{ + // count max line size and number of lines + size_t current_attr_size = 0; + for (int k = 0; k < num_char; ++k) { + // LINE + if (chunk[k] == '\n' || chunk[k] == '\r') + ++(*num_lines); + // ATTRIBUTES + if (chunk[k] == DELIM) { + if (current_attr_size > *max_attr_size) + *max_attr_size = current_attr_size; + current_attr_size = 0; + } else + ++current_attr_size; + } +} + +// get num of cols of csv +size_t +get_num_attr(char *chunk) +{ + size_t num_attr = 0; + for (size_t k = 0 ; chunk[k] != '\n' ; ++k) + if (chunk[k] == DELIM) + ++num_attr; + return num_attr; +} + +int +do_map(char *chunk, size_t num_char) +{ + size_t num_lines = 0, num_attr = 0; + size_t max_attr_size = 0; + + get_lines_info(chunk, num_char, &max_attr_size, &num_lines); + num_attr = get_num_attr(chunk); + + // allocate lines (just a big continuous chunk) + // is a 2d arr of char* + char *lines; + lines = calloc(1, num_lines * num_attr * max_attr_size); + + + fill_lines(chunk, num_char, num_lines, num_attr, max_attr_size, lines); + + struct tm dep_time, arr_time; + time_t max_time = 0; + for (size_t k = 0; k < num_lines; ++k) { + memcpy(&dep_time, + get_word(lines, k, DEPARTURE_TIME, num_attr, + max_attr_size), + sizeof(struct tm)); + memcpy(&arr_time, + get_word(lines, k, ARRIVAL_TIME, num_attr, + max_attr_size), + sizeof(struct tm)); + // arr_time.tm_sec); + + strptime(get_word(lines, k, DEPARTURE_TIME, num_attr, + max_attr_size), + "%H:%M:%S", &dep_time); + + strptime(get_word(lines, k, ARRIVAL_TIME, num_attr, + max_attr_size), + "%H:%M:%S", &arr_time); + time_t tmp = substr_time(arr_time, dep_time); + if (tmp > max_time) + max_time = tmp; + } + free(lines); + free(chunk); + return max_time; +} + +// help from https://stackoverflow.com/questions/12939279/mpi-reading-from-a-text-file +// is hosted under a permissive licence, ty Jonathan Dursi :) +int +parprocess(MPI_File *in, const int rank, const int size) +{ + // reads revelant lines from file to chunk. + // IN OUR CASE we will use overlap to reach EOF of this line. + // Duplicates dont matter in our case ; res will be the same either way. + size_t proc_size, total_size, total_size_overlap; + char *chunk; + MPI_Offset globalstart; + MPI_Offset globalend; + MPI_Offset filesize; + + MPI_File_get_size(*in, &filesize); + filesize--; /* get rid of text file eof */ + proc_size = filesize / size; + globalstart = rank * proc_size; + globalend = globalstart + proc_size - 1; + if (rank == size - 1) + globalend = filesize - 1; + + /* add overlap to the end of everyone's chunk except last + * proc... */ + size_t globalend_overlap = globalend; + if (rank != size - 1) + globalend_overlap += OVERLAP; + + total_size_overlap = globalend_overlap - globalstart + 1; + total_size = globalend - globalstart + 1; + + /* allocate memory, filled with 0 */ + chunk = calloc(1, total_size); + printf("[PARPROCESS] begin\n"); + + ssize_t err; + { + err = MPI_File_read_at_all_begin(*in, globalstart, chunk, + total_size, MPI_CHAR); + if (err) { + printf("error %lu\n", err); + MPI_Finalize(); + } + err = MPI_File_read_at_all_end(*in, chunk, MPI_STATUS_IGNORE); + if (err) { + printf("error %lu\n", err); + MPI_Finalize(); + } + } + + + // fills the first incoherent bytes with \0 + //size_t k = 0; + //if (rank != 0) { // first has no incoherece at begining + // for (; chunk[k] != '\r' && chunk[k] != '\n'; + // ++k) // get number of incoherent bytes :) + // ; + // // reset + + // memmove(chunk, chunk + k, total_size); // - 2: dont count \n\r + //} + + // fill char after next EOL wiht \0 ; starting from proc_size to end of + // overlap + //if (rank != size) { // last doesnt have padding, dont check it + // for (; (chunk[globalend] != '\n' && chunk[globalend] != '\r') && + // globalend < globalend_overlap; + // ++globalend) + // ; + // memset(chunk + globalend, '\0', OVERLAP); + // // + //} + //chunk[total_size_overlap] = '\0'; // just to be sure! + + int max = do_map(chunk, total_size); + printf("[PARPROCESS] end, max: %d\n", max); + + return max; +} From ce4e52a5acd14635f72577046e81b22af7a1c690 Mon Sep 17 00:00:00 2001 From: violette Date: Wed, 10 Apr 2024 16:41:04 -0400 Subject: [PATCH 2/5] MPI: cleaner map func --- src/main.c | 59 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/src/main.c b/src/main.c index 282a6d1..4bc80c8 100644 --- a/src/main.c +++ b/src/main.c @@ -39,6 +39,12 @@ main(int argc, char **argv) MPI_Comm_size(MPI_COMM_WORLD, &world_size); MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); + if (argc != 2) { + printf("Please enter key as arg"); + exit(-1); + } + + char *key = argv[1]; int localmax = 0, globalmax = 0; // Get the name of the processor @@ -146,6 +152,33 @@ get_num_attr(char *chunk) return num_attr; } +int get_max_time(char *lines, size_t num_lines, size_t num_attr, size_t max_attr) +{ + struct tm dep_time, arr_time; + time_t max_time = 0; + for (size_t k = 0; k < num_lines; ++k) { + memcpy(&dep_time, + get_word(lines, k, DEPARTURE_TIME, num_attr, max_attr), + sizeof(struct tm)); + memcpy(&arr_time, + get_word(lines, k, ARRIVAL_TIME, num_attr, max_attr), + sizeof(struct tm)); + // arr_time.tm_sec); + + strptime(get_word(lines, k, DEPARTURE_TIME, num_attr, + max_attr), + "%H:%M:%S", &dep_time); + + strptime(get_word(lines, k, ARRIVAL_TIME, num_attr, + max_attr), + "%H:%M:%S", &arr_time); + time_t tmp = substr_time(arr_time, dep_time); + if (tmp > max_time) + max_time = tmp; + } + return max_time; +} + int do_map(char *chunk, size_t num_char) { @@ -163,30 +196,8 @@ do_map(char *chunk, size_t num_char) fill_lines(chunk, num_char, num_lines, num_attr, max_attr_size, lines); - struct tm dep_time, arr_time; - time_t max_time = 0; - for (size_t k = 0; k < num_lines; ++k) { - memcpy(&dep_time, - get_word(lines, k, DEPARTURE_TIME, num_attr, - max_attr_size), - sizeof(struct tm)); - memcpy(&arr_time, - get_word(lines, k, ARRIVAL_TIME, num_attr, - max_attr_size), - sizeof(struct tm)); - // arr_time.tm_sec); - - strptime(get_word(lines, k, DEPARTURE_TIME, num_attr, - max_attr_size), - "%H:%M:%S", &dep_time); - - strptime(get_word(lines, k, ARRIVAL_TIME, num_attr, - max_attr_size), - "%H:%M:%S", &arr_time); - time_t tmp = substr_time(arr_time, dep_time); - if (tmp > max_time) - max_time = tmp; - } + int max_time = get_max_time(lines, num_lines, num_attr, max_attr_size); + free(lines); free(chunk); return max_time; From 649632991427762d67183579f753f40f93f6078a Mon Sep 17 00:00:00 2001 From: violette Date: Wed, 10 Apr 2024 18:58:26 -0400 Subject: [PATCH 3/5] MPI: search key --- src/main.c | 128 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 92 insertions(+), 36 deletions(-) diff --git a/src/main.c b/src/main.c index 4bc80c8..3b0e6da 100644 --- a/src/main.c +++ b/src/main.c @@ -25,9 +25,10 @@ enum HEADER { #define OVERLAP 100 #define STOP_FILE "./stop_times.txt" -int do_map(char *, size_t); +// adding key here! +int do_map(char *, size_t, char *, int); void do_reduce(size_t); -int parprocess(MPI_File *, const int, const int); +int parprocess(MPI_File *, const int, const int, char *); int main(int argc, char **argv) @@ -39,22 +40,16 @@ main(int argc, char **argv) MPI_Comm_size(MPI_COMM_WORLD, &world_size); MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); + char *key; if (argc != 2) { - printf("Please enter key as arg"); - exit(-1); + if (world_rank == 0) + printf("no args ; indexing all\n"); + key = NULL; } + else + key = argv[2]; - char *key = argv[1]; - int localmax = 0, globalmax = 0; - - // Get the name of the processor - char processor_name[MPI_MAX_PROCESSOR_NAME]; - int name_len; - MPI_Get_processor_name(processor_name, &name_len); - - // Print off a hello world message - printf("Hello world from processor %s, rank %d out of %d processors\n", - processor_name, world_rank, world_size); + size_t res = 0; int number, size; ssize_t err; @@ -68,12 +63,19 @@ main(int argc, char **argv) } MPI_Comm_size(MPI_COMM_WORLD, &size); - localmax = parprocess(&in, world_rank, world_size); + res = parprocess(&in, world_rank, world_size, key); - MPI_Reduce(&localmax, &globalmax, 1, MPI_INT, MPI_MAX, 0, - MPI_COMM_WORLD); - if (world_rank == 0) - printf("globalmax: %d\n", globalmax); + if (world_rank == 0) { + for (char k = 0; k < world_size; k++) { + MPI_Recv(&res, 1, MPI_UNSIGNED_LONG, MPI_ANY_SOURCE, 0, + MPI_COMM_WORLD, MPI_STATUS_IGNORE); + if (res) { + //printf("res: found in %lu ns\n", res); break; + } + else + printf("res: not found\n"); + } + } MPI_File_close(&in); MPI_Finalize(); @@ -152,7 +154,20 @@ get_num_attr(char *chunk) return num_attr; } -int get_max_time(char *lines, size_t num_lines, size_t num_attr, size_t max_attr) +int +search_key(char *lines, size_t num_lines, size_t num_attr, size_t max_attr, + char *key) +{ + for (size_t k = 0; k < num_lines; ++k) { + if (!strcmp(get_word(lines, k, TRIP_ID, num_attr, max_attr), + key)) + return 1; + } + return 0; +} + +int +get_max_time(char *lines, size_t num_lines, size_t num_attr, size_t max_attr) { struct tm dep_time, arr_time; time_t max_time = 0; @@ -163,14 +178,11 @@ int get_max_time(char *lines, size_t num_lines, size_t num_attr, size_t max_attr memcpy(&arr_time, get_word(lines, k, ARRIVAL_TIME, num_attr, max_attr), sizeof(struct tm)); - // arr_time.tm_sec); - strptime(get_word(lines, k, DEPARTURE_TIME, num_attr, - max_attr), + strptime(get_word(lines, k, DEPARTURE_TIME, num_attr, max_attr), "%H:%M:%S", &dep_time); - strptime(get_word(lines, k, ARRIVAL_TIME, num_attr, - max_attr), + strptime(get_word(lines, k, ARRIVAL_TIME, num_attr, max_attr), "%H:%M:%S", &arr_time); time_t tmp = substr_time(arr_time, dep_time); if (tmp > max_time) @@ -180,10 +192,11 @@ int get_max_time(char *lines, size_t num_lines, size_t num_attr, size_t max_attr } int -do_map(char *chunk, size_t num_char) +do_map(char *chunk, size_t num_char, char *key, int rank) { size_t num_lines = 0, num_attr = 0; size_t max_attr_size = 0; + size_t time, res; get_lines_info(chunk, num_char, &max_attr_size, &num_lines); num_attr = get_num_attr(chunk); @@ -193,20 +206,64 @@ do_map(char *chunk, size_t num_char) char *lines; lines = calloc(1, num_lines * num_attr * max_attr_size); - fill_lines(chunk, num_char, num_lines, num_attr, max_attr_size, lines); - int max_time = get_max_time(lines, num_lines, num_attr, max_attr_size); - + struct timespec start_time, stop_time; + + // test all ; print all + if (key == NULL) { + char file_name[10]; + + sprintf(file_name, "Out%d.txt", rank); + FILE *file = fopen(file_name, "w"); + for (size_t k = 0; k < num_lines; ++k) { + char *trip_name = get_word(lines, k, TRIP_ID, num_attr, + max_attr_size); + clock_gettime(CLOCK_MONOTONIC, &start_time); + // int res = get_max_time(lines, num_lines, num_attr, + // max_attr_size); + size_t res = search_key(lines, num_lines, num_attr, + max_attr_size, trip_name); + + clock_gettime(CLOCK_MONOTONIC, &stop_time); + + if (!res) + continue; // dont print if err + time = (stop_time.tv_sec - start_time.tv_sec) * + 100000000 + + (stop_time.tv_nsec - start_time.tv_nsec); + fprintf(file, "%s:%lu\n", trip_name, time); + } + fclose(file); + // just so we dont lock + MPI_Send(&time, 1, MPI_UNSIGNED_LONG, 0, 0, MPI_COMM_WORLD); + } else { // search for key + clock_gettime(CLOCK_MONOTONIC, &start_time); + // int res = get_max_time(lines, num_lines, num_attr, + // max_attr_size); + res = search_key(lines, num_lines, num_attr, max_attr_size, + key); + + clock_gettime(CLOCK_MONOTONIC, &stop_time); + + time = (stop_time.tv_sec - start_time.tv_sec) * 100000000 + + (stop_time.tv_nsec - start_time.tv_nsec); + if (res) + MPI_Send(&time, 1, MPI_UNSIGNED_LONG, 0, 0, + MPI_COMM_WORLD); + else + MPI_Send(&res, 1, MPI_UNSIGNED_LONG, 0, 0, + MPI_COMM_WORLD); + } free(lines); free(chunk); - return max_time; + return res; } // help from https://stackoverflow.com/questions/12939279/mpi-reading-from-a-text-file // is hosted under a permissive licence, ty Jonathan Dursi :) int -parprocess(MPI_File *in, const int rank, const int size) +parprocess(MPI_File *in, const int rank, const int size, char *key) { // reads revelant lines from file to chunk. // IN OUR CASE we will use overlap to reach EOF of this line. @@ -236,7 +293,6 @@ parprocess(MPI_File *in, const int rank, const int size) /* allocate memory, filled with 0 */ chunk = calloc(1, total_size); - printf("[PARPROCESS] begin\n"); ssize_t err; { @@ -253,7 +309,8 @@ parprocess(MPI_File *in, const int rank, const int size) } } - + // eh commenting this out, at worst we'll have one unusable line, but this + // still works w/ padding // fills the first incoherent bytes with \0 //size_t k = 0; //if (rank != 0) { // first has no incoherece at begining @@ -277,8 +334,7 @@ parprocess(MPI_File *in, const int rank, const int size) //} //chunk[total_size_overlap] = '\0'; // just to be sure! - int max = do_map(chunk, total_size); - printf("[PARPROCESS] end, max: %d\n", max); + int max = do_map(chunk, total_size, key, rank); return max; } From c3ba8068d76a492367725e2cd56e78071fbf7632 Mon Sep 17 00:00:00 2001 From: violette Date: Wed, 10 Apr 2024 21:58:01 -0400 Subject: [PATCH 4/5] OpenMP: done --- CMakeLists.txt | 6 +- src/main.c | 183 ++++++++++++++----------------------------------- 2 files changed, 55 insertions(+), 134 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index df55db3..896abec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,9 +14,9 @@ set(src set(CMAKE_DEBUG_POSTFIX d) add_executable(ift630_sts3 ${src}) -find_package(MPI) #make it REQUIRED, if you want -include_directories(SYSTEM ${MPI_INCLUDE_PATH}) -target_link_libraries(ift630_sts3 ${MPI_C_LIBRARIES}) +find_package(OpenMP) #make it REQUIRED, if you want +include_directories(SYSTEM ${OpenMP_INCLUDE_PATH}) +target_link_libraries(ift630_sts3 ${OpenMP_C_LIBRARIES}) set_target_properties(ift630_sts3 PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) target_compile_features(ift630_sts3 PRIVATE c_std_99) diff --git a/src/main.c b/src/main.c index 3b0e6da..a768773 100644 --- a/src/main.c +++ b/src/main.c @@ -1,7 +1,7 @@ #define _XOPEN_SOURCE 700 #include -#include +#include #include #include #include @@ -26,59 +26,36 @@ enum HEADER { #define STOP_FILE "./stop_times.txt" // adding key here! -int do_map(char *, size_t, char *, int); +int do_map(char *, size_t); void do_reduce(size_t); -int parprocess(MPI_File *, const int, const int, char *); +int parprocess(char *, size_t); int main(int argc, char **argv) { - // Initialize the MPI environment - MPI_Init(NULL, NULL); - - int world_size, world_rank; - MPI_Comm_size(MPI_COMM_WORLD, &world_size); - MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); - - char *key; - if (argc != 2) { - if (world_rank == 0) - printf("no args ; indexing all\n"); - key = NULL; - } - else - key = argv[2]; - size_t res = 0; - int number, size; ssize_t err; - MPI_File in; + FILE *in = fopen("./stop_times.txt", "rb"); - if ((err = MPI_File_open(MPI_COMM_WORLD, STOP_FILE, MPI_MODE_RDONLY, - MPI_INFO_NULL, &in))) { - fprintf(stderr, "%s: Couldn't open file %s\n", argv[0], - STOP_FILE); - exit(-1); - } - MPI_Comm_size(MPI_COMM_WORLD, &size); - - res = parprocess(&in, world_rank, world_size, key); - - if (world_rank == 0) { - for (char k = 0; k < world_size; k++) { - MPI_Recv(&res, 1, MPI_UNSIGNED_LONG, MPI_ANY_SOURCE, 0, - MPI_COMM_WORLD, MPI_STATUS_IGNORE); - if (res) { - //printf("res: found in %lu ns\n", res); break; - } - else - printf("res: not found\n"); - } + if (!in) { + fputs("File error", stderr); + exit(1); } - MPI_File_close(&in); - MPI_Finalize(); + fseek(in, 0, SEEK_END); + int file_size = ftell(in); + rewind(in); + + char *buffer = (char *)malloc(sizeof(char) * file_size); + // copy the file into the buffer: + fread(buffer, 1, file_size, in); + + omp_set_num_threads(16); + //do_map(buffer, file_size); + parprocess(buffer, file_size); + + fclose(in); exit(0); } @@ -192,7 +169,7 @@ get_max_time(char *lines, size_t num_lines, size_t num_attr, size_t max_attr) } int -do_map(char *chunk, size_t num_char, char *key, int rank) +do_map(char *chunk, size_t num_char) { size_t num_lines = 0, num_attr = 0; size_t max_attr_size = 0; @@ -211,10 +188,8 @@ do_map(char *chunk, size_t num_char, char *key, int rank) struct timespec start_time, stop_time; // test all ; print all - if (key == NULL) { - char file_name[10]; + char file_name[] = "Out.txt"; - sprintf(file_name, "Out%d.txt", rank); FILE *file = fopen(file_name, "w"); for (size_t k = 0; k < num_lines; ++k) { char *trip_name = get_word(lines, k, TRIP_ID, num_attr, @@ -236,25 +211,7 @@ do_map(char *chunk, size_t num_char, char *key, int rank) } fclose(file); // just so we dont lock - MPI_Send(&time, 1, MPI_UNSIGNED_LONG, 0, 0, MPI_COMM_WORLD); - } else { // search for key - clock_gettime(CLOCK_MONOTONIC, &start_time); - // int res = get_max_time(lines, num_lines, num_attr, - // max_attr_size); - res = search_key(lines, num_lines, num_attr, max_attr_size, - key); - clock_gettime(CLOCK_MONOTONIC, &stop_time); - - time = (stop_time.tv_sec - start_time.tv_sec) * 100000000 + - (stop_time.tv_nsec - start_time.tv_nsec); - if (res) - MPI_Send(&time, 1, MPI_UNSIGNED_LONG, 0, 0, - MPI_COMM_WORLD); - else - MPI_Send(&res, 1, MPI_UNSIGNED_LONG, 0, 0, - MPI_COMM_WORLD); - } free(lines); free(chunk); return res; @@ -263,78 +220,42 @@ do_map(char *chunk, size_t num_char, char *key, int rank) // help from https://stackoverflow.com/questions/12939279/mpi-reading-from-a-text-file // is hosted under a permissive licence, ty Jonathan Dursi :) int -parprocess(MPI_File *in, const int rank, const int size, char *key) +parprocess(char *buff, size_t file_size) { // reads revelant lines from file to chunk. // IN OUR CASE we will use overlap to reach EOF of this line. // Duplicates dont matter in our case ; res will be the same either way. size_t proc_size, total_size, total_size_overlap; - char *chunk; - MPI_Offset globalstart; - MPI_Offset globalend; - MPI_Offset filesize; + size_t start; + size_t end; - MPI_File_get_size(*in, &filesize); - filesize--; /* get rid of text file eof */ - proc_size = filesize / size; - globalstart = rank * proc_size; - globalend = globalstart + proc_size - 1; - if (rank == size - 1) - globalend = filesize - 1; - - /* add overlap to the end of everyone's chunk except last - * proc... */ - size_t globalend_overlap = globalend; - if (rank != size - 1) - globalend_overlap += OVERLAP; - - total_size_overlap = globalend_overlap - globalstart + 1; - total_size = globalend - globalstart + 1; - - /* allocate memory, filled with 0 */ - chunk = calloc(1, total_size); - - ssize_t err; + #pragma omp parallel { - err = MPI_File_read_at_all_begin(*in, globalstart, chunk, - total_size, MPI_CHAR); - if (err) { - printf("error %lu\n", err); - MPI_Finalize(); - } - err = MPI_File_read_at_all_end(*in, chunk, MPI_STATUS_IGNORE); - if (err) { - printf("error %lu\n", err); - MPI_Finalize(); - } + size_t size = omp_get_num_threads(); + size_t rank = omp_get_thread_num(); + + printf("%lu / %lu\n", rank, size); + proc_size = file_size / size; + start = rank * proc_size; + end = start + proc_size - 1; + if (rank == size - 1) + end = file_size - 1; + + /* add overlap to the end of everyone's chunk except last + * proc... */ + size_t end_overlap = end; + if (rank != size - 1) + end_overlap += OVERLAP; + + total_size_overlap = end_overlap - start + 1; + total_size = end - start + 1; + + /* allocate memory, filled with 0 */ + char *chunk = calloc(1, total_size); + memcpy(chunk, buff + start, proc_size); + + ssize_t err; + int max = do_map(chunk, total_size); } - - // eh commenting this out, at worst we'll have one unusable line, but this - // still works w/ padding - // fills the first incoherent bytes with \0 - //size_t k = 0; - //if (rank != 0) { // first has no incoherece at begining - // for (; chunk[k] != '\r' && chunk[k] != '\n'; - // ++k) // get number of incoherent bytes :) - // ; - // // reset - - // memmove(chunk, chunk + k, total_size); // - 2: dont count \n\r - //} - - // fill char after next EOL wiht \0 ; starting from proc_size to end of - // overlap - //if (rank != size) { // last doesnt have padding, dont check it - // for (; (chunk[globalend] != '\n' && chunk[globalend] != '\r') && - // globalend < globalend_overlap; - // ++globalend) - // ; - // memset(chunk + globalend, '\0', OVERLAP); - // // - //} - //chunk[total_size_overlap] = '\0'; // just to be sure! - - int max = do_map(chunk, total_size, key, rank); - - return max; + return 0; } From 227b5053b4c35a76d24e8babaf809fcce1d6ee89 Mon Sep 17 00:00:00 2001 From: violette Date: Wed, 10 Apr 2024 23:29:23 -0400 Subject: [PATCH 5/5] makefile + pdf --- CMakeLists.txt | 22 --- Makefile | 31 ++++ README.md | 1 + logo.png | Bin 0 -> 19302 bytes main.pdf | Bin 0 -> 74867 bytes main.typ | 76 +++++++++ src/mpi.c | 340 +++++++++++++++++++++++++++++++++++++++ src/{main.c => openmp.c} | 10 +- 8 files changed, 454 insertions(+), 26 deletions(-) delete mode 100644 CMakeLists.txt create mode 100644 Makefile create mode 100644 logo.png create mode 100644 main.pdf create mode 100644 main.typ create mode 100644 src/mpi.c rename src/{main.c => openmp.c} (96%) diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 896abec..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -cmake_minimum_required(VERSION 3.14) - -project( - ift630_sts3 - VERSION 0.1.0 - DESCRIPTION "bs project to learn openMPI / openMP" - LANGUAGES C -) - -set(src - src/main.c - ) - -set(CMAKE_DEBUG_POSTFIX d) -add_executable(ift630_sts3 ${src}) - -find_package(OpenMP) #make it REQUIRED, if you want -include_directories(SYSTEM ${OpenMP_INCLUDE_PATH}) -target_link_libraries(ift630_sts3 ${OpenMP_C_LIBRARIES}) - -set_target_properties(ift630_sts3 PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) -target_compile_features(ift630_sts3 PRIVATE c_std_99) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4190ab7 --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +.PHONY: all omp mpi runmpi runomp + +SRCMPI = src/mpi.c +SRCOMP = src/openmp.c +OJB = $(SRC:.c=.o) +OUT = build + +CC = /usr/bin/gcc +MPICC = /usr/bin/mpicc +MPIRUN = /usr/bin/mpirun +CFLAGS = -ansi -Wall -std=c99 -O3 +OMP = -fopenmp +RM = /bin/rm -fr + +all: mpi openmp + cp ./stop_times.txt build + +runmpi: mpi + cd build ; $(MPIRUN) -np 8 ./mpi + +runomp: omp + cd build ; ./omp + +mpi: + $(MPICC) $(SRCMPI) $(CFLAGS) -o $(OUT)/mpi + +omp: + $(CC) $(SRCOMP) $(OMP) -o $(OUT)/omp + +clean: + $(RM) $(OUT)/* diff --git a/README.md b/README.md index 8a92ccd..9bbea72 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # ift630_sts3 +[see typst doc](./main.pdf) diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6a6f919f43c8ab860e1cdeb703170e675c8e8e8a GIT binary patch literal 19302 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 literal 0 HcmV?d00001 diff --git a/main.typ b/main.typ new file mode 100644 index 0000000..c79c79f --- /dev/null +++ b/main.typ @@ -0,0 +1,76 @@ +#set page( + numbering: "1 / 1", + header: [ + #set text(8pt) + _IFT630 #h(1fr) Violette Paulin_ + ], +) + +#let title(content) = { + pagebreak(weak:true) + set text(size:17pt, weight: "bold") + set align(center) + v(70pt) + [#content] + v(50pt) +} + +#set par( + first-line-indent: 1em, + justify: true, +) + +#title[ + IFT630 - Projet #3 +] + +#show outline.entry.where( + level: 1 +): it => { + v(14pt, weak: false) + strong(it) +} + + +#v(20pt) + +#image("logo.png") + +#align(center)[ + #text(size: 15pt)[ + Violette PAULIN – PAUM1202\ + _Violette.Paulin\@USherbrooke.ca_\ \ + ] +] + +#v(20pt) + +#pagebreak() += Build et test +Une fois le dossier `build` créé, on peut build directement en exécutant +`make all`. Pour tester MPI, `make runmpi`. Pour tester OpenMP, `make runomp`. +Ces commandes créent des fichiers `OutX.txt`. Ceux-ci montrent la clef testée à +gauche, et le temps pour la trouver à droite, séparée par un ':'. + += Performance +Je ne comprends pas la question de mesure de performance. Dans mon cas, mesurer +les performances revient à mesurer la totalité du temps écoulé, ce qui va +comprendre une part non négligeable d'allocation, ainsi que l'overhead des +différentes librairies. Alors que si je mesure le temps moyen pour trouver une +clef, je mesurerais la même chose dans les deux cas. Il m'est donc impossible de +pouvoir tirer une conclusion satisfaisante sur les performances, pour mon +implémentation. J'ai quand même fait le choix de mesurer le temps pour trouver +une clef, sans prendre en compte l'allocation. + +Cependant, OpenMP peut marcher sur tous les cœurs logiques (thread) de ma +machine, alors que MPI ne fonctionne qu'avec les cœurs physiques (il me semble). +Ainsi, on gagne théoriquement en temps global avec OpenMP. + +De plus, je n'ai pas utilisé OpenMP de la manière la plus optimale, faute de +temps. A la place de diviser sur une boucle, je l'ai divisé comme je l'ai fait +pour MPI. Ceci coute plus de temps en allocation, et l'on est contraint à +utiliser plusieurs fichiers + +Dans les deux cas, on s'aperçoit que plus une clef est loin, plus elle est difficile à charger. +C'est le comportement attendu. De plus, les temps semblent être linéaire, ce +qui est encore une fois attendu. diff --git a/src/mpi.c b/src/mpi.c new file mode 100644 index 0000000..3b0e6da --- /dev/null +++ b/src/mpi.c @@ -0,0 +1,340 @@ +#define _XOPEN_SOURCE 700 +#include + +#include +#include +#include +#include +#include + +enum HEADER { + TRIP_ID = 0, + ARRIVAL_TIME, + DEPARTURE_TIME, + STOP_ID, + STOP_SEQUENCE, + STOP_HEADSIGN, + PICKUP_TYPE, + DROP_OFF_TYPE, + SHAPE_DIST_TRAVELED, + TIMEPOINT, + END +}; + +#define DELIM ',' +#define OVERLAP 100 +#define STOP_FILE "./stop_times.txt" + +// adding key here! +int do_map(char *, size_t, char *, int); +void do_reduce(size_t); +int parprocess(MPI_File *, const int, const int, char *); + +int +main(int argc, char **argv) +{ + // Initialize the MPI environment + MPI_Init(NULL, NULL); + + int world_size, world_rank; + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); + + char *key; + if (argc != 2) { + if (world_rank == 0) + printf("no args ; indexing all\n"); + key = NULL; + } + else + key = argv[2]; + + size_t res = 0; + + int number, size; + ssize_t err; + MPI_File in; + + if ((err = MPI_File_open(MPI_COMM_WORLD, STOP_FILE, MPI_MODE_RDONLY, + MPI_INFO_NULL, &in))) { + fprintf(stderr, "%s: Couldn't open file %s\n", argv[0], + STOP_FILE); + exit(-1); + } + MPI_Comm_size(MPI_COMM_WORLD, &size); + + res = parprocess(&in, world_rank, world_size, key); + + if (world_rank == 0) { + for (char k = 0; k < world_size; k++) { + MPI_Recv(&res, 1, MPI_UNSIGNED_LONG, MPI_ANY_SOURCE, 0, + MPI_COMM_WORLD, MPI_STATUS_IGNORE); + if (res) { + //printf("res: found in %lu ns\n", res); break; + } + else + printf("res: not found\n"); + } + } + + MPI_File_close(&in); + MPI_Finalize(); + exit(0); +} + +time_t +substr_time(struct tm a, struct tm b) +{ + return (a.tm_hour * 3600 + a.tm_min * 60 + a.tm_sec) - + (b.tm_hour * 3600 + b.tm_min * 60 + b.tm_sec); +} + +char * +get_word(char *lines, size_t x, size_t y, size_t num_attr, size_t max_attr) +{ + size_t offset = (x * (num_attr * max_attr) + y * (max_attr)); + return lines + offset; +} + +void +fill_lines(char *chunk, size_t num_char, size_t num_lines, size_t num_attr, + size_t max_attr, char *lines) +{ + size_t attr_pos = 0, line_pos = 0, word_pos = 0; + for (size_t k = 0; k < num_char; ++k) { + if (chunk[k] == DELIM) { + // go to next attrib + char *word = get_word(lines, line_pos, attr_pos, + num_attr, max_attr); + memcpy(word, chunk + k - word_pos, word_pos); + word[word_pos] = '\0'; + ++attr_pos; + word_pos = 0; + } + else if (chunk[k] == '\n') { + ++line_pos; + attr_pos = 0; + word_pos = 0; + } + else if (chunk[k] == '\r') {} + else { + ++word_pos; + } + } +} + +void +get_lines_info(char *chunk, size_t num_char, size_t *max_attr_size, + size_t *num_lines) +{ + // count max line size and number of lines + size_t current_attr_size = 0; + for (int k = 0; k < num_char; ++k) { + // LINE + if (chunk[k] == '\n' || chunk[k] == '\r') + ++(*num_lines); + // ATTRIBUTES + if (chunk[k] == DELIM) { + if (current_attr_size > *max_attr_size) + *max_attr_size = current_attr_size; + current_attr_size = 0; + } else + ++current_attr_size; + } +} + +// get num of cols of csv +size_t +get_num_attr(char *chunk) +{ + size_t num_attr = 0; + for (size_t k = 0 ; chunk[k] != '\n' ; ++k) + if (chunk[k] == DELIM) + ++num_attr; + return num_attr; +} + +int +search_key(char *lines, size_t num_lines, size_t num_attr, size_t max_attr, + char *key) +{ + for (size_t k = 0; k < num_lines; ++k) { + if (!strcmp(get_word(lines, k, TRIP_ID, num_attr, max_attr), + key)) + return 1; + } + return 0; +} + +int +get_max_time(char *lines, size_t num_lines, size_t num_attr, size_t max_attr) +{ + struct tm dep_time, arr_time; + time_t max_time = 0; + for (size_t k = 0; k < num_lines; ++k) { + memcpy(&dep_time, + get_word(lines, k, DEPARTURE_TIME, num_attr, max_attr), + sizeof(struct tm)); + memcpy(&arr_time, + get_word(lines, k, ARRIVAL_TIME, num_attr, max_attr), + sizeof(struct tm)); + + strptime(get_word(lines, k, DEPARTURE_TIME, num_attr, max_attr), + "%H:%M:%S", &dep_time); + + strptime(get_word(lines, k, ARRIVAL_TIME, num_attr, max_attr), + "%H:%M:%S", &arr_time); + time_t tmp = substr_time(arr_time, dep_time); + if (tmp > max_time) + max_time = tmp; + } + return max_time; +} + +int +do_map(char *chunk, size_t num_char, char *key, int rank) +{ + size_t num_lines = 0, num_attr = 0; + size_t max_attr_size = 0; + size_t time, res; + + get_lines_info(chunk, num_char, &max_attr_size, &num_lines); + num_attr = get_num_attr(chunk); + + // allocate lines (just a big continuous chunk) + // is a 2d arr of char* + char *lines; + lines = calloc(1, num_lines * num_attr * max_attr_size); + + fill_lines(chunk, num_char, num_lines, num_attr, max_attr_size, lines); + + struct timespec start_time, stop_time; + + // test all ; print all + if (key == NULL) { + char file_name[10]; + + sprintf(file_name, "Out%d.txt", rank); + FILE *file = fopen(file_name, "w"); + for (size_t k = 0; k < num_lines; ++k) { + char *trip_name = get_word(lines, k, TRIP_ID, num_attr, + max_attr_size); + clock_gettime(CLOCK_MONOTONIC, &start_time); + // int res = get_max_time(lines, num_lines, num_attr, + // max_attr_size); + size_t res = search_key(lines, num_lines, num_attr, + max_attr_size, trip_name); + + clock_gettime(CLOCK_MONOTONIC, &stop_time); + + if (!res) + continue; // dont print if err + time = (stop_time.tv_sec - start_time.tv_sec) * + 100000000 + + (stop_time.tv_nsec - start_time.tv_nsec); + fprintf(file, "%s:%lu\n", trip_name, time); + } + fclose(file); + // just so we dont lock + MPI_Send(&time, 1, MPI_UNSIGNED_LONG, 0, 0, MPI_COMM_WORLD); + } else { // search for key + clock_gettime(CLOCK_MONOTONIC, &start_time); + // int res = get_max_time(lines, num_lines, num_attr, + // max_attr_size); + res = search_key(lines, num_lines, num_attr, max_attr_size, + key); + + clock_gettime(CLOCK_MONOTONIC, &stop_time); + + time = (stop_time.tv_sec - start_time.tv_sec) * 100000000 + + (stop_time.tv_nsec - start_time.tv_nsec); + if (res) + MPI_Send(&time, 1, MPI_UNSIGNED_LONG, 0, 0, + MPI_COMM_WORLD); + else + MPI_Send(&res, 1, MPI_UNSIGNED_LONG, 0, 0, + MPI_COMM_WORLD); + } + free(lines); + free(chunk); + return res; +} + +// help from https://stackoverflow.com/questions/12939279/mpi-reading-from-a-text-file +// is hosted under a permissive licence, ty Jonathan Dursi :) +int +parprocess(MPI_File *in, const int rank, const int size, char *key) +{ + // reads revelant lines from file to chunk. + // IN OUR CASE we will use overlap to reach EOF of this line. + // Duplicates dont matter in our case ; res will be the same either way. + size_t proc_size, total_size, total_size_overlap; + char *chunk; + MPI_Offset globalstart; + MPI_Offset globalend; + MPI_Offset filesize; + + MPI_File_get_size(*in, &filesize); + filesize--; /* get rid of text file eof */ + proc_size = filesize / size; + globalstart = rank * proc_size; + globalend = globalstart + proc_size - 1; + if (rank == size - 1) + globalend = filesize - 1; + + /* add overlap to the end of everyone's chunk except last + * proc... */ + size_t globalend_overlap = globalend; + if (rank != size - 1) + globalend_overlap += OVERLAP; + + total_size_overlap = globalend_overlap - globalstart + 1; + total_size = globalend - globalstart + 1; + + /* allocate memory, filled with 0 */ + chunk = calloc(1, total_size); + + ssize_t err; + { + err = MPI_File_read_at_all_begin(*in, globalstart, chunk, + total_size, MPI_CHAR); + if (err) { + printf("error %lu\n", err); + MPI_Finalize(); + } + err = MPI_File_read_at_all_end(*in, chunk, MPI_STATUS_IGNORE); + if (err) { + printf("error %lu\n", err); + MPI_Finalize(); + } + } + + // eh commenting this out, at worst we'll have one unusable line, but this + // still works w/ padding + // fills the first incoherent bytes with \0 + //size_t k = 0; + //if (rank != 0) { // first has no incoherece at begining + // for (; chunk[k] != '\r' && chunk[k] != '\n'; + // ++k) // get number of incoherent bytes :) + // ; + // // reset + + // memmove(chunk, chunk + k, total_size); // - 2: dont count \n\r + //} + + // fill char after next EOL wiht \0 ; starting from proc_size to end of + // overlap + //if (rank != size) { // last doesnt have padding, dont check it + // for (; (chunk[globalend] != '\n' && chunk[globalend] != '\r') && + // globalend < globalend_overlap; + // ++globalend) + // ; + // memset(chunk + globalend, '\0', OVERLAP); + // // + //} + //chunk[total_size_overlap] = '\0'; // just to be sure! + + int max = do_map(chunk, total_size, key, rank); + + return max; +} diff --git a/src/main.c b/src/openmp.c similarity index 96% rename from src/main.c rename to src/openmp.c index a768773..d104d5a 100644 --- a/src/main.c +++ b/src/openmp.c @@ -26,7 +26,7 @@ enum HEADER { #define STOP_FILE "./stop_times.txt" // adding key here! -int do_map(char *, size_t); +int do_map(char *, size_t, int); void do_reduce(size_t); int parprocess(char *, size_t); @@ -169,7 +169,7 @@ get_max_time(char *lines, size_t num_lines, size_t num_attr, size_t max_attr) } int -do_map(char *chunk, size_t num_char) +do_map(char *chunk, size_t num_char, int rank) { size_t num_lines = 0, num_attr = 0; size_t max_attr_size = 0; @@ -188,7 +188,9 @@ do_map(char *chunk, size_t num_char) struct timespec start_time, stop_time; // test all ; print all - char file_name[] = "Out.txt"; + char file_name[] = "OutXX.txt"; + sprintf(file_name, "Out%d.txt", rank); + FILE *file = fopen(file_name, "w"); for (size_t k = 0; k < num_lines; ++k) { @@ -255,7 +257,7 @@ parprocess(char *buff, size_t file_size) memcpy(chunk, buff + start, proc_size); ssize_t err; - int max = do_map(chunk, total_size); + int max = do_map(chunk, total_size, rank); } return 0; }