This chapter gives you an introduction to how Alchemi implements the concept of grid computing and discusses concepts required for using Alchemi. Some key features of the framework are highlighted along the way.
Here is a definition from http://www.gridcomputing.com:
Grid implementations differ in the way they implement this abstraction. One of the key differentiating features of Alchemi is the way it abstracts the grid, with the aim to make the process of developing grid software as easy as possible. The next section expands on this.
Generally speaking, software suited to grid computing has the following features:
Traditional grid implementations have only offered a high-level abstraction of the "virtual machine", where the smallest unit of parallel execution is a process (typically referred to as a job, with many jobs constituting a task).
In this scenario, writing software to run on a grid involves dealing with processes, an approach that can be complicated and inflexible.
Alchemi on the other hand, primarily offers a more low-level (and hence more powerful) abstraction of the underlying grid by providing a programming model that is object-oriented and that imitates traditional multi-threaded programming.
The smallest unit of parallel execution in this case is a grid thread (object), where a grid thread is very similar to a "normal" thread. A grid application is defined simply as an application that is to be executed on a grid and that consists of a number of grid threads executing in parallel.
Note: Hereafter, applications and threads can be taken to mean grid applications and grid threads respectively, unless stated otherwise.
The developer deals only with application and thread objects and any other custom objects, allowing him/her to concentrate on the application itself without worrying about the "plumbing" details. Furthermore, abstraction at this level allows the use of programming language constructs such as events between local and remote code.
All of this is offered via the Alchemi API.
The fact that a) objects are much easier to deal with than processes combined with b) a programming model that is familiar to developers and c) the use of language constructs across local and remote code makes this a superior approach to the one described in the previous section.
The additional benefit of this approach is that it does not limit the developer to applications that are completely or "embarrassingly" parallel. Indeed, it allows development of grid applications where inter-thread communication is required.
Note: While Alchemi currently only supports completely parallel threads, support for inter-thread communication is planned for future releases.
While Alchemi supports the execution of grid jobs, this support is present for the following reasons:
Development via the Alchemi API is preferred due to its ease of use, power and flexibility and should be used for all new applications, while the "grid job" model should be used for grid-enabling existing applications or writing cross-platform applications.
Alchemi is written for the .NET CLR. Hence all machines running any Alchemi software component must have the .NET Framework installed. Note: While Alchemi has only been tested on Windows, it is conceivable that it could run on Unix-class operating systems as well.
Additionally, the Alchemi API is closely tied in with the .NET CLR and thus can only be used by .NET applications.
However, as mentioned previously, Alchemi does offer support for execution of cross-platform applications via web services. Thus, using the "grid job" model one can write:
Grids are constructed using three types of distributed components (or nodes). (I refer to distributed in the context of standard distributed systems i.e. usually, but not necessarily residing in separate machines.) These are named according to their roles with respect to a grid application. All components are installed using Windows installers.
The Manager manages the execution of grid applications and provides services associated with managing thread execution. It is deployed as an executable.
An optional sub-component of the Manager is the Cross Platform Manager, which is deployed as a web service.
The Executor executes individual grid threads and provides services associated with executing threads. It is deployed as an executable.
An Executor can be configured to be dedicated (meaning the Manager initiates thread execution directly) or non-dedicated (meaning that thread execution is initiated by the Executor on a volunteer basis via a screen saver or some other user-defined options.)
The Owner owns an application and provides services associated with the ownership of an application (and its constituent threads).
The Owner is implicitly created by the Alchemi API.
To clarify the concepts discussed so far, consider the following analogies between traditional multithreaded programming and grid programming:
Multiprocessor Machine | Grid |
Operating System | Alchemi |
Processor | Executor |
OS Services (High-Level) | Owner |
OS Services (Low-Level) | Manager |
Process | Grid Application |
Thread | Grid Thread |
System API | Alchemi API |
The simplest grid consists of one Manager. Multiple Executors are configured to connect to the Manager. One or more Owners can execute applications on the grid by connecting to the Manager.
Multi-level grids are created by connecting Managers hierarchically. The key to accomplishing this is in Alchemi's inherent architecture, which allows a Manager to behave like an Executor towards a higher level Manager.
The higher-level Owner has access to the entire computing power of the grid, while the lower-level Owner only has access to the computing power managed by the lower-level Manager.
Grids can be scaled to an infinite number of levels in this fashion.
Alchemi provides a cross-platform web service interface to the Manager. This can be used by software written on other platforms to extend Alchemi grids.
The Manager should be installed on a stable and reasonably capable machine. The Manager requires:
Note: SQL Server / MSDE do not necessarily need to installed be on the same machine as the Manager.
If using SQL Server, ensure that SQL Server authentication is enabled. Otherwise, follow these instructions to install and prepare MSDE 2000 for Alchemi.
Make a note of the system administrator (sa) password in either case.
The Manager is configured from the application itself.
The Manager can be run from the desktop or Start -> Programs -> Alchemi -> Alchemi Manager. The database configuration settings used during installation automatically appear when the Manager is first started.
For a stand-alone Manager (for a uni-level grid or the highest-level multi-level grid Manager) you only need to designate the "OwnPort" setting, which is the port that Manager listens on for all communication:
For an intermediate (lower-level) Manager that is part of a multi-level grid you need to check the "Intermediate" box and specify the higher-level Manager's host and port.
Since the Manager behaves like an Executor towards the higher-level Manager, you also need to specify whether it is a dedicated or non-dedicated "Executor" by checking/uncheckig the "Dedicated" box.
Click the "Start" button to start the Manager.
The Cross Platform Manager (XPManager) requires:
If the XPManager is installed on a different machine that the Manager, or if the default port of the Manager is changed, the web service's configuration must be modified. The XPManager is configured via the ASP.NET Web.config
file located in the installation directory (wwwroot\Alchemi\CrossPlatformManager
by default):
<appSettings> <add key="ManagerUri" value="tcp://localhost:9000/Alchemi_Node" /> </appSettings>
The XPManager web service URL is of the format:
http://[host_name]/[installation_path]
The default is therefore:
http://[host_name]/Alchemi/CrossPlatformManager
The web service communicates with the Manager. The Manager must therefore be running and started for the web service to work.
The Executor is configured from the application itself.
The Executor can be run from the desktop or Start -> Programs -> Alchemi -> Alchemi Executor.
You need to configure 2 aspects of the Executor:
You can verify successful setup of a grid by running a sample application on it. The Alchemi SDK contains a sample application "PiCalculator" that calculates the value of Pi to 100 digits.
Configure PiCalculator to point to a Manager:
Examples\PiCalculator\PiCalculatorExec\bin\debug\PiCalculator.exe.config
Run it:
Examples\PiCalculator\PiCalculatorExec\bin\debug\PiCalculator.exe
Certain types of software are more suited to a grid environment than others. In order to design and develop effective grid applications, one must appreciate some aspects of grid software.
Firstly, the application should be able to be broken up into a number of parallel threads (i.e. threads that do not require constant communication among themselves). Having said that, it should be noted that limited synchronisation mechanisms are indeed available.
Secondly, the application must be computationally intensive with a high 'compute time' vs. 'communication time' ratio.
Reasons for these should be clear to the reader, given the nature of grids.
Grid applications typically employ the parameter sweep model. Examples of applications are (source):
A key decision in designing a grid application is how the application will be parallelised in order to maximise performance (minimise execution time). i.e. the number of threads vs. the workload of each thread.
There is a certain amount of overhead in running a grid thread (network data transfer, setting up and destroying threads etc.) as opposed to running a "normal" thread. Clearly, total overhead can be minimised by having a low number of threads with high computational workload.
On the other hand, a high number of threads running in parallel will result in better utilisation of the grid, leading to faster total execution time for the application.
Given these two opposing factors, one needs to find a balance between the number of threads and each thread's workload. Some specific factors that will affect this decision are a) the size of the grid and b) importance of performance.
Follow these steps to set up a development environment:
Alchemi.Core.dll
for referencing in applications
This tutorial provides a basic introduction to grid programming. Familiarity with VS.NET and C# is assumed.
Let's pretend that multiplying two integers is a very computationally intensive process. Since we need to multiply ten sets of integers, we decide to write a grid application.
To start off,
Alchemi.Core.dll
(Alchemi.Core.dll
must be referenced by any projects using the Alchemi API)There are essentially two parts to a grid application:
The grid code can be compiled separately from the local code as one or more .dlls or it can be compiled as part of the local code executable. In the tutorial, all code is in one file, Tutorial.cs
.
To write a grid thread class, we derive a new class from the Alchemi.Core.GThread
class and override the void Start()
method. We must also add the Serializable
attribute to it:
[Serializable] public class MultiplierThread : GThread { public override void Start() { } }
We now add the following bits to make the grid thread perform a multiplication between two integers:
Result
propertyvoid Start()
method
using System; using Alchemi.Core; namespace Alchemi.Examples.Tutorial { [Serializable] public class MultiplierThread : GThread { private int _A; private int _B; private int _Result; public int Result { get { return _Result; } } public MultiplierThread(int a, int b) { _A = a; _B = b; } public override void Start() { _Result = _A * _B; } } ...
The Main()
method of the console application is as follows:
... class MultiplierApplication { static GApplication ga; [STAThread] static void Main(string[] args) { Console.WriteLine("[enter] to start grid application ..."); Console.ReadLine(); // create grid application ga = new GApplication("localhost", 9099); // add GridThread module (this executable) as a dependency ga.Manifest.Add(new ModuleDependency(typeof(MultiplierThread).Module)); // create and add 10 threads to the application for (int i=0; i<10; i++) { // create thread MultiplierThread thread = new MultiplierThread(i, i+1); // set the thread finish callback method thread.FinishCallback = new GThreadFinish(ThreadFinished); // add thread to application ga.Threads.Add(thread); } // set the application finish callback method ga.FinishCallback = new GApplicationFinish(ApplicationFinished); // start application ga.Start(); Console.ReadLine(); } ...
Main()
method
First, a GApplication
object is created. The Alchemi Manager host and port are supplied in the constructor. Next, a ModuleDepencency
using the module (.dll) of the grid thread is created and added to the Manifest
of the application, signaling it as a dependency for all threads.
Ten threads are then created. A
delegate is set for each thread (this delegate is called when the thread finishes executing) and the thread is added to the application.ThreadFinished
Finally, the ApplicationFinished
delegate is set for the application (this delegate is called when the application finishes executing), and the application is started.
We can now add the two delegate (callback) methods:
... static void ThreadFinished(GThread th) { // cast GThread back to MultiplierThread MultiplierThread thread = (MultiplierThread) th; if (thread.RemoteExecutionException == null) { // thread executed OK Console.WriteLine( "thread # {0} finished with result '{1}'", thread.Id, thread.Result); } else { // an exception occured Console.WriteLine( "thread # {0} finished with error '{1}'", thread.Id, thread.RemoteExecutionException); } } static void ApplicationFinished() { Console.WriteLine("\napplication finished"); Console.WriteLine("\n[enter] to continue ..."); } } }
Examples\Tutorial
directory. Note: To recompile it, you must replace the reference to Alchemi.Core.dll
with a new reference pointing to your copy of Alchemi.Core.dll
The tutorial only demonstrates basic features of the Alchemi API. This section contains some notes on other features.
The tutorial demonstrates only one module dependency (GridThread.dll
) containing only one class (MultiplierThread
). You can have multiple GThread
-inherited classes. The GThread
-inherited class(es) can use other classes as well.
You need only ensure that all modules are added to the Manifest
as dependencies.
The tutorial only uses one ThreadFinished
delegate for all threads, but you can specify a different one for each thread.
In the tutorial, all threads are added to the application and it is then started. An optional scenario is on-the-fly execution. This is useful for real-time or "service" applications where information about all threads to be executed is not known beforehand. Please note the following for using on-the-fly execution.
GApplication.Start()
after all dependencies have been setGApplication.StartThread
(..) to execute a thread on-the-flyGApplication.Stop()
once no more threads need to be executed. (This is not required if setting the GApplication.ApplicationFinished
delegate, but setting this delegate usually does not make sense for on-the-fly execution.)Examples\Tutorial_OTF
contains a version of the tutorial application modified to demonstrate on-the-fly execution (Note: To recompile it, you must replace the reference to Alchemi.Core.dll
with a new reference pointing to your copy of Alchemi.Core.dll
) :
You can request a priority by setting the Priority
property for a thread before it is executed. The highest priority is 0 (default) with larger integers denoted lower priorities. Note: The requested priority is not guaranteed to be met.
Even though inter-thread communication is not possible (in this version), limited thread synchronisation - using the results of a thread for a subsequent thread - can still be coded into the ThreadFinish
and/or AppliationFinish
callback methods. It should be clear to the reader how this could be achieved.
Alchemi supports the traditional job model i.e. jobs within tasks, where each job is a process with input and output files.
It should be noted that in Alchemi:
GJob
class which inherits from the GThread
class (i.e. a job is actually just a specialised grid thread)This means that any tools or code that work with grid applications and grid threads will also work with taks and jobs respectively.
There are two options available for grid-enabling, running and monitoring existing applications:
The JobAPI example ([SDK]\Examples\JobAPI
example demonstrates the use of the Alchemi API for tasks/jobs. The solution contains two projects: Reverse
(a simple console application that reverses the text of a file specified as a command-line argument and displays the results) and GridReverser
(a console application that demonstrates the grid-enabling of Reverse
).
Here is the complete listing of GridReverser.cs
:
using System; using System.Xml; using System.IO; using Alchemi.Core; using Alchemi.Core.Utility; namespace Alchemi.Examples.CrossPlatformDemo { class GridReverser { static GApplication ga; [STAThread] static void Main(string[] args) { Console.WriteLine("Press [enter] to start ..."); Console.ReadLine(); try { ga = new GApplication("localhost", 9099); ga.Manifest.Add(new EmbeddedFileDependency("Reverse.exe", @"..\..\..\Reverse\bin\Debug\Reverse.exe")); GJob job1 = new GJob(); job1.InputFiles.Add(new EmbeddedFileDependency("input1.txt", @"..\..\input1.txt")); job1.RunCommand = "Reverse input1.txt"; job1.OutputFiles.Add(new EmbeddedFileDependency("out.txt")); ga.AddThread(job1, 0, new GThreadFinish(JobFinished)); ga.Start(); ga.FinishCallback = new GApplicationFinish(ApplicationFinished); } catch (Exception e) { Console.WriteLine(e); } Console.WriteLine("Started .. Waiting for jobs to finish ..\n"); Console.ReadLine(); } public static void JobFinished(GThread thread) { GJob job = (GJob) thread; Console.WriteLine("Finished job {0}", job.Id); foreach (FileDependency fd in job.OutputFiles) { Directory.CreateDirectory("job_" + job.Id); fd.UnPack(Path.Combine("job_" + job.Id, fd.FileName)); Console.WriteLine("Unpacked file {0} for job {1}", fd.FileName, job.Id); } } public static void ApplicationFinished() { ga.Stop(); } } }
The MCI is a console application ([SDK]\alchemi_mci.exe
) that can be used to monitor grid applications/threads. It can also be used to submit tasks/jobs and retrieve their results.
Its use is demonstrated here by a simple example. The files required for this example can be found in [SDK]\Examples\MCIUsage
.
The file test.xml
contains an example representation of a task:
<task> <manifest> <embedded_file name="Reverse.exe" location="Reverse.exe" /> </manifest> <job id="0"> <input> <embedded_file name="input1.txt" location="input1.txt" /> </input> <work run_command="Reverse.exe input1.txt > result1.txt" /> <output> <embedded_file name="result1.txt"/> </output> </job> <job id="1"> <input> <embedded_file name="input2.txt" location="input2.txt" /> </input> <work run_command="Reverse input2.txt > result2.txt" /> <output> <embedded_file name="result2.txt"/> </output> </job> </task>
The MCI must be started with the host and port of an Alchemi Manager:
The following screenshot shows how the MCI can be used to submit a task, monitor its jobs and retrieve results:
There are other commands you can use as well; invoke alchemi_mci.exe
without any arguments for help.
The Mandelbrot set generator example can be found in [SDK]\Examples\Mandelbrot
:
[todo]