CS 420 - Assignment 3

Due: Tuesday, October 2nd by 11:59 PM

Parallel game of life

Updated: Friday, September 28th

In this assignment you will add support for multithreaded operation to the game of life program.  By running the simulation with two threads on a dual-core machine, you should be able to measure a significant speedup compared to the single-threaded version.

Getting Started

Download CS420_Assign3.zip.  Extract it.  Copy your versions of life.h and life.c from assignment 1 into the LifeLib subdirectory.  You can work on the project using Visual Studio (just open the solution file) or from the command line using the provided Makefile.  The program should be able to compile and run on any OS supporting POSIX threads (pthreads), including Linux and Solaris.  The zipfile contains the pthreads-win32 library which also allows the use of the pthreads API on Windows.

Your task is to implement the functions in the file worker.c in the LifeProg directory.  Each function is documented in the header file worker.h.

In this project, LifeProg has been modified to support both sequential (single CPU) and parallel (multiple CPU) operation.  To run LifeProg, compile the program, open a terminal window, change directory to the base directory of the project, and run one of the following commands (depending on system):

Windows using Visual Studio:

debug/LifeProg.exe inputfile numgens mode

Linux, Solaris, Mac OS X, or Windows using cygwin/gcc:

./lifeprog inputfile numgens mode

Inputfile is the name of the input file.  You can use the provided file called random-500x1000.txt as an example of a large input problem.

Numgens is the number of generations to compute.

Mode is ether S or P, meaning Sequential or Parallel.  Sequential mode executes the game of life simulation on a single CPU.  Parallel mode divides the overall game board into 2 roughly equal halves, and simulates each one in a separate thread.  Assuming you have two CPU cores, each thread can execute in parallel.  Because each thread is working on a problem about half the size of the overall problem, together the two threads should be able to finish computing each generation in about half the time it would take a single processor.

Theory of operation

The updated version of LifeProg uses worker threads to parallelize the execution of the game of life simulation.  Your task is to implement the worker threads using your existing game board library (the game_board functions).  The worker thread API (which you will implement) is defined in the files worker.h and worker.c.

The state information of a worker thread is contained in a data structure called struct worker.  This struct is defined in worker.h as follows:

/*
* Data structure collecting all of the data
* used by a worker thread.
*/
struct worker {
/* game board representing part of the overall game board */
struct game_board *gb;

/* which generation the worker has computed: should be increased by 1
* each time the worker computes a generation */
int gen;

/* what state the worker is in (WAIT, WORK, or SHUTDOWN) */
int state;

/* mutex used to protect shared data */
pthread_mutex_t mutex;

/* condition variable used to broadcast notification of changes to shared data */
pthread_cond_t cond;

/* thread identifier */
pthread_t thread_id;
};

The worker data structure contains the game board data, state information, and a mutex and condition variable which you use for synchronization with the main thread.

You will need to implement the functions described in worker.h: worker_create, worker_start, worker_compute_next_gen_async, worker_wait_for_completion, worker_shutdown, and worker_destroy.  The comments describe how each function should work.  (Some of the functions are already partly or completely implemented in worker.c.)

You will also need to implement the worker thread start function, which is the code that each worker thread executes.  This function is called worker_run, and is defined in worker.c.  It should execute a loop that works as follows:

while (true) {
wait for the main thread to give this worker an instruction

if (the instruction is to shut down) {
break;
}

compute another generation

notify the main thread that this worker has completed another generation
}

The idea is that each iteration of the loop computes one generation using the worker thread's game board.  Eventually the main thread will call the worker_shutdown function, which should send a notification to the worker thread to shut down.  When the worker thread receives the shutdown notification, it breaks out of the loop, returning from the start function and causing the worker thread to exit.

Hints

Your main challenge will be correctly implementing the communication between the worker functions (which are called by the main thread) and the worker thread.

The general idea is that the worker thread is in the WAIT state while it is waiting to receive an instruction from the main thread.  When the main thread wants to issue an instruction to a worker, it should wait until the worker is in the WAIT state.

In order to issue an instruction, the main thread should change the worker's state to WORK or SHUTDOWN and notify the worker by doing a cond_broadcast() on the worker's condition variable.

The worker thread must notify the main thread when it is done computing a generation (since the worker_wait_for_completion() function should not return until the worker has completely computed the requested generation.)

See the pthreads API documentation for more information on the pthreads functions.

The lecture notes on pthreads may be useful.  Also see the code examples.

Performance Analysis

The file report.txt contained in the top-level directory of the solution contains instructions for how you should measure the performance of the program.  You should show running times for both sequential and parallel modes for the input file random-500x1000.txt for 50, 100, 200, and 400 generations.  Also, show speed up (sequential running time divided by parallel running time) for each data point (50 generations, 100 generations, etc.)

You can measure the running time using the Unix/cygwin time command: e.g.

time ./debug/LifeProg.exe inputfile numgens mode > outputfile

or

time ./lifeprog inputfile numgens mode > outputfile

Important: make sure you are running the program on a dual-core machine!  All of the machines in GH 123 or GH 125 have dual-core CPUs.

Important: The "> outputfile" part of the command saves the output to a file.  This is somewhat important, because writing to a file is generally faster than writing to a console window.

The "real" time is what you want to record.  Do the timing runs on an idle system.

Submitting

Please create a zip file of your completed assignment (both your program and your report) and submit using the Marmoset server:

https://camel.ycp.edu:8443/

This assignment is Project 3.

If you install the Marmoset Add-in for Visual Studio, you can submit automatically from within Visual Studio.  (Instructions are available.)  Just choose Tools->Submit... from the menu, type your Marmoset username and password, and click Submit.  If the upload succeeds you should see the following dialog:

If the submission does not succeed, create the zip file manually submit through a web browser. (Please clean the solution and delete program database (.pdb) files before submitting.)