The basic premise is flipping a coin a million times. Except instead of flipping a coin, I generate a random value between 0 and 1. If the value is less than or equal to .5 is is a head, if larger it is a tail. As a serial operation, this could take quite a while. However, if we parallelize this process, each member will take a subset of the one million flips (one million devided by the number of nodes) and report back the its results when done. The master node then tallies up the number of heads and tails reported by each node to produce a final result. In a perfect world, this should be 50% heads and 50% tails, but as the random function is not completely random, these values will vary and we calculate an error based on that.
First we begin by loading the Parallel::MPI model and Sys::Hostname. The latter allows us to retrieve the hostname of the node executing the code, which can be beneficial for debugging purposes.
#!/usr/bin/perl
# Initialize the Libraries
use Parallel::MPI qw(:all);
use Sys::Hostname;
Next we initialize the MPI Library.
MPI_Init();Now we initialize some variables. MPI_COMM_WORLD is a communication string that all nodes use for communicating with one another. This can be called whatever you would like, but MPI_COMM_WORLD is what is referenced in the documentation. These variables are xecuted on each node and hold node specific information.
- $numprocs holds the total number of nodes in the cluster.
- $myid holds the id within the cluster starting with 0. The last value of $myid will be 1 less than the number of nodes.
- $name stores the node name.
# Get the number of nodes in the clusterNode 1 starts the timer using the MPI_Wtime function and is used later in combination with the end time to determine the total time required to process the problem. This is helpful in demonstrating the benefits of adding a node to the cluster.
$numprocs = MPI_Comm_size(MPI_COMM_WORLD);
# Determine each members ID
$myid = MPI_Comm_rank(MPI_COMM_WORLD);
# Determine each members hostname
$name = hostname;
print "Process $myid of $numprocs on $name\n";
if ($myid==0) {$startwtime = MPI_Wtime();}Now we need to initialize some variables.
- $iterations holds the number of times we will flip the coin.
- $heads stores the number of heads.
- $tails stores the number of tails.
$iterations=1000000;
$heads=0;
$tails=0;
We now generate a counter ($i) that increments by the number of nodes ($numprocs) and increases until the number of iterations ($iterations) is reached. What this means is that for a cluster of four computers, node 0 will start at 0 and at its next iteration $i will be 4, then 8, and so on. Similarly node 1 will start at 1 and $i will be 5 at the second iteration, then 9 and so on. Each node will continue this way until the value of $iterations (one million) is reached. For each iteration, $x is assigned a random value between 0 and 1. If $x is less then .5 the number of heads on this node is incremented by one. If it is larger than or equal to .5 the number of tails is incremented by 1.
for ($i=$myid;$i<$iterations;$i+=$numprocs) {For debugging purposes I print the Node id and number of heads and tails it computed.
$x=rand();
if ($x<=.5) {
$heads=$heads+1;
} else {$tails=$tails+1;}
}
print "Heads on $myid: $heads Tails on $myid: $tails\n\n";
Now check the ID of the node. If this is not Node 0, we will populate an array (@info) with the total number of heads calculated by this nodes ($heads) as the first element of the array and the total number of tails ($tails) as the second. The MPI_Send function is the utilized to send this information to Node 0. The syntax is as follows: MPI_SEND(VALUE,Number of values,Data_type,Node to send to,Communication Tag, Communication label). So in this code I am sending @info as the value, @info contains 2 values, they are of MPI_INT data type, are being sent to Node 0, with a tag of 1, on the MPI_COMM_WORLD communication label.
if ($myid!=0) {Now we set $headcnt equal to the number of heads calculated by Node 0 and $tailcnt equal to the number of tails calculated by Node 0. These values will be added to the totals calculated by other nodes shortly. For Node 0 we set $x at 1 and increase $x by 1 until we reach the number of nodes in the cluster ($numprocs). For each value of $x, Node 0 issues an MPI_Recv, to receive the data sent from the section of code above. The syntax here is MPI_Recv(value, number of values, data type,node number,tag, communication label). So below we are storing the received values in @info, are receiving 2 values, data type is MPI_INT, receiving from the node number currently stored in $x, tag is 1, and communication label is MPI_COMM_WORLD. Once the values are received we add them to the current totals for $headcnt and $tailcnt respectively.
@info[0]=$heads;
@info[1]=$tails;
MPI_Send(\@info,2,MPI_INT,0,1,MPI_COMM_WORLD);
}
else {
$headcnt=$heads;
$tailcnt=$tails;
for($x=1;$x<$numprocs;$x++) {
MPI_Recv(\@info,2,MPI_INT,$x,1,MPI_COMM_WORLD);
$headcnt=$headcnt+@info[0];
$tailcnt=$tailcnt+@info[1];
}
}Now that the calculations have completed MPI_Wtime() is used to get a current timestamp. This will be used to calculated elapsed time later on. MPI_Barrier is called to halt processing until all nodes complete communication, and MPI_Finalize ends the MPI session.
$endwtime = MPI_Wtime();Now on Node 0, we calculate the elapsed time and store it in $time, calculate the percent error for heads and tails and the print the results. As additional members are added to the cluster, the overall time to complete the processing will be seen to decrease.
MPI_Barrier(MPI_COMM_WORLD);
MPI_Finalize();
if ($myid==0) {
$time=$endwtime-$startwtime;
$headerror=abs(($headcnt-$iterations/2)/$headcnt*100);
$tailerror=abs(($tailcnt-$iterations/2)/$tailcnt*100);
print "Heads Count: $headcnt Tails Count: $tailcnt\n";
print "Head Error: $headerror% Tail Error: $tailerror%\n";
print "Time: $time\n\n";}
The above script is calle dusing the mpiexec program via a command similar to the following:
mpiexec -f ~/mpi_testing/machinefile -np 2 ~/scripts/flip.pl 1
- ~/mpi_testing/machinefile is a list of ip addresses of all participating nodes. One address per line.
- -np 2 states to use two nodes for the calculation
- ~/scripts/flip.pl is the path to the program to be run.
I hope this script helps to demonstrate and clarify some of the MPI functions and uses within the Parallel::MPI PERL module.
No comments:
Post a Comment