From ff5baf92140ccfbb467cf323754a5dc7781e4e27 Mon Sep 17 00:00:00 2001 From: Yan-Hua Date: Mon, 23 Jun 2025 22:25:09 -0400 Subject: [PATCH] initial commit --- pthread_qs.c | 769 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 769 insertions(+) create mode 100644 pthread_qs.c diff --git a/pthread_qs.c b/pthread_qs.c new file mode 100644 index 0000000..1e43b73 --- /dev/null +++ b/pthread_qs.c @@ -0,0 +1,769 @@ +/* + * Project 2: Multithreaded Hybrid Sort + * Author: Alexander Wong + * Date: 03/07/22 (MM/DD/YY) + * Description: The purpose of this code is to implement POSIX-style Pthreads to multi-thread Quicksort for sorting huge lists. This will be accomplished + * by using various strategies such as using a hybrid sorting approach, a combination of quicksort and insertion/ shell sort, and various command + * line arguments that a user may specify, such as the size, threshold, alternate sorting algorithm, seed, multi-threading, pieces, maxthreads, and + * pivoting strategy, median-of-three. For more information see help() or the main method argument parser. For more on how this code functions, see + * the function comments or descriptive comments included in the code. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * This is a simple data structure that is used to hold the + * start and end, or low (lo) and high (hi), segment of the + * sequence array. The size is calculated based on hi - lo + * and represents the size of the segment. + */ +struct pTuple { + int hi; + int lo; + int size; +}; + +/* + * This data structure is used to organize the arguments into + * one structure. The reason for this is to avoid potential + * collisions in local variable names and to create a configuration + * structure that is much more defined than setting a global variable + * by itself. + */ +struct arg_conf { + uint32_t size; // The size of the array to generate + int seed ; // Used in determining randomness + uint32_t pieces; // Number of pieces, or segments, to partition + uint32_t threshold; // Limit used to switch to the alternate sorting algorithm + uint32_t maxthreads; // Specified number of threads to run + bool median3; // Determines whether to use median-of-three for pivot strategy + char alternate; // Specifies the alternate sorting algorithm (insertion or shell) + bool multithreaded; // Used to determine whether to multithread or not +}; + +struct arg_conf config; // Initialize a global config + + +uint32_t *sequence; // Global array used in sorting +struct pTuple *partitions; // Partitions based off pieces representing a part of the array sequence + +regex_t positive_integer; // Regex to ensure input is a positive integer +regex_t yes_no_re; // Regex to lint boolean Y/n inputs +regex_t sort_flags_re; // Regex to lint sorting S/i inputs + +// Timer variables used in metric gathering +struct timeval start, start_tr, stop, stop_tr; +clock_t start_c, end_c; +float wlclk_tdiff, tr_tdiff; +double cpu_tdiff; + +void shuffle() { + /* + * This function is used to randomize the sequence array in order to + * prepare it for sorting. If the seed value is set to 0 or some number + * larger, then that value is used as the seed. If the seed value is -1, + * the CPU clock will be used instead. If the seed value is -2, nothing + * happens and the value is determined when rand() is called. + */ + int tmp; + if (config.seed >= 0) { + srand(config.seed); // Set the user specified seed + } else if (config.seed = -1) { + srand(clock()); // Set the CPU clock as the seed + } else { + + } + int cur; + for (int i = 0; i < config.size - 1; i++) { + // This for loop randomizes by swapping two positions + // of the array as we increment over the array + cur = rand() % config.size; + tmp = sequence[i]; + sequence[i] = sequence[cur]; + sequence[cur] = tmp; + } +} + +void *hybridsort(void *param) { + /* + * This is a hybrid sort function combining three different algorithms, two of which + * are combined to make an alternate sort option. The three algorithms used to sort + * include, Quicksort, Insertion Sort, and Shell Sort. Upon calling hybridsort() with + * paramater param, the parameter, which is expected to be of type pTuple is set to + * the cursor, or current working, partition. The distance between the low and high end + * is used to represent the size of the segment. The number of elements is not used + * since we consider 1 element to be no sortable elements, thus 0. This is a design choice + * that has a relatively small, but noteable impact when it comes to how the function handles + * specific cases or when to add one to the hi value. + * + * Cases: + * 1) When size is 1, check and swap (if needed) the two elements indexed by lo and hi and return + * 2) When the threshold is 0, we should default to quick sort + * 3) When the partitions size is less than the threshold, use the alternate sorting algorithm + */ + struct pTuple *cur_part = param; + struct pTuple lo_part; + struct pTuple hi_part; + + if(cur_part->size == 1) { + /* + * This if statement checks to see if the indexes stored in + * lo and hi need to be swapped. If they do need to be swapped + * this function will swap them, if not it will not. Once the + * operation is completed, we can return as there is no need to + * use any sorting algorithms. + */ + if (sequence[cur_part->lo] > sequence[cur_part->hi]) { + int tmp = cur_part->lo; + cur_part->hi = cur_part->lo; + cur_part->lo = tmp; + } + return NULL; + } + + if (config.threshold != 0 && cur_part->size <= config.threshold) { + /* + * This is the alternate sorting algorithm which is a combination + * of shell sort and insertion sort. The default case is set to be + * shell sort, however, this can be changed by specifying the alternate + * sorting algorithm in the config using -a. + */ + if(cur_part->size == 0) return 0; // This returns if the current distance is 0 (1 element) + if(config.alternate == 'I' || config.alternate == 'i') { + /* + * This is a check to see if the config is set to use + * insertion sort. If insertion sort is used, the partition + * will be sorted one element at a time until it is finished. + */ + for(int i = cur_part->lo; i < cur_part->hi + 1; i++) { + int tmp = sequence[i]; + int j = i - 1; + while (j >= 0 && sequence[j] > tmp) { + sequence[j + 1] = sequence[j]; + j = j - 1; + } + sequence[j + 1] = tmp; + } + } else { + /* + * This is the default case which is shell sort using Hibbard's + * implementation. This sort works by determining a gap size + * and then sorting element that are spread out, based on the gap size. + * It starts with the elements that are considered to be far apart and then + * progressively gets smaller as the loop continues. + */ + int k = 1; while(k <= cur_part->size) k *= 2; k = (k / 2) - 1; // Determine the gap size + if(cur_part->size != 0) { + // If the size is 0, we don't need to execute. + do { + for(int i = cur_part->lo; i < (cur_part->hi + 1 - k); i++) { + /* + * This loop uses the partitions lo and hi value rather than 0 and size + * since we are only looking to operate on the segment that was passed + * into hybridsort(). + */ + for(int j = i; j >= cur_part->lo; j -= k) { + if(sequence[j] <= sequence[j + k]) { + break; + } else { + int tmp = sequence[j]; + sequence[j] = sequence[j + k]; + sequence[j + k] = tmp; + } + } + } + k /= 2; // Reduce the gap + } while(k > 0); // When the gap is 0 or less than 0 we've finished sorting. + } + } + } else { + /* + * This is the shell sort function. In comparison to traditional shell sort, + * there is no difference in how it operates. It will take a partition, split it into + * two more partitions, referred to as left and right, and then pass those into + * quicksort recursively. + */ + if (cur_part->lo < cur_part->hi) { + /* + * If the current part lo is less than hi, we're ready to begin + * quick sorting, if that's not the case, we return and let the + * main function handle the issue. + */ + int lo = cur_part->lo; + int hi = cur_part->hi; + int i = lo; + int j = hi + 1; + int pivot = sequence[lo]; + + if(config.median3) { + /* + * This statement is used to change the pivoting strategy from using the left most (lo) + * value of the partition to median-of-three. This function works by calculating the mid + * value and then determining based off the cases betlow which value is the closest to the + * median. This is to help produce more even splits, however, even splits are not guaranteed. + */ + int mid = (lo + hi) / 2; // Calculate the middle value of the partition + int lo_val = sequence[lo]; + int med_val = sequence[mid]; + int hi_val = sequence[hi]; + int median; + if ((lo_val >= med_val && lo_val <= hi_val) || (lo_val >= hi_val && lo_val <= med_val)) median = lo; + // Checks to see if the value at index lo meets median conditions + else if ((med_val >= lo_val && med_val <= hi_val) || (med_val >= hi_val && med_val <= lo_val)) median = mid; + // Checks to see if the value at index mid meets median conditions + else median = hi; + // Checks to see if the value at index hi meets median conditions + + // Swap the new median-of-three calculated pivot with the old left-most pivot + int tmp = sequence[median]; + sequence[median] = sequence[lo]; + sequence[lo] = tmp; + pivot = sequence[lo]; + } + + /* + * This do loop iterates until the sequence value of the lower (left) end interator and + * higher (right) end iterator of the segment passes the pivot. This + * determines the left and right partitions and will swap values + * when the left iterator is less than the right iterator. This will + * keep happening until i is not less than j for which the do while loop + * will exit. + */ + do { + do i++; while (sequence[i] < pivot); + do j--; while (sequence[j] > pivot); + if (i < j) { + int temp = sequence[i]; + sequence[i] = sequence[j]; + sequence[j] = temp; + } else break; + } while(true); + + // Swaps the pivot location + sequence[lo] = sequence[j]; + sequence[j] = pivot; + + int left_size = j - lo; + int right_size = hi - j; + + /* + * Unlike traditional quick sort, we pass a data structure containing the end + * points of the partition. This is simply to remain consistent with the data + * structure we built to retain our master partitions prior to calling hybridsort(). + */ + if ((left_size < right_size)) { + // If the left side is smaller than the right side, sort the left side first, then right. + lo_part.lo = lo; + lo_part.hi = j-1; + lo_part.size = left_size; + hybridsort(&lo_part); + hi_part.lo = j+1; + hi_part.hi = hi; + hi_part.size = right_size; + hybridsort(&hi_part); + } else { + // If the right side is smaller than the left side, sort the right side first, then left. + hi_part.lo = j+1; + hi_part.hi = hi; + hi_part.size = right_size; + hybridsort(&hi_part); + lo_part.lo = lo; + lo_part.hi = j-1; + lo_part.size = left_size; + hybridsort(&lo_part); + } + } + } + return NULL; +} + +void multithread() { + /* + * The purpose of this multi-thread function is to pass the partitions that were + * created in to the hybridsort() function. Becuse there may be more pieces than + * threads, this function will poll and use logic to determine when it is safe to + * create a new thread with that partition. Since we cannot spawn an unlimited amount + * of threads, we have to poll for an available one. Once we have exhausted our pieces + * and all threads have finished running, we can stop multi-threading. + * + * This multi-thread code was broken out to a function since + * we need to break out of the polling loop. By using a function + * breaking out of the polling loop is much easier and removes another + * layer of complexity. This is because the multithread() function creates + * a boolean array that holds a true or false value to help indicate whether + * a thread is running. The reason we use an array for this is because we need + * a way to track and be aware of all active and inactive threads. A counter is + * a possible solution, however, this implementation is much more explicit. + * The threads and their attributes are allocated, but not initialzied, + * in arrays. This is because we need to dynamically create them as the user may + * specify 1, 2, 3, or 4, referred to in the config as maxthreads. + */ + pthread_t *tids = (pthread_t *)malloc(config.maxthreads * sizeof(pthread_t)); // Allocate and create an array of thread ids + pthread_attr_t tattrs[config.maxthreads]; // Initialize an array of thread attributes + bool *thread_status = (bool *)malloc(config.maxthreads * sizeof(bool)); // Allocate and create an array of boolean status + + int part_id = 0; // Partition starting index + int tid = 0; // Thread id starting index + bool done = false; // Default being done to false + + for (int i = 0; i < config.maxthreads; i++) { + // Initializes the thread status' with false since no threads are running + thread_status[i] = false; + } + + while(1) { + /* + * This while loop will create a thread when no thread is running and + * when there are still paritions that need processing. Once threads + * are running, we set the status of that thread id, tid, to true in + * thread_status, which lets us know that we need to poll, or check the + * status of, that thread. If a thread finishes, since we don't want to + * constantly waste time in two places checking to see if we are done multi-threading + * we set done to true which can be overridden by creating a new thread or discovering + * through thread_status that we have threads running still. + */ + if(thread_status[tid] == true) { + // Check the thread status ID + // If it is 0, we might be done and need to clear the running status + int status = pthread_tryjoin_np(tids[tid], NULL); + if(status == 0) { + done = true; + thread_status[tid] = false; + } + usleep(50); // Sleep to let sorting finish + } else if (thread_status[tid] == false && part_id < config.pieces) { + // Creates a new thread if we have pieces to run and the thread isn't running + // Also clears done and sets the thread_status + pthread_attr_init(&tattrs[tid]); // Initialize the thread attributes + if (pthread_create(&tids[tid], &tattrs[tid], &hybridsort, &partitions[part_id++])) { + // Reports an error when creating the thread and exits + fprintf(stderr, "There was an error creating a new pthread.\n"); + exit(1); + } + done = false; + thread_status[tid] = true; + } + + if(done && part_id == config.pieces) { + // Check to see if there are running threads + // if there are, return to the while loop + // if not, return + for(int j = 0; j < config.maxthreads; j++) { + if(thread_status[j]) { + done = false; + break; + } + } + if(done) return; + } + + tid++; // Increment the thread id + + if(tid == config.maxthreads) tid = 0; // Reset the thread id so we can continue to poll our threads + + } +} + +void help() { + /* + * This function is designed soley for the purpose of providing help text when -h is specified in the arguments. + */ + fprintf(stdout, + "project2 -n SIZE\n [-s THRESHOLD]\n [-a ALTERNATE]\n [-r SEED]\n [-m MULTITHREAD]\n [-p PIECES]\n [-t MAXTHREADS]\n [-m3 MEDIAN]\n\n where:\n SIZE is the number of items to sort (must be a positive 32-bit integer)\n \n THRESHOLD is an optional parameter that represents the threshold point (the size of the\n segment) at which you will switch from recursive Quicksort to the alternate\n sort. If the size of the segment is less than or equal THRESHOLD, use the\n alternate algorithm. This implies that if you start the program with the same\n value for SIZE and THRESHOLD, it will make one call into Quicksort, which\n will then sort the entire array using the alternate sort algorithm. At the\n other end of that spectrum, if THRESHOLD is set to zero, it will ALWAYS\n recursively Quicksort the entire array (all the way down to single items).\n If THRESHOLD is omitted, the default value is 10.\n \n ALTERNATE is an optional parameter that specifies what the alternate algorithm will be\n The default is 's' (Shell sort). The user can specify S, s, I, or i (I is for\n Insertion Sort).\n \n SEED is an optional (integer) parameter. If omitted, the random number generator\n is not seeded, and you run with whatever sequence it generates (note: it will\n generate the same sequence every time, so there will be the appearance of\n randomness WITHIN a run of the program, but no randomness BETWEEN runs.\n If SEED is specified (but is not -1), you will see the random number\n generator with the supplied value. If SEED is specified as -1, you will seed\n it with clock().\n \n MULTITHREAD is also optional, and will be a single character, used to determine whether\n or not to multithread the solution. If it is 'n' or 'N', simply call\n Quicksort on a single thread; otherwise, run it multithreaded. The default\n value is 'y'.\n \n PIECES is another optional parameter. PIECES Specifies how many partitions to\n divide the original list into. The default value is ten. If MULTITHREAD is\n 'n' or 'N' (or not specified), then setting PIECES has no effect\n \n MAXTHREADS is another optional parameter, which applies only if MULTITHREAD is 'y'. It\n specifies gives the number of threads to attempt to run at once, but\n MAXTHREADS must be no more than PIECES (we can't run 3 pieces on 4 threads).\n The default value for MAXTRHEADS is 4.\n \n MEDIAN is another optional y/n parameter, which determines whether each segment\n will be partitioned using the MEDIAN-OF-THREE technique. The default is 'n'.\n" + ); +} + +int main(int argc, char *argv[]) { + + // Initialize config defaults + config.size = 0; + config.seed = -2; + config.pieces = 10; + config.threshold = 10; + config.maxthreads = 4; + config.median3 = false; + config.alternate = 's'; + config.multithreaded = true; + + // Compile regular expressions that are used to validate arguments + gettimeofday(&start_tr, NULL); // Start the total run-time timer + if(regcomp(&positive_integer, "[1-9]+", REG_EXTENDED)) // Checks to see if an input is a positve integer + exit(1); + + if(regcomp(&yes_no_re, "^[yYnN]$", REG_EXTENDED)) // Checks to see of the input is 'Y', 'y', 'N', or 'n' + exit(1); + + if(regcomp(&sort_flags_re, "^[sSiI]$", REG_EXTENDED)) // Checks to see of the input is 'S', 's', 'I', or 'i' + exit(1); + + for(int i = 1; i < argc; ++i) { + /* + * This for loop iterates over the arguments captured in argc, below are the valid arguments and details. + * project2 -n SIZE + * [-s THRESHOLD] + * [-a ALTERNATE] + * [-r SEED] + * [-m MULTITHREAD] + * [-p PIECES] + * [-t MAXTHREADS] + * [-m3 MEDIAN] + * + * where: + * SIZE is the number of items to sort (must be a positive 32-bit integer) + * + * THRESHOLD is an optional parameter that represents the threshold point (the size of the + * segment) at which you will switch from recursive Quicksort to the alternate + * sort. If the size of the segment is less than or equal THRESHOLD, use the + * alternate algorithm. This implies that if you start the program with the same + * value for SIZE and THRESHOLD, it will make one call into Quicksort, which + * will then sort the entire array using the alternate sort algorithm. At the + * other end of that spectrum, if THRESHOLD is set to zero, it will ALWAYS + * recursively Quicksort the entire array (all the way down to single items). + * If THRESHOLD is omitted, the default value is 10. + * + * ALTERNATE is an optional parameter that specifies what the alternate algorithm will be + * The default is 's' (Shell sort). The user can specify S, s, I, or i (I is for + * Insertion Sort). + * + * SEED is an optional (integer) parameter. If omitted, the random number generator + * is not seeded, and you run with whatever sequence it generates (note: it will + * generate the same sequence every time, so there will be the appearance of + * randomness WITHIN a run of the program, but no randomness BETWEEN runs. + * If SEED is specified (but is not -1), you will see the random number + * generator with the supplied value. If SEED is specified as -1, you will seed + * it with clock(). + * + * MULTITHREAD is also optional, and will be a single character, used to determine whether + * or not to multithread the solution. If it is 'n' or 'N', simply call + * Quicksort on a single thread; otherwise, run it multithreaded. The default + * value is 'y'. + * + * PIECES is another optional parameter. PIECES Specifies how many partitions to + * divide the original list into. The default value is ten. If MULTITHREAD is + * 'n' or 'N' (or not specified), then setting PIECES has no effect + * + * MAXTHREADS is another optional parameter, which applies only if MULTITHREAD is 'y'. It + * specifies gives the number of threads to attempt to run at once, but + * MAXTHREADS must be no more than PIECES (we can't run 3 pieces on 4 threads). + * The default value for MAXTRHEADS is 4. + * + * MEDIAN is another optional y/n parameter, which determines whether each segment + * will be partitioned using the MEDIAN-OF-THREE technique. The default is 'n'. + * + */ + + const char* arg = argv[i]; // Grabs the first argument which is a expected to be a flag + const char* arg2; + + if (strcmp(arg, "-h") == 0) { + // If help is called, output help text and exit + help(); + exit(1); + } + + // Grabs the next argument which is expected to be a value + if(argv[++i] != NULL) { + arg2 = argv[i]; + } else { + fprintf(stderr, "An incorrect argument and/ or value was specified.\n"); + exit(1); + } + + int value = strcmp(arg, "-p"); + if(strcmp(arg, "-n") == 0) { + if(!regexec(&positive_integer, arg2, 0, NULL, 0)) { + config.size = atoi(arg2); + } else { + fprintf(stderr, "SIZE requires a positive 32 bit integer to be specified.\n"); + exit(1); + } + } else if(strcmp(arg, "-s") == 0) { + if(!regexec(&positive_integer, arg2, 0, NULL, 0)) { + config.threshold = atoi(arg2); + } else { + fprintf(stderr, "THRESHOLD requires a positive 32 bit integer to be specified.\n"); + exit(1); + } + } else if(strcmp(arg, "-a") == 0) { + if(!regexec(&sort_flags_re, arg2, 0, NULL, 0)) { + config.alternate = *arg2; + } else { + fprintf(stderr, "ALTERNATE requires S/i to be specified.\n"); + exit(1); + } + } else if(strcmp(arg, "-r") == 0) { + if(!regexec(&positive_integer, arg2, 0, NULL, 0) || strcmp(arg2, "-1") == 0) { + if(strcmp(arg2, "-1") == 0) config.seed = -1; + else config.seed = atoi(arg2); + } else { + fprintf(stderr, "SEED requires a positive 32 bit integer, or -1, to be specified.\n"); + exit(1); + } + } else if(strcmp(arg, "-m") == 0) { + if(!regexec(&yes_no_re, arg2, 0, NULL, 0)) { + if(strcmp(arg2, "n") == 0 || strcmp(arg, "N") == 0) config.multithreaded = false; + } else { + fprintf(stderr, "MULTITHREADED requires Y/n to be specified.\n"); + exit(1); + } + } else if(strcmp(arg, "-p") == 0) { + if(!regexec(&positive_integer, arg2, 0, NULL, 0)) { + config.pieces = atoi(arg2); + } else { + fprintf(stderr, "PIECES requires and positive 32 bit integer to be specified.\n"); + exit(1); + } + } else if(strcmp(arg, "-t") == 0) { + if(!regexec(&positive_integer, arg2, 0, NULL, 0)) { + config.maxthreads = atoi(arg2); + if(config.maxthreads > 4) { + fprintf(stderr, "MAXTHREADS cannot be larger than 4.\n"); + exit(1); + } + } else { + fprintf(stderr, "MAXTHREADS requires and positive 32 bit integer to be specified.\n"); + exit(1); + } + } else if(strcmp(arg, "-m3") == 0) { + if(!regexec(&yes_no_re, arg2, 0, NULL, 0)) { + if(strcmp(arg2, "y") == 0 || strcmp(arg2, "Y") == 0) config.median3 = true; + } else { + fprintf(stderr, "MEDIAN requires Y/n to be specified.\n"); + exit(1); + } + } else { + fprintf(stderr, "An incorrect argument was specified.\n"); + exit(1); + } + } + + if(config.size == 0) { + // If the config is 0, exit since we need a size of 1 or more + fprintf(stderr, "A SIZE must be specified.\n"); + exit(1); + } + + if(config.maxthreads > config.pieces) { + // If pieces is less than maxthreads, exit as we cannot split 3 pieces across 4 threads + fprintf(stderr, "The MAXTHREADS must be equal or less than PIECES.\n"); + exit(1); + } + + if(config.size < config.pieces) { + // if size is smaller than pieces exit since we cannot split a size smaller than pieces + fprintf(stderr, "The SIZE must be equal or larger than PIECES.\n"); + exit(1); + } + + gettimeofday(&start, NULL); // Start timer + sequence = (uint32_t *)malloc(config.size * sizeof(uint32_t)); // Allocate the array in memory + gettimeofday(&stop, NULL); // Stop timer + wlclk_tdiff = (float) (stop.tv_sec - start.tv_sec) + (stop.tv_usec - start.tv_usec) * 1e-6; // Calculate time difference + fprintf(stdout, "Array took %0.3f seconds to create.\n", wlclk_tdiff); + + gettimeofday(&start, NULL); // Start timer + for (uint32_t i = 0; i < config.size; i++) sequence[i] = i; // Populate the array from 0 to config.size - 1 + gettimeofday(&stop, NULL); // Stop timer + wlclk_tdiff = (float) (stop.tv_sec - start.tv_sec) + (stop.tv_usec - start.tv_usec) * 1e-6; // Calculate time difference + fprintf(stdout, "Array took %0.3f seconds to initialize.\n", wlclk_tdiff); + + gettimeofday(&start, NULL); // Start timer + shuffle(); // Shuffle the array + gettimeofday(&stop, NULL); // Stop timer + wlclk_tdiff = (float) (stop.tv_sec - start.tv_sec) + (stop.tv_usec - start.tv_usec) * 1e-6; // Calculate time difference + fprintf(stdout, "Array took %0.3f seconds to scramble.\n", wlclk_tdiff); + + if(config.multithreaded) { + /* + * When multi-threading is true, partitions are created that will be passed into + * hybridsort() by the multithread() function. These partitions are formed the exact + * same way as quicksort, however, instead of recursively calling quicksort, these + * left and right partitions are stored into a partition array. This partition array + * contains the pTuple structure. Furthermore, this partitioning code is designed to + * ensure the largest partitions are split in half by sorting the partitions each pass + * to ensure the largest one is at index 0. This means we don't have to worry about overwriting + * another array since we always will place the largest array at index 0 and the smaller at the end. + * If a swap between partitions needs to happen, they will be swapped. + */ + + partitions = (struct pTuple *)malloc(config.pieces * sizeof(struct pTuple)); // Allocate the partitions in memory + + int parts_count = 1; + struct pTuple part; + int left_size; + int right_size; + + // Initialzie a basic paritition representing the whole array + partitions[0].lo = 0; + partitions[0].hi = config.size - 1; + partitions[0].size = config.size; + + gettimeofday(&start, NULL); // Start timer + while(parts_count < config.pieces) { + + for (int k = 0; k < parts_count; ++k) { + /* + * This for loop is used to ensure that the largest partition is + * found at index 0 of the partitions so we can reliably split the + * largest partition into smaller paritions. + */ + if (partitions[k].size > partitions[0].size) { + struct pTuple tmp_part = partitions[0]; + partitions[0] = partitions[k]; + partitions[k] = tmp_part; + } + } + + int lo = partitions[0].lo; + int hi = partitions[0].hi; + int i = lo; + int j = hi + 1; + int pivot = sequence[lo]; + + if(config.median3) { + /* + * This statement is used to change the pivoting strategy from using the left most (lo) + * value of the partition to median-of-three. This function works by calculating the mid + * value and then determining based off the cases betlow which value is the closest to the + * median. This is to help produce more even splits, however, even splits are not guaranteed. + */ + int mid = (lo + hi) / 2; // Calculate the middle value of the partition + int lo_val = sequence[lo]; + int med_val = sequence[mid]; + int hi_val = sequence[hi]; + int median; + if ((lo_val >= med_val && lo_val <= hi_val) || (lo_val >= hi_val && lo_val <= med_val)) median = lo; + // Checks to see if the value at index lo meets median conditions + else if ((med_val >= lo_val && med_val <= hi_val) || (med_val >= hi_val && med_val <= lo_val)) median = mid; + // Checks to see if the value at index mid meets median conditions + else median = hi; + // Checks to see if the value at index hi meets median conditions + + // Swap the new median-of-three calculated pivot with the old left-most pivot + int tmp = sequence[median]; + sequence[median] = sequence[lo]; + sequence[lo] = tmp; + pivot = sequence[lo]; + } + + /* + * This do loop iterates until the sequence value of the lower (left) end interator and + * higher (right) end iterator of the segment passes the pivot. This + * determines the left and right partitions and will swap values + * when the left iterator is less than the right iterator. This will + * keep happening until i is not less than j for which the do while loop + * will exit. + */ + do { + do i++; while (sequence[i] < pivot); + do j--; while (sequence[j] > pivot); + if (i < j) { + int temp = sequence[i]; + sequence[i] = sequence[j]; + sequence[j] = temp; + } else break; + } while(1); + + // Swaps the pivot location + sequence[lo] = sequence[j]; + sequence[j] = pivot; + + left_size = j - lo; + right_size = hi - j; + + if (left_size > right_size) { + // If the left side is larger than the right side, store it at index 0 and store the right side at the end. + partitions[0].lo = lo; + partitions[0].hi = j-1; + partitions[0].size = left_size; + partitions[parts_count].lo = j+1; + partitions[parts_count].hi = hi; + partitions[parts_count].size = right_size; + } else { + // If the right side is larger than the left side, store it at index 0 and store the left side at the end. + partitions[0].lo = j+1; + partitions[0].hi = hi; + partitions[0].size = right_size; + partitions[parts_count].lo = lo; + partitions[parts_count].hi = j-1; + partitions[parts_count].size = left_size; + } + parts_count++; // Increment our partition count + + } + + for (int i = 0; i < parts_count; ++i) { + /* + * This loop does one final pass to ensure that we have + * properly sorted our paritions to have the largest be + * processed first. + */ + for (int j = i + 1; j < parts_count; ++j) { + if (partitions[i].size < partitions[j].size) { + struct pTuple tmp_part = partitions[i]; + partitions[i] = partitions[j]; + partitions[j] = tmp_part; + } + } + } + gettimeofday(&stop, NULL); // Stop timer + wlclk_tdiff = (float) (stop.tv_sec - start.tv_sec) + (stop.tv_usec - start.tv_usec) * 1e-6; // Calculate time difference + fprintf(stdout, "Multi-thread pieces took %0.3f seconds to partition.\n", wlclk_tdiff); + + gettimeofday(&start, NULL); // Start timer + start_c = clock(); // Start timer + multithread(); // Call multithread() + gettimeofday(&stop, NULL); // Stop timer + end_c = clock(); // Stop timer + cpu_tdiff = (double) (end_c - start_c) / CLOCKS_PER_SEC; // Calculate time difference + wlclk_tdiff = (float) (stop.tv_sec - start.tv_sec) + (stop.tv_usec - start.tv_usec) * 1e-6; // Calculate time difference + + } else { + /* + * If we are not multi-threading, we do not need to pass or + * partition any pieces, we will simply just pass the whole array. + */ + + // Create a basic partition representing the whole array/ sequence + struct pTuple full_part; + full_part.lo = 0; + full_part.hi = config.size - 1; + full_part.size = config.size; + + gettimeofday(&start, NULL); // Start timer + start_c = clock(); // Start timer + hybridsort(&full_part); // Call hybridsort() + gettimeofday(&stop, NULL); // Stop timer + end_c = clock(); // Stop timer + cpu_tdiff = (double) (end_c - start_c) / CLOCKS_PER_SEC; // Calculate time difference + wlclk_tdiff = (float) (stop.tv_sec - start.tv_sec) + (stop.tv_usec - start.tv_usec) * 1e-6; // Calculate time difference + } + gettimeofday(&stop_tr, NULL); // Stop the total run-time timer + tr_tdiff = (float) (stop_tr.tv_sec - start_tr.tv_sec) + (stop_tr.tv_usec - start_tr.tv_usec) * 1e-6; // Calculate time difference + + for(int i = 1; i < config.size; i++) { + // This for loop checks to make sure the array is sorted, if not it will output verbose + // debug info about the flags input and exit. + if (sequence[i] < sequence[i - 1]) { + fprintf(stderr, "Sequence is not sorted.\n"); + fprintf(stdout, "Debug | %d | %d | %d | %d | %s | %c | %s | %0.3f | %0.3f | %0.3f\n", + config.size, config.pieces, config.threshold, config.maxthreads, config.multithreaded ? "true" : "false", + config.alternate, config.median3 ? "true" : "false", wlclk_tdiff, cpu_tdiff, tr_tdiff); + exit(1); + } + } + + fprintf(stdout, "Seconds spend sorting (sec)... Wall Clock: %0.3f / CPU: %0.3f\nTotal Run Time (sec): %0.3f\n", wlclk_tdiff, cpu_tdiff, tr_tdiff); + + return 0; +} +