Project 6 Group Report - Lewellyn Yap, Scott Mantei, Hye Kim

advertisement
EE 524 / CS 561
Spring, 2000
PROJECT 6
A Parallel and Synchronized Music Interpreter
for the Beowulf Cluster
Hye Kim, Scott Mantei & Llewellyn Yap
Prepared for: Professor Mark L. Manwaring
EECS, Washington State University
Pullman, WA 99164
March 28, 2000
A Parallel and Synchronized Music Interpreter for the Beowulf Cluster
Introduction
The task at hand is to build a music generator that will run concurrently on the four-Beowulf
cluster. Synchronization, hence, becomes an issue. This document reports on the algorithm and
implementation of the music-generating program.
Before any programming can begin, certain requirements are necessary. A sound generating
function for the Linux OS must be available. One such module can be found in [1]
(complements to Rama Shenai). Next, LAM/MPI must be accessible on the Beowulf. This
condition has been satisfied since the last project. What remains is to piece together a concurrent
program that will play synchronous melodies. Such a design is presented below.
Parallel / Synchronizing Algorithm of the Music Interpreter
Several assumptions were made before the design of the program. We attempted to synchronize
4-part harmony music, having each voice play at each Beowulf node (i.e. SATB on nodes n0, n1,
n2, n3 respectively or in any order). The file format and music grammar will be discussed in a
latter section. We decided that the best way to synchronize the processes is to use TIME as the
measure. Since music is written in phrases, we thought that synchronizing at every bar should
work. Shown below is the pseudo code of the program. Following that is a brief description of
what it does.
/* Pseudo Code for music_v4.c */
Define sound(freq, length) function (adapted from [1])
Begin main() {
Initialize MPI;
Determine which node you are (MPI_Comm_rank());
Open “/dev/console” file; {write to this file to play sound)
If I’m the World Node {
Open song file;
Read song file & Distribute (write) parts to Arrays - n0, n1, n2, n3;
Send appropriate array size and array to nodes;
i.e. array n1 -> send to node1;
array n2 -> send to node2; ... etc
Receive Acknowledgement from each node; /* initial synchronization */
PLAY MUSIC! {
As music being played, keep track of time increments;
(i.e. accumulated sum of the delays)
If (accumulated time == MILEPOST)
Receive synchronizing flag from each node;
Once all nodes reported in, Issue flag to continue playing;
Reinit accumulated time = 0;
Repeat & Play till music ends;
}
}
If I’m NOT the World Node {
Receive array size and array from World Node;
Send Acknowledgement indicating reception of data and ready to play;
PLAY MUSIC! {
As music being played, keep track of time increments;
If (accumulated time == MILEPOST)
Send synchronizing flag to World Node;
Wait to Receive Continue signal;
Upon reception, continue with piece;
Reinit accumulated time = 0;
Repeat & Play until music ends;
}
}
Exit_MPI();
} /* end main() */
The main gist of the program is described here. It begins by tagging each node with a rank. The
World Node identifies itself and begins opening the song data file. It distributes its contents to 4
predefined structured arrays. After accomplishing this, it sends the appropriate array size and
array to the rest of the Beowulf nodes. It waits for acknowledgements from the nodes. After
this, it begins playing its part. As it plays, it keeps record of the time passing by. When time
reaches a MILEPOST (a time to synchronize), it pends for synchronizing flags from each node.
When all nodes are accounted for, it flags everyone to continue where they left off. This
synchronization continues at every MILEPOST till the end of the music.
For the other nodes, their jobs are simpler. They wait for song data from the World Node, and
sends off the acknowledgement. Music plays. At a MILEPOST, they send a synchronizing flag
to the World Node and wait for the continue flag, which will only arrive after the World Node
receives flags from all nodes. Nodes continue playing and synchronizing until they reach the end
of the music.
The complete source code may be found in Appendix A. Appendix B holds the compilation and
execution of the program.
Music Grammar Implemented
The music passage we are using for this project is “Oh Beautiful for Spacious Skies.” In
choosing a piece, we searched for music that had four distinct parts, namely soprano, alto, tenor
and bass. We looked at hymnals to find such a piece because hymnals are full of four-part
music.
Once the piece was decided upon, we translated the notes into a text file. The first column of the
text file specified the node number. Every note that node 1 was to play was given the number 1,
for example. The second column was the frequency value given in Hertz. And the third column
was the duration of the note given in milliseconds. A sample of the data file is as follows:
1 392 250
1 392 375
1 330 125
:
:
2 330 250
2 330 375
2 262 125
:
Page 2
:
3 262 250
3 262 375
3 196 125
:
:
The result of this musical passage is all four nodes playing synchronously in four-part harmony.
The program searches for a node number and copies the frequency and duration to a pre-defined
data structure. So far, no actions have been taken to check for errors in the data file, except a
requirement that the total time length of each node be exact.
Tests & Results
After some sticky situations with the sticky bit (as discussed in Appendix B), we were finally
able to enjoy and be impressed by the 4-part harmony. The piece is finally parallelized and
synchronized.
At the start of program, there was some network activity as the World Node was sending data to
the child nodes. For this “Oh Beautiful for Spacious Skies” piece, the MILEPOST was set at
1000ms (i.e. synchronization occurs every 1s). Network traffic during the play of the song, as
observed at the hub, was very minimal (< 5%). The piece sounded smooth and fluent. Latency
was not an issue.
In order to determine if our synchronizing techniques really worked, we forced some of the
nodes to play faster (or slower) than the rest (by multiplying the duration parameter by an
appropriate factor). Without any synchronization (i.e. setting MILEPOST = large number), the
piece sounded terrible. Applying synchronization, the song was synchronized at each
MILEPOST and after that, it goes wild, but to again be synchronized at the next MILEPOST.
Synchronization is hence achieved.
Though these tests were not quantitative, the program does do a good stab at parallelizing and
synchronizing our 4-part harmony. In fact, the human listener won’t be able detect the
millisecond time-differences. So we feel justified for not doing a more formal analysis, and let
our ears be the judge.
Conclusions
We were successful in implementing the music-generating program which plays up to 4 different
voices synchronously. Music may be broken down into different parts by cleverly writing the
music data file. The frequency of synchronization may be adjusted with the MILEPOST value.
The speed of the song is set by the TEMPO value. Lengthy songs may be played by increasing
the array size allocator, NOTE_BUF.
Henceforth, users are encouraged to write their own music files and have the Beowulf entertain
them for hours.
References
[1] console_beep-0.1.tar.gz (beep.c function). http://metalab.unc.edu/pub/Linux/apps/sound/misc/.
Page 3
APPENDIX A
SOURCE CODE (music_v4.c)
/**********************************************************************/
/* File: x86wulf:/home/lyap1/LAM/PROJ6/music_v4.c
/*
/* Programmers: Llewellyn Yap, Scott Mantei, Hye Kim
/*
/* Inputs: Music file "obeautf.dat" (embedded in code)
/*
/* Output: Sound via internal speaker
/*
/* Requirements: This program requires a Beowulf cluster of 4
/*
machines and a music file. The music file
/*
should be written in the format:
/*
/*
... node# freq duration ...
/*
/*
(where node# = {1, 2, 3 or 4}
/*
/* Description: This program parallelizes / synchronizes 4/*
part harmony on the Beowulf using MPI routines.
/*
The TEMPO determines the speed of the music.
/*
A TEMPO value < 1 increases the speed etc.
/*
MILESTONE determines how often the song should
/*
be synchronized (in ms). These parameters may
/*
change with different pieces.
/*
/* Date: 3-28-2000
/***********************************************************************/
#include <mpi.h>
#include <stdio.h>
#define TEMPO 1.0
#define MILEPOST 1000
#define NOTE_BUF 100
/* MULTIPLIER, speed of music
*/
/* synchronize at every MILEPOST ms */
/* limits notes to be stored
*/
typedef struct{
int freq;
int length;
}note;
/* console_beep V0.1 "borrowed" from Josef Pavlik <jetset@ibm.net> */
FILE *dest;
void sound(int freq, int length) {
fprintf(dest,"\33[10;%d]\33[11;%d]\a\33[10]\33[11]", freq, length);
fflush(dest);
if (length) usleep(length*1000L);
}
int main(int argc, char **argv)
{
int rank, size, partner;
/* MPI accessories */
int i=0,j=0,k=0,l=0,n,node; /* generic counters */
int check=0, sync=0;
/* program flags
*/
note n0[NOTE_BUF], n1[NOTE_BUF], n2[NOTE_BUF], n3[NOTE_BUF];
FILE *infile;
MPI_Status stat;
int Len0=0, Len1=0, Len2=0, Len3=0;
MPI_Init(&argc, &argv);
/*
size = query number of processes */
Page 4
MPI_Comm_size(MPI_COMM_WORLD, &size);
/* rank = which one am I? */
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
dest = fopen("/dev/console", "w");
if (!dest) {
perror ("Destination open error");
exit(1);
}
/* world node job description */
if (rank == 0) {
/* read file and distribute parts to array */
infile = fopen("obeautf.dat","r");
while(!feof(infile)) {
fscanf(infile,"%d",&node);
switch(node) {
case(1):
fscanf(infile,"%d",&n0[i].freq);
fscanf(infile,"%d",&n0[i].length);
Len0 += n0[i].length;
i++;
break;
case(2):
fscanf(infile,"%d",&n1[j].freq);
fscanf(infile,"%d",&n1[j].length);
Len1 += n1[j].length;
j++;
break;
case(3):
fscanf(infile,"%d",&n2[k].freq);
fscanf(infile,"%d",&n2[k].length);
Len2 += n2[k].length;
k++;
break;
case(4):
fscanf(infile,"%d",&n3[l].freq);
fscanf(infile,"%d",&n3[l].length);
Len3 += n3[l].length;
l++;
break;
}
}
if (!(Len0 + Len0 == Len0 + Len0)) {
printf("L0=%d i=%d L1=%d j=%d L2=%d k=%d L3=%d l=%d\n", Len0, i, Len1, j,
Len2,k ,Len3, l);
printf("\n Error: Length of times different, cannot synchronize.\n");
exit(1);
}
/* send array size and array to individual nodes */
MPI_Send(&j, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
MPI_Send(n1, 2*j, MPI_INT, 1, 0, MPI_COMM_WORLD);
MPI_Send(&k, 1, MPI_INT, 2, 0, MPI_COMM_WORLD);
MPI_Send(n2, 2*k, MPI_INT, 2, 0, MPI_COMM_WORLD);
MPI_Send(&l, 1, MPI_INT, 3, 0, MPI_COMM_WORLD);
MPI_Send(n3, 2*l, MPI_INT, 3, 0, MPI_COMM_WORLD);
/* checks if all nodes are present & synchronize */
printf("\n Piece will be synchronized at every %d ms\n", MILEPOST);
printf(" n%d querying ...\n", rank);
for ( partner=1 ; partner<size ; partner++ ) {
MPI_Recv(&check, 1, MPI_INT, partner, 0, MPI_COMM_WORLD, &stat);
if (check==1)
Page 5
printf(" n%d synchronized ...\n", partner);
else
printf(" n%d MIA\n", partner);
}
/* play sound */
for (n=0; n<i; n++) {
sound(n0[n].freq,n0[n].length*TEMPO);
sync += n0[n].length;
if (sync >= MILEPOST) {
check = 1;
for ( partner=1 ; partner<size ; partner++ ) {
MPI_Recv(&sync, 1, MPI_INT, partner, 0, MPI_COMM_WORLD, &stat);
printf(" rcved from n%d\n", partner);
}
for ( partner=1 ; partner<size ; partner++ )
MPI_Send(&check, 1, MPI_INT, partner, 0, MPI_COMM_WORLD);
printf("
=== re-sync-ed ===\n\n");
sync = 0;
}
printf(" Completed %d\%, sync = %d/%d\n", n*100/i, sync, MILEPOST);
}
printf(" Completed 100\%\n");
}
/* other nodes: */
else
{
/* receive size of array and array from world node */
MPI_Recv(&i, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &stat);
MPI_Recv(n1, 2*i, MPI_INT, 0, 0, MPI_COMM_WORLD, &stat);
check = 1; /* alert world node, init sync */
MPI_Send(&check, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
for (n=0; n<i; n++) {
sound(n1[n].freq,n1[n].length*TEMPO);
sync += n1[n].length;
/* synchronizing routine, performed at each MILEPOST */
if (sync >= MILEPOST) {
MPI_Send(&sync, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
MPI_Recv(&check, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &stat);
sync = 0;
}
}
}
/* All Done */
MPI_Finalize();
exit(0);
}
Page 6
APPENDIX B
COMPILING & EXECUTING THE PROGRAM
Playing Sounds to All the Nodes
Because of the way the sound function was written, it plays to the local speaker even though you
executed the file on a remote machine. The destination it writes to is stdout. In order to fix
this, the /dev/console file has to accessed in order to play music on a remote computer. To
do this (special thanks to fellow colleague Nilesh Bhide), one has to change the ownership of the
executable to root and assign the sticky bit to it. Explicit command lines are shown below.
Compiling & Executing
The steps to compile and run the program are presented below:
#
#
#
#
#
#
#
#
#
hcc -o music music_v4.c –lmpi
sudo /bin/tcsh
chown root:root music
chmod +s music
scp music 10.0.0.2:/home/lyap1/LAM/PROJ6
scp music 10.0.0.3:/home/lyap1/LAM/PROJ6
scp music 10.0.0.4:/home/lyap1/LAM/PROJ6
exit
mpirun -v n0-3 music
Page 7





Compile program
Switch to root
Change ownership of music to root
Assign sticky bit to music
Copies executable file to
other nodes
 Revert from root to user
 Run executable
Download