High Performance Computing - Charles Severance [114]
The most complicated applications have nonuniform data flows and data that migrates around the system as the application changes and the load changes on the system.
In this section, we have two example programs: one is a master-slave operation, and the other is a data decomposition-style solution to the heat flow problem.
Queue of Tasks
In this example, one process (mast) creates five slave processes (slav) and doles out 20 work units (add one to a number). As a slave process responds, it’s given new work or told that all of the work units have been exhausted:
% cat mast.c
#include #include "pvm3.h" #define MAXPROC 5 #define JOBS 20 main() { int mytid,info; int tids[MAXPROC]; int tid,input,output,answers,work; mytid = pvm_mytid(); info=pvm_spawn("slav", (char**)0, 0, "", MAXPROC, tids); /* Send out the first work */ for(work=0;work pvm_pkint(&work, 1, 1 ) ; pvm_send(tids[work],1) ;/* 1 = msgtype */ } /* Send out the rest of the work requests */ work = MAXPROC; for(answers=0; answers < JOBS ; answers++) { pvm_recv( -1, 2 ); /* -1 = any task 2 = msgtype */ pvm_upkint( &tid, 1, 1 ); pvm_upkint( &input, 1, 1 ); pvm_upkint( &output, 1, 1 ); printf("Thanks to %d 2*%d=%d\n",tid,input,output); pvm_initsend(PvmDataDefault); if ( work < JOBS ) { pvm_pkint(&work, 1, 1 ) ; work++; } else { input = -1; pvm_pkint(&input, 1, 1 ) ; /* Tell them to stop */ } pvm_send(tid,1) ; } pvm_exit(); } % One of the interesting aspects of the PVM interface is the separation of calls to prepare a new message, pack data into the message, and send the message. This is done for several reasons. PVM has the capability to convert between different floating-point formats, byte orderings, and character formats. This also allows a single message to have multiple data items with different types. The purpose of the message type in each PVM send or receive is to allow the sender to wait for a particular type of message. In this example, we use two message types. Type one is a message from the master to the slave, and type two is the response. When performing a receive, a process can either wait for a message from a specific process or a message from any process. In the second phase of the computation, the master waits for a response from any slave, prints the response, and then doles out another work unit to the slave or tells the slave to terminate by sending a message with a value of -1. The slave code is quite simple — it waits for a message, unpacks it, checks to see if it is a termination message, returns a response, and repeats: % cat slav.c #include #include "pvm3.h" /* A simple program to double integers */ main() { int mytid; int input,output; mytid = pvm_mytid(); while(1) { pvm_recv( -1, 1 ); /* -1 = any task 1=msgtype */ pvm_upkint(&input, 1, 1); if ( input == -1 ) break; /* All done */ output = input * 2; pvm_initsend( PvmDataDefault ); pvm_pkint( &mytid, 1, 1 ); pvm_pkint( &input, 1, 1 ); pvm_pkint( &output, 1, 1 ); pvm_send( pvm_parent(), 2 ); } pvm_exit(); } % When the master program is executed, it produces the following output: % pheat Thanks to 262204 2*0=0 Thanks to 262205 2*1=2 Thanks to 262206 2*2=4 Thanks to 262207 2*3=6 Thanks to 262204 2*5=10 Thanks to 262205 2*6=12 Thanks to 262206 2*7=14 Thanks to 262207 2*8=16 Thanks to 262204 2*9=18 Thanks to 262205 2*10=20 Thanks to 262206 2*11=22 Thanks to 262207 2*12=24 Thanks to 262205 2*14=28 Thanks to 262207 2*16=32 Thanks to 262205 2*17=34 Thanks to 262207 2*18=36 Thanks to 262204 2*13=26 Thanks to 262205 2*19=38 Thanks to 262206 2*15=30 Thanks to 262208 2*4=8 % Clearly the processes are operating in parallel, and the order of execution is somewhat random. This code is an excellent skeleton for handling a wide range of computations. In the next example, we perform an SPMD-style computation to solve the heat