Although the Bayanihan framework can support other programming models,
all our applications currently use the master-worker model
shown in Fig. 2.
As the work manager receives results from workers, it places the results in the result pool and marks the corresponding work in the work pool done. When all the work in the pool have been done, the work manager calls its parent program object's signalDone() method. By default, this method calls createNextWork(), which sets up the next stage of the computation by refilling the work pool and calling startNewWorkBatch(). In applications, programmers can override createNextWork() or signalDone() to separate blocks of parallel computation such that one block is guaranteed to be computed completely before the other is started. This provides a simple form of barrier synchronization. In our factoring application, for example, the program object has a list of target numbers to be factored, and its createNextWork() method is used to move to the next target while making sure that all the results from the previous one have been received.
Users can view results and statistics and control the computation through
watcher clients that communicate with the watch manager on the server.
A watcher client's engine runs in a loop, periodically requesting the
watch manager for a list of new results,
and passing these results to the watch GUI, which displays them accordingly.
The watcher client also allows the user to send request objects to the
watch manager via the makeRequest() method. The watch manager forwards
such requests to the program object, which then reacts in an
application-specific way. In our Mandelbrot rendering demo, for example, the watch GUI (shown in Fig. 3)
allows users to select a portion of the screen to zoom in or out to, and sends
corresponding request objects to the server. When the program object receives
the request, it responds by calling reset() on the work and watch managers
(causing the work and result pools to be cleared), and calling its own
createWork(request) method.
To write an application for this model, programmers need only to override appropriate methods in the application-specific classes MWProgram, WorkData, WorkGUI, ResultData, and WatchGUI. In this manner, we have written a variety of applications, including factoring [13], distributed web-crawling [14], RC5-64 decryption, and Mandelbrot rendering. Although seemingly simple and limited to ``embarrassingly parallel'' applications, the master-worker model is actually quite versatile and practical. Our Mandelbrot demo, for example, represents parallel rendering applications used not only in the scientific community but in the media industry as well. Other potential applications include some forms of data mining, Monte Carlo simulations, and any computations in general where one wants to run the same sequential computation with a large number of varying input combinations. Interestingly, because most programmers today are still accustomed to sequential programming, a lot of applications in the real world may actually fall into this category.
By extending the appropriate classes, we can also use the master-worker model to support more complex programming models. For example, we have built a sub-framework for parallel genetic algorithms that defines a generic work data class, GAWork, whose doWork() method calls abstract evaluation, selection, reproduction, and mutation methods on a set of genes, and a generic program class, GAProgram, whose createNextWork() method redistributes the resulting genes into a new generation of work objects. By writing application-specific subclasses of GAWork and GAProgram, we have successfully applied the sub-framework to problems such as multivariable function optimization. Currently, we are looking into implementing BSP [15], a growingly popular programming model which makes programming more natural by providing remote memory access and message-passing functions, but at the same time makes implementation easier by specifying that these communication operations only take effect at the next global barrier synchronization. It should be possible to implement BSP on top of the master-work model by defining a work engine that allows work objects to make communication requests as they run, and a work manager that collects these requests and performs them during in signalDone() or createNextWork(). In the future, we may also implement a coarse-grain dataflow programming model by extending the master-worker components to support dependencies between work objects.