/* * Josh Holtrop * 2008-12-11 * CS677 Final Project * This program implements a fractal-image generator and viewer * that uses OpenMPI and OpenMP. */ #include #include #include /* struct timeval, gettimeofday() */ #include #include #include "Computation.h" #include "NewtonComputation.h" #include "FatouComputation.h" using namespace std; #define PROGNAME "Josh's CS677 Final : MPI Fractal Generator" #define getXVirt(x) (((x) - (width >> 1)) * zoom + x_center) #define getYVirt(y) ((-((y) - (height >> 1))) * zoom + y_center) /************************************************************************** * Utility functions * *************************************************************************/ bool createWindow(int width, int height, SDL_Surface ** screen, Uint32 ** pixels); void getSizes(int * rank, int * size, int * nprocs); void draw(int rank, int world_size, int nprocs, int width, int height, Uint32 * pixels, Uint32 * taskVals, Computation * computation); void sendWindowVals(double * winVals, int world_size); /************************************************************************** * Global variables * *************************************************************************/ static double x_center = 0.0; static double y_center = 0.0; static double zoom = 1/300.0; /* a "task" will be processing task_size pixels */ static int task_size = 100; /************************************************************************** * This is the main entry point for our program * *************************************************************************/ int main(int argc, char * argv[]) { int width = 600; int height = 600; int my_rank = 0; int world_size = 0; int nprocs = 0; Computation * computation = NULL; int fractal_type = 0; bool display_times = false; SDL_Surface * screen; Uint32 * pixels; MPI_Init(&argc, &argv); for (int i = 1; i < argc; i++) { if (!strncmp(argv[i], "-w", 2)) { width = atoi(strlen(argv[i]) > 2 ? argv[i] + 2 : argv[++i]); } else if (!strncmp(argv[i], "-h", 2)) { height = atoi(strlen(argv[i]) > 2 ? argv[i] + 2 : argv[++i]); } else if (!strncmp(argv[i], "-t", 2)) { fractal_type = atoi(strlen(argv[i]) > 2 ? argv[i] + 2 : argv[++i]); } else if (!strcmp(argv[i], "--times")) { display_times = true; } else if (!strcmp(argv[i], "--no-threads")) { omp_set_num_threads(1); } else if (!strncmp(argv[i], "-s", 2)) { task_size = atoi(strlen(argv[i]) > 2 ? argv[i] + 2 : argv[++i]); } } getSizes(&my_rank, &world_size, &nprocs); if (my_rank == 0) { char hostname[1000]; gethostname(&hostname[0], 1000); cout << "Master hostname: " << hostname << endl; } switch (fractal_type) { case 0: default: computation = new NewtonComputation(); x_center = 0.0; y_center = 0.0; zoom = 2.0 / width; break; case 1: computation = new FatouComputation(); x_center = 3.001; y_center = 0.075975; zoom = 2.0 / width; break; } unsigned int * taskVals = new unsigned int[task_size + 1]; double window_vals[4]; if (my_rank == 0) { SDL_Event event; bool going = true; bool window_success = createWindow(width, height, &screen, &pixels); bool redraw = true; if (!window_success) going = false; /* master loop */ while (going && SDL_WaitEvent(&event) != 0) { if (redraw) { struct timeval before, after; window_vals[0] = 0.0; window_vals[1] = x_center; window_vals[2] = y_center; window_vals[3] = zoom; sendWindowVals(&window_vals[0], world_size); gettimeofday(&before, NULL); draw(my_rank, world_size, nprocs, width, height, pixels, taskVals, computation); gettimeofday(&after, NULL); if (display_times) { double time_before = before.tv_sec + before.tv_usec / 1000000.0; double time_after = after.tv_sec + after.tv_usec / 1000000.0; double diff = time_after - time_before; cout << "Elapsed time: " << diff << " seconds." << endl; } redraw = false; } SDL_UpdateRect(screen, 0, 0, 0, 0); switch (event.type) { case SDL_QUIT: going = false; break; case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_q) going = false; break; case SDL_MOUSEBUTTONDOWN: switch (event.button.button) { case 1: /* left-click to re-center and zoom in */ x_center = getXVirt(event.button.x); y_center = getYVirt(event.button.y); zoom /= 2.0; redraw = true; break; case 2: /* middle click just to re-center */ x_center = getXVirt(event.button.x); y_center = getYVirt(event.button.y); redraw = true; break; case 4: /* zoom in */ zoom /= 2.0; redraw = true; break; case 5: /* zoom out */ zoom *= 2.0; redraw = true; break; } break; } } window_vals[0] = 1.0; sendWindowVals(&window_vals[0], world_size); } else { /* slave loop */ for (;;) { // DEBUG: // cout << "MPI node " << my_rank << " waiting for command." << endl; /* wait for a redraw or quit command */ MPI_Recv(&window_vals[0], 4, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, NULL); if (window_vals[0] != 0.0) break; x_center = window_vals[1]; y_center = window_vals[2]; zoom = window_vals[3]; // DEBUG: // cout << "MPI node " << my_rank << " received (" // << x_center << ", " << y_center << "), zoom " << zoom << endl; draw(my_rank, world_size, nprocs, width, height, NULL, taskVals, computation); } } delete[] taskVals; MPI_Finalize(); delete computation; return 0; } /************************************************************************** * This utility function is used by the master MPI node to create a * * window using SDL for displaying the fractal in and getting user input. * *************************************************************************/ bool createWindow(int width, int height, SDL_Surface ** screen, Uint32 ** pixels) { if (SDL_Init(SDL_INIT_VIDEO)) { cerr << "Failed to initialize SDL!" << endl; return false; } atexit(SDL_Quit); if (!(*screen = SDL_SetVideoMode(width, height, 32, 0))) { cerr << "Failed to set video mode!" << endl; return false; } SDL_WM_SetCaption(PROGNAME, PROGNAME); *pixels = (Uint32 *) (*screen)->pixels; return true; } /************************************************************************** * This utility function returns the MPI node's rank, the total number * * of MPI nodes, and the number of processing cores on the local node * *************************************************************************/ void getSizes(int * rank, int * size, int * nprocs) { MPI_Comm_rank(MPI_COMM_WORLD, rank); MPI_Comm_size(MPI_COMM_WORLD, size); *nprocs = sysconf(_SC_NPROCESSORS_CONF); int displs[*size]; int counts[*size]; for (int i = 0; i < *size; i++) { displs[i] = i; counts[i] = 1; } int all_nprocs[*size]; MPI_Gatherv(nprocs, 1, MPI_INT, &all_nprocs[0], &counts[0], &displs[0], MPI_INT, 0, MPI_COMM_WORLD); if (*rank == 0) { int total_nprocs = 0; cout << "Number of cores on each MPI node:" << endl; int i = (*size > 1) ? 1 : 0; for (; i < *size; i++) { cout << all_nprocs[i] << " "; total_nprocs += all_nprocs[i]; } cout << endl; cout << "Total number of cores: " << total_nprocs << endl; } } /************************************************************************** * This function is executed by each MPI node every time a fractal * * frame is to be drawn. * *************************************************************************/ void draw(int rank, int world_size, int nprocs, int width, int height, Uint32 * pixels, Uint32 * taskVals, Computation * computation) { // DEBUG: // cout << "In draw() with rank " << rank << endl; MPI_Status mpi_status; if (world_size == 1) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { double x_virt = getXVirt(x); double y_virt = getYVirt(y); *pixels++ = computation->compute(x_virt, y_virt); } } } else if (rank == 0) { int done_val = -1; int num_pixels = width * height; int pixel = 0; for (int to_proc = 1; to_proc < world_size; to_proc++) { if (pixel < num_pixels) { MPI_Send(&pixel, 1, MPI_INT, to_proc, 0, MPI_COMM_WORLD); pixel += task_size; } else { MPI_Send(&done_val, 1, MPI_INT, to_proc, 0, MPI_COMM_WORLD); } } int num_tasks = (num_pixels + task_size - 1) / task_size; for (int received = 0; received < num_tasks; received++) { MPI_Recv(taskVals, task_size + 1, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &mpi_status); if (pixel < num_pixels) { MPI_Send(&pixel, 1, MPI_INT, mpi_status.MPI_SOURCE, 0, MPI_COMM_WORLD); pixel += task_size; } else { MPI_Send(&done_val, 1, MPI_INT, mpi_status.MPI_SOURCE, 0, MPI_COMM_WORLD); } memcpy(pixels + taskVals[0], taskVals + 1, task_size * sizeof(int)); } } else { for (;;) { int pixel_num; /* wait to be told what to do */ MPI_Recv(&pixel_num, 1, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, NULL); if (pixel_num < 0) /* exit if we are done */ break; #pragma omp parallel for for (int i = 0; i < task_size; i++) { int this_pixel_num = pixel_num + i; int x = this_pixel_num % width; int y = this_pixel_num / width; double x_virt = getXVirt(x); double y_virt = getYVirt(y); taskVals[i + 1] = computation->compute(x_virt, y_virt); } /* send the computed pixel data to the master node */ taskVals[0] = pixel_num; MPI_Send(taskVals, task_size + 1, MPI_INT, 0, 0, MPI_COMM_WORLD); } } } /************************************************************************** * This utility function is used by the master process to update all * * of the slave processes for the position and zoom-level of the view. * *************************************************************************/ void sendWindowVals(double * winVals, int world_size) { // DEBUG: // cout << "Master sending out new window values" << endl; for (int to_proc = 1; to_proc < world_size; to_proc++) { MPI_Send(winVals, 4, MPI_DOUBLE, to_proc, 0, MPI_COMM_WORLD); } }