From: Sam Moore Date: Sun, 30 Sep 2012 11:54:54 +0000 (+0800) Subject: Parallel Programming - Finished pthreads X-Git-Url: https://git.ucc.asn.au/?p=matches%2Fhonours.git;a=commitdiff_plain;h=20979b1c07eda73b7f86b2fe8cb66eb58d959e04 Parallel Programming - Finished pthreads I finally fixed all the stupid bugs in pthreads. Committing before messing with the openmp version (which surprisingly compiles) I basically just need to get rid of the busy loop in the openmp version. I will probably have to completely change the structure or something. Grrr. --- diff --git a/course/semester2/pprog/assignment1/get_data.py b/course/semester2/pprog/assignment1/get_data.py index 04ad2bec..359c4181 100755 --- a/course/semester2/pprog/assignment1/get_data.py +++ b/course/semester2/pprog/assignment1/get_data.py @@ -14,11 +14,10 @@ plot = Gnuplot.Gnuplot(persist=0) programs = { "single-thread" : "./single-thread/nbody -v 5" } -for n in range(2, 6): - programs.update({"mthread"+str(n) : "./mthread/nbody -v 5 --pedantic-graphics -n "+str(n)}) +for n in range(2, 20, 4): programs.update({"mthread"+str(n) : "./mthread/nbody -v 5 -n "+str(n)}) - #programs.update({"slow-mthread"+str(n) : "./mthread/nbody-slow --pedantic-graphics -v 5 -n "+str(n)}) - #programs.update({"openmp"+str(n) : "./openmp/nbody -v 5 --pedantic-graphics -n " +str(n)}) + #programs.update({"slow-mthread"+str(n) : "./mthread/nbody-slow -v 5 -n "+str(n)}) + programs.update({"openmp"+str(n) : "./openmp/nbody -v 5 -n " +str(n)}) diff --git a/course/semester2/pprog/assignment1/mthread/barrier.c b/course/semester2/pprog/assignment1/mthread/barrier.c new file mode 100644 index 00000000..9b1a3a09 --- /dev/null +++ b/course/semester2/pprog/assignment1/mthread/barrier.c @@ -0,0 +1,140 @@ +/** + * @file barrier.c + * @purpose Implementation of a pthread barrier + * @author Sam Moore (20503628) 2012 + */ + +#include "barrier.h" + + +/** + * @function Barrier_Init + * @purpose Initialise a Barrier + * Should only be called once for each Barrier + * @param b - The Barrier + * @param total_threads - Number of threads that will be participating in the barrier + * + */ +void Barrier_Init(Barrier * b, unsigned total_threads) +{ + b->total_threads = total_threads; + b->threads_done = total_threads; // All threads start as done. + // When Barrier_Join is called, the first entering thread will set threads_done to zero. + // Each thread joining the barrier increments threads_done, and sleep until all threads have joined. + b->active = false; +} + +void Barrier_Enter(Barrier * b) +{ + b->active = true; +} + +void Barrier_Wait(Barrier * b) +{ + + pthread_mutex_lock(&(b->mutex)); + while (b->active) + { + pthread_cond_wait(&(b->threads_done_cv), &(b->mutex)); + } + pthread_mutex_unlock(&(b->mutex)); +} + +/** + * @function Barrier_Join + * @purpose A Barrier that waits for the required number of threads to enter it, before allowing all threads to continue + * + * This function should be called for each thread participating in the barrier. + * Barrier_Init should first be called with the correct total number of threads participating in the barrier. + * + * @param b - The barrier + */ +void Barrier_Join(Barrier * b) +{ + pthread_mutex_lock(&(b->mutex)); + + if (b->threads_done >= b->total_threads) // The barrier is not active + { + b->threads_done = 0; // Activate the barrier + b->active = true; + } + + b->threads_done += 1; + //printf("Thread joins barrier %p, %u done out of %u\n", (void*)(b), b->threads_done, b->total_threads); + if (b->threads_done == b->total_threads) + { + pthread_cond_broadcast(&(b->threads_done_cv)); + b->active = false; + //printf("\tThread wakes up other threads\n"); + } + else + { + //printf("\tThread waiting on barrier\n"); + while (b->threads_done < b->total_threads) // Keep waiting as long as the barrier is active + pthread_cond_wait(&(b->threads_done_cv), &(b->mutex)); + + //printf("\tThread done with barrier\n"); + + } + + + //printf("\tThread leaves barrier; %u threads done out of %u\n", b->threads_done, b->total_threads); + pthread_mutex_unlock(&(b->mutex)); + +} + + +void Barrier_ForceExit(Barrier * b) +{ + pthread_mutex_lock(&(b->mutex)); + pthread_cond_broadcast(&(b->threads_done_cv)); + b->active = false; + pthread_mutex_unlock(&(b->mutex)); +} + +/** + * @function Barrier_JoinCall + * @purpose Behaves as Barrier_Join(b), except the last thread in the barrier calls the specified function + * (This is a hack to get code to execute in one thread without additional mutexes when the code happens just after a barrier anyway). + * (Could make the first thread execute the function for efficiency, but for the nbody simulator that would make the performance metric wrong) + * @param b - The barrier + * @param call - Function pointer to call (function pointers are fun!) + * @param args - Arguments to the function + */ +void * Barrier_JoinCall(Barrier * b, void*(*call)(void*), void * args) +{ + void * result = NULL; + pthread_mutex_lock(&(b->mutex)); + + if (b->threads_done >= b->total_threads) + { + b->threads_done = 0; + b->active = true; + } + + b->threads_done += 1; + //printf("Thread joins barrier %p, %u done out of %u\n", (void*)(b), b->threads_done, b->total_threads); + if (b->threads_done == b->total_threads) + { + result = (*call)(args); + pthread_cond_broadcast(&(b->threads_done_cv)); + b->active = false; + //printf("\tThread wakes up other threads\n"); + } + else + { + //printf("\tThread waiting on barrier\n"); + while (b->threads_done < b->total_threads) + pthread_cond_wait(&(b->threads_done_cv), &(b->mutex)); + //printf("\tThread done with barrier\n"); + + } + + pthread_mutex_unlock(&(b->mutex)); + + return result; +} + +//EOF + + diff --git a/course/semester2/pprog/assignment1/mthread/barrier.h b/course/semester2/pprog/assignment1/mthread/barrier.h new file mode 100644 index 00000000..e89630ac --- /dev/null +++ b/course/semester2/pprog/assignment1/mthread/barrier.h @@ -0,0 +1,59 @@ +/** + * @file barrier.h + * @purpose Declaration for a pthread barrier + * @author Sam Moore (20503628) 2012 + */ + +// The barrier can be used as follows: + +// Think of the barrier as relating to a section of work that must be completed by a team of threads +// The barrier is initialised with the number of threads in the team +// Each thread in the team calls Barrier_Join when finished; this ensures all threads reach Barrier_Join before continuing. + +// To make it conceptually easier to synchronise with threads that are not part of the "team": +// I have introduced Barrier_Wait, and Barrier_Enter +// Barrier_Wait is used to either: +// 1. Immediately continue if threads have already completed a task and called Barrier_Join +// 2. Sleep until all threads call Barrier_Join if they have not completed their task +// Barrier_Enter should be called in threads that belong to the "team", to indicate that they have started their task. +// Otherwise, Barrier_Wait will generally fail. +// This requires a single boolean, and makes things easier (for me) to understand, so I think it is worth it. +// Also, *any* number of threads may call Barrier_Wait; it is not restricted by the number of threads actually doing the "work". +// If there are no threads calling Barrier_Wait, then Barrier_Enter is not needed; the first thread to call Barrier_Join always activates the barrier. It is always deactivated when threads leave. + + +#ifndef _BARRIER_H +#define _BARRIER_H + +#include +#include + +/** + * Structure to represent a barrier for multiple threads + * @param mutex - Mutex around the counter + * @param busy - Counter of threads within the barrier + * @param threads_done_cv - Condition to wake up threads waiting on barrier once all working threads have left it + */ +typedef struct +{ + pthread_mutex_t mutex; + unsigned threads_done; // Counter of threads which are finished + unsigned total_threads; // Total number of threads participating in the barrier + bool active; // Indicates whether barrier is active + pthread_cond_t threads_done_cv; +} Barrier; + + + +void Barrier_Init(Barrier * b, unsigned total_threads); +void Barrier_Enter(Barrier * b); +void Barrier_Join(Barrier * b); +void Barrier_Wait(Barrier * b); + +void * Barrier_JoinCall(Barrier * b, void*(*call)(void*), void * args); + +void Barrier_ForceExit(Barrier * b); // Force all threads to exit barrier + + +#endif //_BARRIER_H + diff --git a/course/semester2/pprog/assignment1/mthread/nbody.c b/course/semester2/pprog/assignment1/mthread/nbody.c index de9b8aaa..13831cd1 100644 --- a/course/semester2/pprog/assignment1/mthread/nbody.c +++ b/course/semester2/pprog/assignment1/mthread/nbody.c @@ -6,12 +6,13 @@ #include "nbody.h" // Declarations #include "../single-thread/nbody.c" // Include all functions from the single threaded version - +#include #include "graphics.h" // For declaration of Graphics_Run only +#include "barrier.c" // --- Variable declarations --- // -pthread_t compute_thread; // The thread responsible for computations; it spawns worker threads +pthread_t compute_thread; // The thread responsible for computations; it spawns worker threads (* terms and conditions apply) pthread_t * worker_thread = NULL; //Array of worker threads responsible for Force and Position updates System * sub_system = NULL; //Array of Systems used to divide up the main "universe" System for worker threads @@ -21,7 +22,6 @@ pthread_mutex_t mutex_runstate; // Mutex around the runstate -pthread_attr_t attr; //thread attribute for the workers. Barrier force_barrier; // I laughed at this variable name. A bit sad really. Barrier position_barrier; @@ -29,22 +29,33 @@ Barrier position_barrier; Barrier graphics_barrier; +pthread_mutex_t mutex_threads_running; +int threads_running = 0; /** * @function Compute_Thread * @purpose Thread - Continuously computes steps for a system of bodies. Seperate from graphics functions. * Spawns worker threads to divide up computation. - * @param arg - Can be cast to the System for which computations are performed (ie: &universe) + * @param arg - Can be cast to the System for which computations are performed + * + * NOTE: + * This will always be (void*)(&universe) where universe is the global System variable that has every Body in it. + * But I don't like global variables. And since the argument had to be passed, I thought I might as well use it. + * That way, when I change "universe" to "solar_system", I only have to change the argument where this is called, not all through it. + * Find and replace? Who uses that!? */ + + void * Compute_Thread(void * arg) { System * s = (System*)(arg); //cast argument to a System* - // If no number of threads provided, use the default value, unless someone changed that to a stupid value + + // If no number of threads provided, use the default value, unless someone (me) changed that to a stupid value if (options.num_threads <= 0) - options.num_threads = (DEFAULT_WORKING_THREADS > 1) ? DEFAULT_WORKING_THREADS : 1; + options.num_threads = (DEFAULT_WORKING_THREADS > 1) ? DEFAULT_WORKING_THREADS : 1; // Fear the ternary operator! if (options.nested_threads <= 0) options.nested_threads = 1; @@ -57,91 +68,119 @@ void * Compute_Thread(void * arg) s->N, options.num_threads, s->N); options.num_threads = s->N; } + + // Initialise the barriers ("shields up!") + Barrier_Init(&force_barrier, options.num_threads); + Barrier_Init(&position_barrier, options.num_threads); + Barrier_Init(&graphics_barrier, 1); + - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //Needs to be detached, so that memory can be reused. - if (options.num_threads > 1) // Allocate worker threads and sub systems, as long as there would be more than 1 + if (options.num_threads > 1) // If we require additional worker threads... { - worker_thread = Allocate_Threads(options.num_threads); + // Allocate worker threads and sub systems + sub_system = Split_System(&universe, options.num_threads); if (options.nested_threads > 1) nested_sub_system = Split_System(&universe, options.nested_threads); - #ifdef PERSISTENT_THREADS - for (unsigned i = 0; i < options.num_threads; ++i) + #ifdef PERSISTENT_THREADS // Code for the smart way of doing it (spawn threads once, keep them running) + worker_thread = Allocate_Threads(options.num_threads-1); + // Spawn a bunch of worker threads, and let them all do their thing. + // Note the "-1". Because this thread can do work too! + for (unsigned i = 0; i < options.num_threads-1; ++i) { - if (pthread_create(worker_thread+i, & attr, Worker_Thread, (void*)(sub_system+i)) != 0) + if (pthread_create(worker_thread+i, NULL, Worker_Thread, (void*)(sub_system+i)) != 0) { perror("In compute thread, couldn't create worker thread"); QuitProgram(true); pthread_exit(NULL); } } + Worker_Thread((void*)(sub_system+options.num_threads-1)); // This thread becomes a worker thread + #else + worker_thread = Allocate_Threads(options.num_threads); #endif //PERSISTENT_THREADS - } #ifdef PERSISTENT_THREADS - else + else // We only require one worker thread... { + // So just do all computations in this thread while (!ExitCondition()) { - if (options.verbosity != 0 && universe.steps % options.verbosity == 1) - DisplayStatistics(); - - // Just do everything in this thread + System_Forces(s, s); + // If required, wait for graphics to finish drawing + if (options.draw_graphics && options.pedantic_graphics) + Barrier_Wait(&graphics_barrier); System_Positions(s); + StepFunction(s); + + if (options.draw_graphics && options.pedantic_graphics) + Barrier_ForceExit(&position_barrier); //Make the graphics continue } QuitProgram(false); pthread_exit(NULL); } - #else - // The main computation loop + #else // Code for the stupid way of doing it (respawn threads each step) + // (ie: The way I immediately implemented and didn't realise was stupid until someone told me) + + + + + // Run until we can't run anymore while (!ExitCondition()) { - if (options.verbosity != 0 && universe.steps % options.verbosity == 1) - DisplayStatistics(); + - if (options.num_threads <= 1) + if (options.num_threads <= 1) // If there is only 1 worker thread... { // Just do everything in this thread System_Forces(s, s); + // If required, wait for graphics to finish drawing + if (options.draw_graphics && options.pedantic_graphics) + Barrier_Join(&graphics_barrier); + System_Positions(s); + StepFunction(s); + + if (options.draw_graphics && options.pedantic_graphics) + Barrier_Join(&position_barrier); //Make the graphics continue continue; } - //Compute forces + //Compute forces by spawning threads, each thread gets a sub system for (unsigned i = 0; i < options.num_threads; ++i) { - if (pthread_create(worker_thread+i, &attr, Force_Thread, (void*)(sub_system+i)) != 0) + if (pthread_create(worker_thread+i, NULL, Force_Thread, (void*)(sub_system+i)) != 0) { perror("In compute thread, couldn't create worker thread (force)"); QuitProgram(true); pthread_exit(NULL); } - + } + for (unsigned i = 0; i < options.num_threads; ++i) + pthread_join(worker_thread[i], NULL); - Barrier_Wait(&force_barrier); - - //All the forces are now computed - + + // If required, wait for graphics to finish drawing if (options.draw_graphics && options.pedantic_graphics) - { Barrier_Wait(&graphics_barrier); - } - //Compute positions + + Barrier_Enter(&position_barrier); + + //Compute positions by spawning a bunch of threads to do it for (unsigned i = 0; i < options.num_threads; ++i) { - if (pthread_create(worker_thread+i, &attr, Position_Thread, (void*)(sub_system+i)) != 0) + if (pthread_create(worker_thread+i, NULL, Position_Thread, (void*)(sub_system+i)) != 0) { perror("In compute thread, couldn't create worker thread (position)"); QuitProgram(true); @@ -149,17 +188,13 @@ void * Compute_Thread(void * arg) } } - //Wait for positions to be computed - Barrier_Wait(&position_barrier); - - - //Update number of steps computed - universe.steps += 1; - + for (unsigned i = 0; i < options.num_threads; ++i) + pthread_join(worker_thread[i], NULL); - + StepFunction(s); // Execute single threaded stuff + } QuitProgram(false); @@ -172,34 +207,39 @@ void * Compute_Thread(void * arg) * @purpose Called in graphics thread before the draw loop * When --pedantic-graphics enabled, will wait for position computations to finish before drawing * Otherwise does nothing + * + * This originally seemed like a good place to put the code now in StepFunction(), since only one thread runs this + * But then I realised that the graphics might be disabled, + * and there was no point having a thread that only existed to call that code. + * + * So I changed it to the horrible solution that I currently have. */ void BeforeDraw() { - if (options.verbosity != 0 && universe.steps % options.verbosity == 0) - DisplayStatistics(); + //printf("BEFORE DRAW\n"); if (!options.pedantic_graphics) return; - Barrier_Wait(&position_barrier); - - - Barrier_Enter(&graphics_barrier); + //printf("Graphics thread waits on position barrier\n"); + Barrier_Wait(&position_barrier); + //printf("\tGraphics thread wakes up\n"); + Barrier_Enter(&graphics_barrier); } /** * @function AfterDraw * @purpose Called in graphics thread after the draw loop - * When --pedantic-graphics is supplied, will signal computation thread that drawing is finished + * When --pedantic-graphics is supplied, will signal computation threads that drawing is finished * So that positions can be safely altered * Otherwise does nothing */ void AfterDraw() { - universe.steps += 1; + //universe.steps += 1; if (!options.pedantic_graphics) return; - Barrier_Leave(&graphics_barrier); + Barrier_Join(&graphics_barrier); } @@ -207,83 +247,67 @@ void AfterDraw() /** * @function Worker_Thread - * @purpose Thread - Calculate stuff + * @purpose Thread - A self contained worker thread to compute a particular sub system of bodies + * + * This is the "smart" way to do it, because threads are only created once, and compute both force and position. + * The greatest difficulty with pthreads is getting a *single* thread from the team to execute certain code + * (ie: The stuff in StepFunction()). + * With the "continuously respawning threads of stupidity" approach, + * because there is one "master" thread (not necessarilly the main thread... don't get confused now) + * to keep respawning the workers, the single threaded code can just be executed in the master thread. + * + * With this approach, I have created a hacky solution so that the *last* thread to leave the position barrier gets to call StepFunction. + * */ void * Worker_Thread(void * arg) { - System * s = (System*)(arg); - - - pthread_t * nested_workers = NULL; - System_ForcePair * system_pairs = NULL; - System * nested_position = NULL; - - Barrier nested_barrier; - Barrier_Init(&nested_barrier); - - printf("options.nested_threads == %d\n", (int)(options.nested_threads)); + System * s = (System*)(arg); // This is mainly to save typing the RHS a lot of times - if (options.nested_threads != 1) + // Each thread runs until the whole program is supposed to end + while (!ExitCondition()) { + + + System_Forces(s, &universe); // Each thread computes the forces for its share of bodies - system_pairs = (System_ForcePair*)(calloc(options.nested_threads, sizeof(System_ForcePair))); - if (system_pairs == NULL) // Handle tedious error cases - { - perror("Couldn't allocate array of system pairs"); - QuitProgram(true); - pthread_exit(NULL); - } - nested_workers = Allocate_Threads(options.nested_threads); - nested_position = + // Do not confuse with "Barrier_Wait". + // Barrier_Wait does not affect the barrier; it just waits for it + // Barrier_Join actively updates the state of the barrier, and wakes up sleeping threads if required. - for (unsigned i = 0; i < options.nested_threads; ++i) - { - system_pairs[i].A = s; - system_pairs[i].B = nested_sub_system+i; - } - } + Barrier_Join(&force_barrier); // All threads must reach here before moving on. + if (ExitCondition()) return NULL; - while (!ExitCondition()) - { - if (options.nested_threads == 1) - { - Barrier_Enter(&force_barrier); - System_Forces(s, &universe); - Barrier_Leave(&force_barrier); - } - else - { - for (unsigned i = 0; i < options.nested_threads; ++i) - { - if (pthread_create(nested_workers+i, &attr, Force_Thread, (void*)(system_pairs+i)) != 0) - { - perror("In worker thread, couldn't create nested worker thread (force)"); - QuitProgram(true); - free(nested_workers); - pthread_exit(NULL); - } - } - } - //printf("Computed forces for %p\n", arg); - Barrier_Wait(&force_barrier); + //fprintf(stderr,"Thread %p - force barrier finished\n", arg); //printf("Computed ALL forces\n"); + + // If required, wait for the graphics to finish drawing stuff if (options.draw_graphics && options.pedantic_graphics) + { + //printf("Worker %p waits on graphics barrier\n", arg); Barrier_Wait(&graphics_barrier); + //printf("\tWorker %p wakes up after graphics barrier\n", arg); + if (ExitCondition()) return NULL; + } + - Barrier_Enter(&position_barrier); - System_Positions(s); + Barrier_Enter(&position_barrier); + System_Positions(s); // Each thread updates the positions for its share of bodies + - Barrier_Leave(&position_barrier); - //printf("Computed positions for %p\n", arg); - Barrier_Wait(&position_barrier); - //printf("Computed ALL positions\n"); + // Barrier_JoinCall behaves in the same way as Barrier_Join, except the *last* thread + // (ie: the one that wakes up the others) also calls the function with arguments given. + Barrier_JoinCall(&position_barrier, StepFunction, (void*)(&universe)); + if (ExitCondition()) return NULL; + //Barrier_Join(&position_barrier); + + // All threads have computed positions, and *one* thread calls StepFunction() + } - printf("Worker thread exits\n"); - QuitProgram(false); - pthread_exit(NULL); + QuitProgram(false); // Set the run state of the program + return NULL; } #endif //PERSISTENT_THREADS @@ -295,12 +319,11 @@ void * Worker_Thread(void * arg) */ void * Force_Thread(void * s) { - System_ForcePair * pair = (System_ForcePair*)s; - Barrier_Enter(&force_barrier); + //System_ForcePair * pair = (System_ForcePair*)s; - System_Forces(pair->A, pair->B); //Simple wrapper - - Barrier_Leave(&force_barrier); + + System_Forces(s, &universe); //Simple wrapper + //printf("Force_Thread waits\n"); return NULL; } @@ -313,118 +336,129 @@ void * Force_Thread(void * s) */ void * Position_Thread(void * s) { - Barrier_Enter(&position_barrier); + System_Positions((System*)s); // Simple wrapper - Barrier_Leave(&position_barrier); + Barrier_Join(&position_barrier); // This needed so that graphics will wait + return NULL; } /** * @function QuitProgram - * @purpose This function can either be called by the main thread in order to signal other threads - * that it wants to exit. The main thread then calls pthread_join before exiting. - * It can also be called by a child thread to request the main thread to exit. - * It is only used this way if there is an unrecovarable error (ie: Can't allocate memory in a child thread) + * @purpose This function can be called in any thread to signal all threads to exit + * Repeated calls to this function have no effect + * + * All threads periodically call ExitCondition(), which will return true if the program should exit. + * One (not the only way) to return true is if this function has been called. + * Threads will call this function if they detect ExitCondition() is true. Only the first call has any effect. */ -void QuitProgram(bool error) +inline void QuitProgram(bool error) { - if (runstate == QUIT || runstate == QUIT_ERROR) - return; //Don't do anything if already quitting + //If already quitting, don't do anything + if (runstate == QUIT || runstate == QUIT_ERROR) + return; + + + + // set the runstate (checked in ExitCondition()) + pthread_mutex_lock(&mutex_runstate); // aquire mutex - if (error) // set the runstate - runstate = QUIT_ERROR; + if (error) + runstate = QUIT_ERROR; // Program is exiting due to an error else - runstate = QUIT; + runstate = QUIT; // Program is exiting naturally pthread_mutex_unlock(&mutex_runstate); //release mutex + + } /** * @function Thread_Cleanup - * @purpose Will be called in the main thread when exit() is called - * Automatically tells all other threads to quit (if they haven't already been told) + * @purpose Will be called in the *main* thread when exit() is called + * Ensures working threads will exit, and waits for them to finish. * Then waits for them to finish. * Also frees memory associated with the worker threads. */ void Thread_Cleanup(void) { - if (runstate == RUN) // If this is true, as far as child threads are concerned, the simulation is still running - QuitProgram(false); // So call QuitProgram which will set runstate, and cause child threads to exit - pthread_join(compute_thread, NULL); - free(worker_thread); - free(sub_system); + + + // Threads recheck the exit condition whenever they leave a barrier. + // These calls will stop any threads waiting forever in a barrier for threads that exited before getting to the barrier. + Barrier_ForceExit(&force_barrier); + Barrier_ForceExit(&position_barrier); + + + if (options.draw_graphics) // If the graphics are enabled... + { + // Then there is a computation thread, since graphics are done in the main thread + pthread_join(compute_thread, NULL); + } + + #ifdef PERSISTENT_THREADS + for (unsigned i = 0; i < options.num_threads-1; ++i) + { + pthread_join(worker_thread[i], NULL); + } + #else + // All other worker threads (if they were spawned) are terminated in Compute_Thread + #endif //PERSISTENT_THREADS + + // Scary memory management here. + if (worker_thread != NULL) + free(worker_thread); + if (sub_system != NULL) + free(sub_system); + worker_thread = NULL; + sub_system = NULL; + } /** * @function Simulation_Run * @purpose Initialise and start the simulation. Will be called in the main thread. - * Replaces the single-threaded macro that does nothing, and sets up the compute thread + * Replaces the single-threaded macro that does nothing, and sets up the graphics and computation threads * @param argc - Number of arguments - Passed to Graphics_Run if needed * @param argv - Argument strings - Passed to Graphics_Run if needed */ void Simulation_Run(int argc, char ** argv) { atexit(Thread_Cleanup); - - Barrier_Init(&force_barrier); - Barrier_Init(&position_barrier); - Barrier_Init(&graphics_barrier); - - - if (options.draw_graphics) + if (options.draw_graphics) // The graphics are enabled { - // The graphics are enabled, so create a thread to do computations - // Graphics are done in the main loop - //printf("Graphics are enabled\n"); - #ifdef PERSISTENT_THREADS - Compute_Thread((void*)(&universe)); - #else + // I have chosen to do graphics in the main thread in this case. + // A *single* seperate thread is spawned here to do computations. + // This computation thread will spawn any additional worker threads required. if (pthread_create(&compute_thread, NULL, Compute_Thread, (void*)&universe) != 0) { perror("Error creating compute thread"); exit(EXIT_FAILURE); } - #endif //PERSISTENT_THREADS - //printf("Run compute thread\n"); - Graphics_Run(argc, argv); - } - else - - Compute_Thread((void*)(&universe)); // Graphics are disabled, so do computations in the main thread -} + // This is run in the main thread + // It is effectively the graphics initialisation, followed by the glut loop + Graphics_Run(argc, argv); + // The main thread reaches here after leaving the glut loop when ExitCondition() returns true. -void Barrier_Init(Barrier * b) -{ - b->threads_busy = 0; -} - -void Barrier_Enter(Barrier * b) -{ - pthread_mutex_lock(&(b->mutex)); - b->threads_busy += 1; - pthread_mutex_unlock(&(b->mutex)); -} + QuitProgram(false); -void Barrier_Leave(Barrier * b) -{ - pthread_mutex_lock(&(b->mutex)); - b->threads_busy -= 1; - if (b->threads_busy == 0) + exit(EXIT_SUCCESS); // This is the main thread; use exit() + + } + else //The graphics are disabled { - pthread_cond_signal(&(b->threads_done_cv)); + // If graphics are disabled, there is no point spawning an extra thread. + // In this case, the *main* thread starts computations. + // Note that it will probably spawn additional worker threads (unless options.num_threads <= 1) + Compute_Thread((void*)(&universe)); + QuitProgram(false); + exit(EXIT_SUCCESS); } - pthread_mutex_unlock(&(b->mutex)); } -void Barrier_Wait(Barrier * b) -{ - pthread_mutex_lock(&(b->mutex)); - while (b->threads_busy > 0) - pthread_cond_wait(&(b->threads_done_cv), &(b->mutex)); - pthread_mutex_unlock(&(b->mutex)); -} + /** * @function Split_System @@ -478,3 +512,25 @@ pthread_t * Allocate_Threads(unsigned n) } return result; } + +/** + * @function StepFunction + * @purpose Helper to perform stuff in a single thread every step, after position computations are done + * The reason this has void* all over the place is so that I can pass the function pointer (to horrible dragons and fiendish demons). + * @param arg - Can be cast to System* for which steps are to be updated + * Will always be (void*)(&universe). But I have been brainwashed into the "global variables are baaaaad" philosophy. + * @returns arg + */ +void * StepFunction(void * arg) +{ + //fprintf(stderr, "StepFunction called\n"); + System * s = (System*)(arg); + s->steps += 1; //Increment number of steps computed + + if (options.verbosity != 0 && s->steps % options.verbosity == 1) + DisplayStatistics(); + + + return arg; +} + diff --git a/course/semester2/pprog/assignment1/mthread/nbody.h b/course/semester2/pprog/assignment1/mthread/nbody.h index f2de0229..9948e524 100644 --- a/course/semester2/pprog/assignment1/mthread/nbody.h +++ b/course/semester2/pprog/assignment1/mthread/nbody.h @@ -4,13 +4,14 @@ #include "../single-thread/nbody.h" //Use original simulation code #include +#include "barrier.h" // Barriers #undef SINGLE_THREADED #define PTHREADED #define DEFAULT_WORKING_THREADS 2 -#define PERSISTENT_THREADS //If defined, threads will not be continually destroyed and then respawned +//#define PERSISTENT_THREADS //If defined, threads will not be continually destroyed and then respawned @@ -29,6 +30,8 @@ void AfterDraw(); void * Compute_Thread(void * system); //Thread - Continuously perform computations for a System of bodies. May spawn additional worker threads. + + System * Split_System(System * s, unsigned n); // Splits one system into a number of other systems, returns an array of size n pthread_t * Allocate_Threads(unsigned n); // Allocates space for threads - handles errors @@ -40,33 +43,8 @@ void * Position_Thread(void * system); //Thread - Compute positions for all obje void Thread_Cleanup(void); //Called at program exit to safely join computation thread -/** - * Structure to represent a barrier for multiple threads - * @param mutex - Mutex around the counter - * @param busy - Counter of threads within the barrier - * @param threads_done_cv - Condition to wake up threads waiting on barrier once all working threads have left it - */ -typedef struct -{ - pthread_mutex_t mutex; - unsigned threads_busy; // Counter of threads which are busy - pthread_cond_t threads_done_cv; -} Barrier; - -/** - * Structure to represent a pair of Systems; passed to Force_Thread - * @param A - System to calculate forces for - * @param B - System causing forces on System A - */ -typedef struct -{ - System * A; - System * B; -} System_ForcePair; - -void Barrier_Init(Barrier * b); -void Barrier_Enter(Barrier * b); -void Barrier_Leave(Barrier * b); -void Barrier_Wait(Barrier * b); + + +void * StepFunction(void * s); //Function called at the end of every step #endif //_NBODY_MTHREAD_H diff --git a/course/semester2/pprog/assignment1/mthread/test_barrier.c b/course/semester2/pprog/assignment1/mthread/test_barrier.c new file mode 100644 index 00000000..d2efebb1 --- /dev/null +++ b/course/semester2/pprog/assignment1/mthread/test_barrier.c @@ -0,0 +1,90 @@ +#include "barrier.h" +#include +#include +#include +#include + +#include "barrier.c" + +/** + * @file test_barrier.c + * @purpose Test the barrier! + * @author Sam Moore (20503628) 2012 + */ + +// Compile with gcc --std=c99 -o test_barrier test_barrier.c -lpthread +// Run with ./test_barrier +// Expected output: +// All worker threads print "Hello" before all worker threads print "Goodbye" +// At any point, the "Boss" reports that it is waiting for "Hello" to be said, and goes to sleep. +// At some point whilst worker threads say "Goodbye", the "Boss" wakes up and reports that "Hello" has been said. +// The Boss should *never* report that "Hello" has been said until all workers say "Hello". +// But it may report that it is waiting for "Hello" to be said at any time. +// Confused yet? I am, and I wrote this! +// NOTE: Two barriers are required to prevent deadlocking! + + +pthread_mutex_t mutex_printf; // Printing is not thread safe! Mutex needed! +Barrier worker_barrier[2]; + +void * Worker(void * args) +{ + + //while (true) // Comment out to only test one step + { + Barrier_Enter(&worker_barrier[0]); // Workers are now printing "Hello" + + pthread_mutex_lock(&mutex_printf); + printf("Worker %p: Hello\n", (args)); // Only one thread can print at a time + pthread_mutex_unlock(&mutex_printf); + + sleep(1); + Barrier_Join(&worker_barrier[0]); + + pthread_mutex_lock(&mutex_printf); + printf("\tWorker %p: Goodbye\n", (args)); // Only one thread can print at a time + pthread_mutex_unlock(&mutex_printf); + + + Barrier_Join(&worker_barrier[1]); // If we replace this with: + //Barrier_Join(&worker_barrier[0]); + // Then there is (usually) a deadlock, because the first thread to leave the barrier + // immediately reactivates it, and other threads are unable to leave. + + } +} + +void * Boss(void * args) +{ + //while (true) + { + pthread_mutex_lock(&mutex_printf); + printf("Boss is waiting for \"Hello\" to be said.\n", (args)); // Only one thread can print at a time + pthread_mutex_unlock(&mutex_printf); + Barrier_Wait(&worker_barrier[0]); + pthread_mutex_lock(&mutex_printf); + printf("Boss continues now that workers have said \"Hello\".\n", (args)); // Only one thread can print at a time + pthread_mutex_unlock(&mutex_printf); + sleep(2); + + } +} + +int main(int argc, char ** argv) +{ + #define NUM_THREADS 4 + pthread_t worker[NUM_THREADS]; + pthread_t boss; + + Barrier_Init(&worker_barrier[0], NUM_THREADS); // Initialise barriers with number of contributing threads + Barrier_Init(&worker_barrier[1], NUM_THREADS); + + pthread_create(&boss, NULL, Boss, NULL); //Start boss + + for (long i = 0; i < NUM_THREADS; ++i) + pthread_create(worker+i, NULL, Worker, (void*)(i)); // Start all threads + + for (int i = 0; i < NUM_THREADS; ++i) // Join threads + pthread_join(worker[i], NULL); + return 0; +} diff --git a/course/semester2/pprog/assignment1/openmp/nbody.h b/course/semester2/pprog/assignment1/openmp/nbody.h index 91c4bf4f..3e3d2718 100644 --- a/course/semester2/pprog/assignment1/openmp/nbody.h +++ b/course/semester2/pprog/assignment1/openmp/nbody.h @@ -14,6 +14,9 @@ #undef SINGLE_THREADED #define OMP_THREADED + +#define CRAPPY_VERSION + // Replace default macros with thread-safe functions #undef Simulation_Run void Simulation_Run(int argc, char ** argv); diff --git a/course/semester2/pprog/assignment1/single-thread/graphics.c b/course/semester2/pprog/assignment1/single-thread/graphics.c index 32ca0801..c3d55f52 100644 --- a/course/semester2/pprog/assignment1/single-thread/graphics.c +++ b/course/semester2/pprog/assignment1/single-thread/graphics.c @@ -14,7 +14,7 @@ #include "graphics.h" //Function declarations #include "nbody.h" //The simulation -#include + // --- Variable definitions --- // double previousTime, eyeTheta, eyePhi, eyeRho; @@ -99,12 +99,18 @@ void Graphics_Run(int argc, char ** argv) gluPerspective(VIEW_ANGLE, (double)WIDTH/(double)HEIGHT, WORLD_NEAR, WORLD_FAR); #ifdef FLYING_CAMERA - eye.x[0] = 1; eye.x[1] = 0; eye.x[2] = 0; + eye.x[0] = 0; eye.x[1] = 0; eye.x[2] = 1; eye.y[0] = 0; eye.y[1] = 1; eye.y[2] = 0; - eye.z[0] = 0; eye.z[1] = 0; eye.z[2] = 1; + eye.z[0] = 1; eye.z[1] = 0; eye.z[2] = 0; + eye.p[0] = 0; eye.p[1] = 0; eye.p[2] = -50; #endif //FLYING_CAMERA - glutMainLoop(); + + //This option must be set, or when glut leaves the main loop. the exit(3) function is called... annoying + glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION); + + glutMainLoop(); + } /** @@ -135,6 +141,7 @@ void Graphics_Display() glLoadIdentity(); #ifdef FLYING_CAMERA + gluLookAt(eye.p[0], eye.p[1], eye.p[2], eye.p[0] + eye.x[0], eye.p[1] + eye.x[1], eye.p[2] + eye.x[2], eye.z[0], eye.z[1], eye.z[2]); @@ -150,6 +157,7 @@ void Graphics_Display() for (int i = 0; i < universe.N; ++i) { + Body * b = universe.body+i; glColor3f(0.0f, b->mass/1e11*100, 0.0f); //glColor3f(1.0f, 0.0f, 0.0f); @@ -183,37 +191,45 @@ void Graphics_Keyboard(unsigned char theKey, int mouseX, int mouseY) #ifdef FLYING_CAMERA switch (theKey) { + // Translate in direction of camera case 'W': case 'w': for (unsigned i = 0; i < DIMENSIONS; ++i) eye.p[i] += eye.x[i]; break; + // Translate backwards from camera direction case 'S': case 's': for (unsigned i = 0; i < DIMENSIONS; ++i) eye.p[i] -= eye.x[i]; break; + // Translate left from camera direction case 'A': case 'a': for (unsigned i = 0; i < DIMENSIONS; ++i) - eye.p[i] += eye.y[i]; + eye.p[i] -= eye.y[i]; break; + // Translate right from camera direction case 'D': case 'd': for (unsigned i = 0; i < DIMENSIONS; ++i) - eye.p[i] -= eye.y[i]; + eye.p[i] += eye.y[i]; break; - case 'Z': - case 'z': + // Translate up from camera direction + case 'Q': + case 'q': for (unsigned i = 0; i < DIMENSIONS; ++i) eye.p[i] += eye.z[i]; break; - case 'C': - case 'c': + // Translate down from camera direction + case 'E': + case 'e': for (unsigned i = 0; i < DIMENSIONS; ++i) eye.p[i] -= eye.z[i]; break; + // Rotate camera direction "down" + // (pitch control) case 'I': case 'i': { @@ -227,6 +243,8 @@ void Graphics_Keyboard(unsigned char theKey, int mouseX, int mouseY) } break; } + // Rotate camera direction "up" + // (pitch control) case 'K': case 'k': { @@ -240,11 +258,12 @@ void Graphics_Keyboard(unsigned char theKey, int mouseX, int mouseY) } break; } - + // Rotate camera direction "left" + // (yaw control) case 'J': case 'j': { - float theta = -M_PI/80.0; + float theta = +M_PI/80.0; Camera old = eye; for (unsigned i = 0; i < DIMENSIONS; ++i) @@ -254,11 +273,12 @@ void Graphics_Keyboard(unsigned char theKey, int mouseX, int mouseY) } break; } - + // Rotate camera direction "right" + // (yaw control) case 'L': case 'l': { - float theta = M_PI/80.0; + float theta = -M_PI/80.0; Camera old = eye; for (unsigned i = 0; i < DIMENSIONS; ++i) @@ -268,11 +288,12 @@ void Graphics_Keyboard(unsigned char theKey, int mouseX, int mouseY) } break; } - - case 'Q': - case 'q': + // Rotate camera direction CCW about its axis + // (roll control) + case 'U': + case 'u': { - float theta = M_PI/80.0; + float theta = -M_PI/80.0; Camera old = eye; for (unsigned i = 0; i < DIMENSIONS; ++i) @@ -282,10 +303,12 @@ void Graphics_Keyboard(unsigned char theKey, int mouseX, int mouseY) } break; } - case 'E': - case 'e': + // Rotate camera direction CW about its axis + // (roll control) + case 'O': + case 'o': { - float theta = -M_PI/80.0; + float theta = +M_PI/80.0; Camera old = eye; for (unsigned i = 0; i < DIMENSIONS; ++i) @@ -297,31 +320,17 @@ void Graphics_Keyboard(unsigned char theKey, int mouseX, int mouseY) } } - /* - float x = 0; float y = 0; float z = 0; - for (unsigned i = 0; i < DIMENSIONS; ++i) - { - x += eye.x[i] * eye.x[i]; - y += eye.y[i] * eye.y[i]; - z += eye.z[i] * eye.z[i]; - } - for (unsigned i = 0; i < DIMENSIONS; ++i) - { - eye.x[i] /= sqrt(x); - eye.y[i] /= sqrt(y); - eye.z[i] /= sqrt(z); - } - */ + /* Code used for debugging the camera system("clear"); printf("Camera status:\n"); printf("Position: %f %f %f\n", eye.p[0], eye.p[1], eye.p[2]); printf("x: %f %f %f (%f)\n", eye.x[0], eye.x[1], eye.x[2], sqrt(eye.x[0]*eye.x[0] + eye.x[1]*eye.x[1] + eye.x[2]*eye.x[2])); printf("y: %f %f %f (%f)\n", eye.y[0], eye.y[1], eye.y[2], sqrt(eye.y[0]*eye.y[0] + eye.y[1]*eye.y[1] + eye.y[2]*eye.y[2])); printf("z: %f %f %f (%f)\n", eye.z[0], eye.z[1], eye.z[2], sqrt(eye.z[0]*eye.z[0] + eye.z[1]*eye.z[1] + eye.z[2]*eye.z[2])); - - #else - + */ + #else // The original view code + // I like how 'I' is used three times. if (theKey == 'i' || theKey == 'I') { eyePhi -= M_PI / 20; diff --git a/course/semester2/pprog/assignment1/single-thread/graphics.h b/course/semester2/pprog/assignment1/single-thread/graphics.h index 97ccf472..24a7a331 100644 --- a/course/semester2/pprog/assignment1/single-thread/graphics.h +++ b/course/semester2/pprog/assignment1/single-thread/graphics.h @@ -9,7 +9,14 @@ */ #include + +#define GLUT +//#define SDL + +#ifdef GLUT #include +#include +#endif //GLUT #include "nbody.h" diff --git a/course/semester2/pprog/assignment1/single-thread/main.c b/course/semester2/pprog/assignment1/single-thread/main.c index e9ccb411..e55c75f9 100644 --- a/course/semester2/pprog/assignment1/single-thread/main.c +++ b/course/semester2/pprog/assignment1/single-thread/main.c @@ -21,11 +21,12 @@ Options options; // global variable declared in "nbody.h" - Options passed to pr // --- Function forward declarations --- // void HandleArguments(int argc, char ** argv); //Interprets program arguments and sets up the "options" variable -unsigned UnsignedArgument(unsigned * i, int argc, char ** argv, unsigned * store, unsigned * store2); //Helper function to get switch value for unsigned +unsigned IntegerArgument(unsigned * i, int argc, char ** argv, int * store, int * store2); //Helper function to get integer switch void FloatArgument(unsigned * i, int argc, char ** argv, float * store); //Helper function to get switch value for float void DisplayStatistics(); //Called on exit of program, displays information about computation time, steps computed, etc void Interrupt(int dummy); // Interrupt handler function, called when SIGINT (Ctrl-C) is sent to program + // --- Function implementations --- // /** @@ -43,8 +44,8 @@ int main(int argc, char** argv) options.output = NULL; options.num_threads = 0; options.nested_threads = 0; - options.num_steps = 0; - options.timeout = 0; + options.num_steps = -1; // Negative values => simulation runs forever unless otherwise specified + options.timeout = -1; options.draw_graphics = true; options.pedantic_graphics = false; options.print_positions = false; @@ -70,6 +71,8 @@ int main(int argc, char** argv) exit(EXIT_FAILURE); } + + signal(SIGINT, Interrupt); //Handle SIGINT signals atexit(Universe_Cleanup); //On exit, cleanup universe (and write positions of bodies to file if supplied). atexit(DisplayStatistics); //On exit, print information about the computations done @@ -85,8 +88,10 @@ int main(int argc, char** argv) options.start_clock = clock(); //Get CPU cycles executed before simulation starts Simulation_Run(argc, argv); // Start the simulation - //printf("Got here!\n"); - exit(EXIT_FAILURE); //Should never get to this line + //printf("Main thread done!\n"); + + //pthread_exit(NULL); + exit(EXIT_SUCCESS); //Should never get to this line } @@ -119,34 +124,50 @@ void HandleArguments(int argc, char ** argv) switch (argv[i][1]) //The argument is a switch if we get here { case 'n': //Number of threads switch - UnsignedArgument(&i, argc, argv, &(options.num_threads), &(options.nested_threads)); + IntegerArgument(&i, argc, argv, &(options.num_threads), &(options.nested_threads)); #ifdef SINGLE_THREADED - fprintf(stderr, "Warning: -%c switch has no effect in the single-threaded program.\n", argv[i][1]); + fprintf(stderr, "Warning: -%c switch has no effect in the single-threaded program.\n", 'n'); + #else + if (options.num_threads <= 0) + { + fprintf(stderr, "Require at least one thread (-%c %s is invalid).\n",'n', argv[i]); + exit(EXIT_FAILURE); + } #endif //SINGLE_THREADED break; case 's': //Number of steps switch - UnsignedArgument(&i, argc, argv, &(options.num_steps), NULL); + IntegerArgument(&i, argc, argv, &(options.num_steps), NULL); + if (options.num_steps < 0) + { + fprintf(stderr, "Require zero or more steps to run (-%c %s is invalid).\n", 's', argv[i]); + exit(EXIT_FAILURE); + } break; case 't': //Timeout switch (in seconds) - UnsignedArgument(&i, argc, argv, &(options.timeout), NULL); + IntegerArgument(&i, argc, argv, &(options.timeout), NULL); + if (options.timeout < 0) + { + fprintf(stderr, "Require a timeout greater or equal to zero (-%c %s is invalid).", 't', argv[i]); + exit(EXIT_FAILURE); + } break; case 'g': //Graphics switch options.draw_graphics = !options.draw_graphics; break; case 'v': //Verbosity switch - UnsignedArgument(&i, argc, argv, &(options.verbosity), NULL); + IntegerArgument(&i, argc, argv, &(options.verbosity), NULL); break; case '-': - if (strcmp(argv[i]+1, "pedantic-graphics")) + if (strcmp(argv[i]+2, "pedantic-graphics") == 0) { options.pedantic_graphics = true; #ifdef SINGLE_THREADED fprintf(stderr, "Warning: %s switch has no effect in the single threaded program.\n", argv[i]); #endif //SINGLE_THREADED } - else if (strcmp(argv[i]+1, "fast-graphics")) + else if (strcmp(argv[i]+2, "fast-graphics") == 0) { options.pedantic_graphics = false; #ifdef SINGLE_THREADED @@ -156,7 +177,7 @@ void HandleArguments(int argc, char ** argv) } else { - fprintf(stderr, "Unrecognised switch -%s\n", argv[i]); + fprintf(stderr, "Unrecognised switch %s\n", argv[i]); exit(EXIT_FAILURE); } break; @@ -172,16 +193,16 @@ void HandleArguments(int argc, char ** argv) } /** - * @function UnsignedArgument - * @purpose Helper function to get an unsigned integer following a argument switch + * @function IntegerArgument + * @purpose Helper function to get up to two integers following a argument switch, seperated by ':' * @param i - position in the argument array, will be updated after this function * @param argc - number of arguments * @param argv - argument strings * @param store - pointer to unsigned to store result in - * @param store2 - pointer to second unsigned + * @param store2 - pointer to second integer (set to NULL if there is only one integer) * @returns 1 if store was filled, 2 if both store1 and store2 were filled */ -unsigned UnsignedArgument(unsigned * i, int argc, char ** argv, unsigned * store, unsigned * store2) +unsigned IntegerArgument(unsigned * i, int argc, char ** argv, int * store, int * store2) { if (*i >= argc-1) { @@ -209,12 +230,12 @@ unsigned UnsignedArgument(unsigned * i, int argc, char ** argv, unsigned * store } int val = atoi(argv[*i+1]); - if (val <= 0) + if (val <= 0 && strcmp("0", argv[*i+1]) != 0) { fprintf(stderr,"Supply a positive integer for the -%c switch. %s is invalid.\n", argv[*i][1], argv[*i+1]); exit(EXIT_FAILURE); } - *store = (unsigned)val; + *store = val; *i += 1; return (seperator == NULL) ? 1 : 2; @@ -256,3 +277,4 @@ void Interrupt(int dummy) QuitProgram(false); } + diff --git a/course/semester2/pprog/assignment1/single-thread/nbody.c b/course/semester2/pprog/assignment1/single-thread/nbody.c index e4b4bc70..ee510ed4 100644 --- a/course/semester2/pprog/assignment1/single-thread/nbody.c +++ b/course/semester2/pprog/assignment1/single-thread/nbody.c @@ -23,10 +23,12 @@ RUNSTATE runstate = RUN; * @param a - Body to print' * @param out - FILE* to print to */ -void Body_Print(Body * a, FILE * out) +inline void Body_Print(Body * a, FILE * out) { - fprintf(out,"Body %p M=%f X=%f Y=%f Z=%f Fx=%f Fy=%f Fz=%f Vx=%f Vy=%f Vz=%f\n", - (void*)a, a->mass, a->x[0], a->x[1], a->x[2], a->F[0], a->F[1], a->F[2], a->v[0], a->v[1], a->v[2]); + //fprintf(out,"Body %p M=%f X=%f Y=%f Z=%f Fx=%f Fy=%f Fz=%f Vx=%f Vy=%f Vz=%f\n", + //(void*)a, a->mass, a->x[0], a->x[1], a->x[2], a->F[0], a->F[1], a->F[2], a->v[0], a->v[1], a->v[2]); + fprintf(out, "%f %f %f %f %f %f %f %f %f %f\n", + a->mass, a->x[0], a->x[1], a->x[2], a->v[0], a->v[1], a->v[2], a->F[0], a->F[1], a->F[2]); } /** @@ -35,7 +37,7 @@ void Body_Print(Body * a, FILE * out) * @param a - The Body * @param b - The System */ -void Body_Force(Body * a, System * s) +inline void Body_Force(Body * a, System * s) { double distance; double con; @@ -67,7 +69,7 @@ void Body_Force(Body * a, System * s) * @purpose Compute velocity of a body * @param a - The Body */ -void Body_Velocity(Body * a) +inline void Body_Velocity(Body * a) { for (unsigned i = 0; i < DIMENSIONS; ++i) a->v[i] += a->F[i] / a->mass * DELTA_T; @@ -78,7 +80,7 @@ void Body_Velocity(Body * a) * @purpose Compute position of a body * @param a - The Body */ -void Body_Position(Body * a) +inline void Body_Position(Body * a) { for (unsigned i = 0; i < DIMENSIONS; ++i) a->x[i] += a->v[i] * DELTA_T; @@ -89,7 +91,7 @@ void Body_Position(Body * a) * @purpose Compute forces and then positions for bodies in System * NOTE: Only used in the single threaded version of the program */ -void System_Compute(System * s) +inline void System_Compute(System * s) { System_Forces(s, s); System_Positions(s); @@ -103,7 +105,7 @@ void System_Compute(System * s) * In multi threaded version, called with s2 == &universe, but s1 is constructed for each thread * In nested multi-threaded version, s2 is constructed for the nested threads as well */ -void System_Forces(System * s1, System * s2) +inline void System_Forces(System * s1, System * s2) { //printf("Compute forces for %p - %d bodies...\n", (void*)s1, s1->N); for (unsigned i = 0; i < s1->N; ++i) @@ -119,13 +121,13 @@ void System_Forces(System * s1, System * s2) * @purpose Compute positions of all objects in System * @param s - The system */ -void System_Positions(System * s) +inline void System_Positions(System * s) { //printf("Compute positions for %p - %d bodies...\n", (void*)s, s->N); for (unsigned i = 0; i < s->N; ++i) Body_Position(s->body+i); //printf("Compute positions for %p - Done!\n", (void*)s); - s->steps += 1; + } @@ -137,7 +139,7 @@ void System_Positions(System * s) * @param s - The System * @param fileName - The input file */ -void System_Init(System * s, const char *fileName) +inline void System_Init(System * s, const char *fileName) { char line[LINE_SIZE]; char * token; @@ -155,19 +157,23 @@ void System_Init(System * s, const char *fileName) if (fgets(line, LINE_SIZE, file) != NULL) { Body * a = s->body+i; - token = strtok(line, ",; "); + token = strtok(line, ",; \t"); a->mass = atof(token); for (unsigned j = 0; j < DIMENSIONS; ++j) { - token = strtok(NULL, ",; "); + token = strtok(NULL, ",; \t"); a->x[j] = atof(token); } for (unsigned j = 0; j < DIMENSIONS; ++j) { - token = strtok(NULL, ",; "); + token = strtok(NULL, ",; \t"); a->v[j] = atof(token); } + + // Ignore any additional tokens + while (token != NULL) + token = strtok(NULL, ",; \t"); //Body_Print(a); } } @@ -182,19 +188,30 @@ void System_Init(System * s, const char *fileName) * @purpose Cleans up the universe by freeing all memory * Also prints the bodies in the universe to a file specified in "options" if required. */ -void Universe_Cleanup() +inline void Universe_Cleanup() { + //fprintf(stderr, "Called Universe_Cleanup()\n"); if (options.output != NULL) // An output file was specified... { - FILE * save = fopen(options.output, "w"); + FILE * save = NULL; + if (strcmp("stdout", options.output) == 0) + save = stdout; + else if (strcmp("stderr", options.output) == 0) + save = stderr; + else + save = fopen(options.output, "w"); if (save == NULL) perror("Couldn't save universe to file."); else { - fprintf(save, "# Final field:\n"); + // Print the output in the same format as the initial field file + fprintf(save, "%u\n", universe.N); + for (unsigned i = 0; i < universe.N; ++i) // Print all bodies to the file { - Body_Print(universe.body+i, save); + Body_Print(universe.body+i, save); + + } fclose(save); } @@ -212,7 +229,7 @@ void Universe_Cleanup() * - Clock cycles executed * - Equivelant time for a single thread to execute same number of clock cycles */ -void DisplayStatistics() +inline void DisplayStatistics() { clock_t end = clock(); struct timeval end_time; @@ -232,11 +249,16 @@ void DisplayStatistics() * @purpose Helper to check whether the program is supposed to exit * Does not check for user triggered quit * Checks for either a timeout, or computation of the required number of steps + * + * The reason timeouts are integers is because this function is called a lot in lots of threads + * So using floats (and working out microseconds every time) is expensive + * */ -bool ExitCondition(void) +inline bool ExitCondition(void) { - bool result = (runstate != RUN || (options.timeout > 0.00 && ((unsigned)(time(NULL) - options.start_time.tv_sec) >= options.timeout)) - || (options.num_steps > 0 && universe.steps > options.num_steps)); - //printf("runstate %d\n timeout %d\n steps %d\n", (int)(runstate != RUN), (int)(options.timeout > 0.00 && ((unsigned)(time(NULL) - options.start_time.tv_sec) >= options.timeout)), (int)(options.num_steps > 0 && universe.steps > options.num_steps)); + bool result = (runstate != RUN || + (options.num_steps >= 0 && universe.steps >= options.num_steps) || + (options.timeout >= 0 && ((int)(time(NULL) - options.start_time.tv_sec) >= options.timeout))); + //fprintf(stderr,"runstate %d\n timeout %d\n steps %d\n", (int)(runstate != RUN), (int)(options.timeout > 0.00 && ((unsigned)(time(NULL) - options.start_time.tv_sec) >= options.timeout)), (int)(options.num_steps > 0 && universe.steps > options.num_steps)); return result; } diff --git a/course/semester2/pprog/assignment1/single-thread/nbody.h b/course/semester2/pprog/assignment1/single-thread/nbody.h index 4d53f9d0..a9bfec44 100644 --- a/course/semester2/pprog/assignment1/single-thread/nbody.h +++ b/course/semester2/pprog/assignment1/single-thread/nbody.h @@ -33,9 +33,11 @@ else \ //Macro to be overwritten in multithreaded versions, called before the graphics is allowed to draw anything #define BeforeDraw() \ +System_Compute(&universe); \ +universe.steps += 1; \ if (options.verbosity != 0 && universe.steps % options.verbosity == 0) \ DisplayStatistics(); \ -System_Compute(&universe); + @@ -93,14 +95,14 @@ typedef struct const char * input; // initial body field const char * output; // file to write final positions / velocities of bodies to const char * program; // program name - unsigned num_threads; // number of worker threads to spawn (must be greater than 1 for any to be spawned) - unsigned nested_threads; // number of threads to nest computations with (must be greater than 1 for any to be spawned) - unsigned num_steps; // number of steps to run before stopping (run indefinately if equal to zero) - unsigned timeout; // number of seconds to run before stopping (run indefinately if equal to zero) + int num_threads; // number of worker threads to spawn (must be greater than 1 for any to be spawned) + int nested_threads; // number of threads to nest computations with (must be greater than 1 for any to be spawned) + int num_steps; // number of steps to run before stopping (run indefinately if less than zero) + int timeout; // number of seconds to run before stopping (run indefinately if less than zero) bool draw_graphics; // whether or not to actually draw graphics bool pedantic_graphics; // whether the graphics thread will synchronise with the computation thread (true) or just draw as fast as possible (false) bool print_positions; // print positions of bodies to stdout on every step - unsigned verbosity; // print statistics every number of steps indicated by this variable + int verbosity; // print statistics every number of steps indicated by this variable clock_t start_clock; // clock cycles done when simulation starts struct timeval start_time; // time at which simulation starts } Options;