developer. On the one hand, there is the solution taken by large IT providers who offer a whole platform (PaaS) to develop services that run on top of their clusters. In this case, the underlying infrastructure is uniform, and the platform only provides an implementation of its API that directly interacts with the storage and computing resources. The developers of the platform only need to focus on how to obtain the most performance of the system without worrying about the interoperability. Some outstanding examples of PaaS are Microsoft Azure [5] Cloud and Google AppEngine [4].
The alternative for not binding the usage of a programming model implementation to a concrete infrastructure without dealing directly with the concerns of interoperability is to leverage on some software solution that abstracts the details of the infrastructure. Within this solution, there are two different approaches.
The first one consists in using software that homogenizes the system and implements the programming model highly-coupled to the features of the software. This solution forces system administrators to bind the infrastructure to a specific software. For example, the Apache Founda- tion implementation for the MapReduce model, Hadoop [8], builds on the Hadoop Distributed File System (HDFS) taking advantage of its data fragmentation, distribution and replication to obtain a higher performance on the execution of its applications.
The second option, less restrictive for system administrators, is to leverage on middlewares that offer a set of low-level methods and implements their functionalities in several protocols. Thus, the administrator of each component of the infrastructure can manage it with the desired software stack and applications can make use of them regardless the access protocol. This approach was analyzed and solved by the computer scientist who developed the Grid. They defined an abstract API, the Grid Application Toolkit [13], to command remote data transfers and submit jobs (task executions) to remote resources; two implementations of this API are JavaGAT [97] and SAGA [46].
3.4
COMPSs
COMP Superscalar (COMPSs) is a programming model which aims to ease the development of applications for distributed infrastructures, such as Clusters, Grids and Clouds. For the sake of programming productivity, the COMPSs model builds on three pillars:
• Infrastructure unawareness. COMPSs programs do not include any detail that could tie them to a particular platform. Thus, the model releases developers from dealing with the heterogeneity of the system or struggling with the mapping of the tasks to the processing elements of the infrastructure. By keeping applications agnostic to the infrastructure, they achieve portability across different platforms.
• Sequential programming. To hide away parallelism details from developers, COMPSs analyzes the sequential code of the application to build the workflow of the application.
The model automatically detects the tasks composing it and the data dependencies among them. Using this information, the implementation of the model orchestrates the execution of these tasks on the underlying infrastructure taking care of the required communications and synchronizations to guarantee the sequential consistency of the program.
• Standard programming languages and no APIs. To facilitate the learning of the model, COMPSs does not define any specific language nor provides a specific API or construct to build the application. Instead, developers code the sequential application using standard programming languages (Java, Python or C/C++).
The idea behind COMPSs is to apply the mechanisms implemented in out-of-order superscalar processors to exploit the Instruction Level Parallelism but at a coarser grain: method invocations. As the execution progresses, the code invokes methods of the application. Instead of computing the body of these methods in the same processor, the execution is replaced by the creation of an asynchronous task to run the same method code on a node of the underlying infrastructure. The more method invocations the main code does, the more asynchronous tasks coexist and run in parallel on the infrastructure.
As one instruction can use the value stored in a registry by another one, one method can use as an input parameter a value created by another invocation. Therefore, there are data hazards to control by detecting the data dependencies among tasks. Often, the main code of the application needs a value created within the body of one method invocation. These accesses constitute a control hazard since the main code needs to wait until the corresponding task completes and creates the value (synchronization) to go on with the execution.
To allow fine-tuning the grain of these tasks, developers must select the subset of methods whose invocations create new tasks. The selected methods are known as Core Elements (CE) and the main code of the application, Orchestration Element (OE). This selection is done by means of an interface, known as Core Element Interface (CEI), where the developer declares the methods to consider as a CE. Since interfaces allow to define methods but not the class to which they belong, the application developer needs to explicitly point out the class that contains the method implementation. For that purpose, they must annotate the method definition with the @Method directive and indicate the class with the attribute declaringClass.
An important difference between instructions and user-defined methods is the action per- formed on the parameters. An ISA has a limited number of instructions, and all of them have clearly defined its parameters and behavior; hence, the processor can detect data dependencies among instructions. Conversely, there are countless user-defined functions; each one has a differ- ent set of parameters and operates differently on their values. For determining data dependencies among tasks automatically, developers have to clarify the behavior of the operation by stipulating the action (read, update or create) performed on each parameter. For that purpose, COMPSs provides the @Parameter directive to annotate each parameter of the method declaration and describe the action performed on it. There, developers indicate the directionality of the parameter
3.4. COMPSS
(IN for value reads, INOUT for value updates or OUT for value creations) and its type (BOOLEAN, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE, STRING, OBJECT or FILE). The model can automatically infer the type looking at the class of the parameter except for file passed as a String.
Besides the arguments, a method can operate on two more values: the callee object of the method and its return. COMPSs considers the former as an additional parameter of type OBJECT with INOUT directionality. Likewise, the return value is a new parameter with type OBJECT, but the direction of the parameter is OUT since the initial value does not exist.
The code snippet in Figure 3.3 contains a simple COMPSs application example. Subfig- ure 3.3(a) shows the main code of the application which runs one simulation for each argument of the application. The application aggregates the results of all of them in a single report object and prints it at the end of the execution.
Figure 3.4 contains an example of a CEI for the application that selects three methods as CEs. PrepareParameters is a static method implemented in the Simulation class. It takes one string describing the parameters to run a simulation as input and returns a SimParameters object containing the same configuration. Simulate is an instance method also implemented in the Simulation class. The method takes as the only parameter a SimParameters object which it reads to run the simulation. At the end of the execution, simulate returns a Report object with the result of running the simulation. The third CE corresponds to the static method aggregate implemented in the Report class. It takes two Report objects and updates the content of the first of them to include the values of the second.
When running, the application creates three asynchronous tasks on each iteration, one for each CE. The first detected task – corresponding to the prepareParameters CE invocation in line 9 of the Main class code – reads a string coming from the arguments of the application. Since they do not depend on any other task, every prepareParameters tasks can directly run upon its detection. When the application reaches line 11 of the code, it creates a simulate task. In this case, the simulate CE reads the SimParameters object created by the first task of the iteration; hence, there will always be a data dependency among the prepareParameters and simulate tasks of the same iteration. The third CE invocation on the iteration, aggregate on line 12, creates a task that reads the return value of the simulate task of the same iteration to merge it into the result of the aggregate task corresponding to the previous execution. Finally, once the execution has gone through all the iteration of the loop, the application reaches the System.out.println method invocation on line 14 to print the final result. At this point, the execution needs the actual value of the globalReport variable forcing a synchronization with the last aggregate task to fetch the proper value.
The directed acyclic graph in Figure 3.5 depicts the described workflow for an execution of the application with four arguments. Each node in the graph represents a task; red tasks correspond to prepareParameters tasks; blue tasks are simulate tasks; and yellow tasks, aggregate. Arches in
01 package es.bsc.compss.sample;
02
03 public classMain {
04
05 public static voidmain (String[] args) {
06 int numSims = args.length;
07 Report globalReport = new Report();
08 for (int simId = 0; simId<numSims; simId++){
09 SimParameters sp = Simulation.prepareParameters(args[simId]);
10 Simulation sim = new Simulation();
11 Report sreport = sim.simulate(sp);
12 Report.aggregate(globalReport, sreport);
13 }
14 System.out.println(globalReport);
15 }
16 }
(a) Content of Main class
01 package es.bsc.compss.sample;
02
03 public classSimulation {
04
05 public static SimParametersprepareParameters (String paramsDescription){
06 SimParameters sp = new SimParameters();
07 //Update content of sp according to paramsDescription
08 ...
09 return sp;
10 }
11
12 public Reportsimulate (SimParameters sp){
13 Report r;
14 //Runs the simulation according to the parameters in sp and generates a report
15 ...
16 return r;
17 }
18 }
(b) Content of Simulation class
01 package es.bsc.compss.sample;
02
03 public classReport {
04
05 public static voidaggregate (Report accum, Report diff){
06 // Merges the results in report diff into accum
07 ...
08 }
09 }
(c) Content of Report class
Figure 3.3: Sample application code written in Java.
the graph represent data dependencies among tasks: the task portrayed by the target node of the arch depends on the task corresponding to its source. The red octagon at the bottom of the figure represents the synchronization between the main code and the execution of the last aggregate task.