PROSIT details


Intoduction

Prosit is a sequential and distributed application framework for discrete event simulation developed in the Sloop team at INRIA Sophia-Antipolis. Prosit provides a programming environment which can be used at the same time by the simulationist to study his models and by the researcher as a workbench to test and validate his research results. The Prosit approach specifies both a modeling process and an execution architecture, and is based on several ideas and techniques stemming from contemporary distributed system and object programming research. It provides an active object programming model in which the simulated entities interact via reified function calls in the simulated time. Distributed simulation is achieved through remote service call and active object migration. Different synchronization protocols are currently being implemented. The flexible system design of Prosit seamlessly supports various simulation paradigms.

Objectives

The system is currently designed and implemented with three main concerns in mind: (1) take advantage of the object paradigm; (2) ease of use and versatility; (3) performance. Discrete event simulation and object oriented languages have a long common history -- probably started back in 1967 with the Simula language. With Prosit, we go a step further in the symbiosis by embedding the simulation engine in the mechanisms supporting the object paradigm (objects creation, objects interaction, etc.). Several features of the object paradigm are of particular interest:

Building a Simulation with Prosit

A Prosit simulation consists in a collection of concurrently active objects interacting, via service calls, in the simulated time. An active object encapsulates both the specification and the behavior of the modeled entity. The behavior of an entity consists in the internal management of services and the interactions with other active objects. Service management includes the management of arriving requests and the policy to create and schedule service activities. Coding a simulation consists in building the interacting entities and constructing the model by instantiating and initializing the components. The whole process is described by the next Figure. The Prosit framework includes the following base classes: To mask the simulation paradigm to the final user of the simulator, a library programmer defines a set of classes for a specific field of application. These classes, gathered in a library, allow a model to be built at a higher description level than the one corresponding to the simulation paradigm.

An Active Object Programming Model

The runtime model is based on multi-active objects interacting via service calls in the simulated time. With the help of a reification mechanism service calls are similar to member function calls. In the distributed extension, the programmer can dynamically distribute his active objects and manage them independently of their location. Distributed execution is achieved through remote service call and active object migration.

Active Simulation Objects

Active simulation objects are the basic components that make up the model. For example in a queuing network model, queues and customers are active objects. An active object executes its main activity in an autonomous way, independently of, and concurrently with, other active objects. Active objects can also be multi-active by controlling secondary activities running concurrently in the simulated time. Each active object has a clock giving its current simulation time.

When created, the active object is automatically managed by the kernel, and is ready to be activated. Its activation time is indicated at creation as a parameter of the constructor. Each active object has a unique identifier, called its OID, and a name which can be specified by the programmer. When activated, the object begins to execute its main activity.

During its life, the object can either be in a running mode (currently executing or consuming time), in a blocked mode (blocked due to a synchronous request) or in a sleeping mode (it has put itself in idle--wait state, waiting to be re--activated by another object). The object is considered to be finished when its main activity has terminated. We distinguish normal termination, abort (the object decided to terminate prematurely) and external abort (another object has used the termination primitive).

To be active, an object must be instanciated from a class (active class) inheriting directly or indirectly from the system class Active_Obj. Such a class-based process must at least define the main activity of its instances.

Activities

Activities are used to concurrently execute member functions of active objects. The main activity executes the behave() function and the {\em secondary activities} any other member functions. For example in a queuing network simulation, customer behaviors and services executions are activities.

An activity has a duration in the simulated time and can halt and be reactivated later on. Between suspension and reactivation some predefined or random simulation time may have elapsed. An activity will halt either when explicitly consuming time (wait()), when making a synchronous request to an active object, or when terminating.

An activity terminates when the executed function finishes, but system primitives can be used to terminate prematurely. An activity is an instance of the system class Activity. Each activity has a clock giving its current simulation time.

Interaction

The basic interaction between active objects is service call, and we have unified member function calls and service calls. The call can either be synchronous (the calling activity blocks until termination of the service) or asynchronous (the calling activity carries on immediately). Only synchronous calls can have return values. The service parameters and the return value objets are passed by copy if they are passive, and by reference if they are active. We also distinguish two levels of parameters in a service call: Furthermore, each call issued to an object is reified into an object itself. This mechanism allows the server to manipulate the service calls as objects: they can be stored in waiting queues, passed as parameters to functions, etc. It is then possible to explicitly program the service policy.

The request management policy and the service progress depend on the receiving object characteristics. The receiver decides whether, when and how to execute the service. This means that a request can be fully or partly served or even not served at all. In order to take appropriate actions, the caller of a synchronous request not fully served is always informed.

Distributed Extension of the Active Object Model

To code his distributed simulation, the programmer uses system mechanisms and language primitives allowing him to dynamically create new computing nodes, distribute his active objects and manage them independently of their location.

Distributed Execution Architecture

The Prosit distributed model introduces the concept of Cluster. A cluster can be seen as a sub-simulator located on a virtual Computer. Dynamically, the programmer creates new computers, allocates clusters on them and distributes his active objects among the clusters (for this paper, it suffices to assume that a cluster is equivalent to a unix process). The purpose of a cluster is to define a synchronization group composed of co-located active objects sharing the same address space and the same CPU. Co-located active objects are managed by a dedicated simulation kernel and share the same synchronization scheme. Both intra-cluster and inter-cluster synchronizations are implemented. The clusters can be located on heterogeneous machines, and each cluster knows the list of available computers and clusters. Using those 2 abstractions, the programmer can manage the runtime configuration of his simulation; he has complete control of data location, parallelism granularity and synchronization.

To create a new computer, the programmer instanciates the system class Computer and gives as parameters a symbolic name and the network name of the machine. Many computers can be associated to the same physical machine, providing that they all have different symbolic names. Symbolic naming is convenient and allows to define a virtual architecture useful when mapping the simulation to different configurations. A configuration file associating symbolic names to physical machines can also be supplied. It will automatically be used by the runtime to instanciate computers.

A cluster is created by instanciating the system class Cluster. By default the cluster is located on the less loaded computer\footnote{ We are currently working on techniques and heuristics for model partitioning and active objects mapping, but those topics are beyond the scope of this paper.}, but the programmer can also specify, to the allocator, the target computer

Computer *computer1 = new Computer(``huba'',``uranus.solar.fr''); Computer *computer2 = new Computer(``hop'',``neptune.solar.fr''); ...... Cluster *cluster1 = new(Computer::current()) Cluster; Cluster *cluster2 = new(computer2) Cluster; Cluster *cluster3 = new(``huba'') Cluster; The Computer and Cluster classes have various management primitives allowing for example to retrieve the list of available clusters or computers, to get the location of a cluster, to get the less loaded computer, etc.

Extending the Active Objects Capabilities

Each active object is mapped into a cluster which can be indicated, at creation, by the programmer or selected by the runtime according to a load balancing and synchronization scheme. When an active object owns a handle on another active object, it is able to invoke any of its member functions independently of the object's location. As in the sequential model, the service parameters and the return value objets are automatically passed by copy if they are passive, and by reference if they are active. Service calls are syntactically and semantically identical in the two models. There are three ways to get a handle on an active object: (1) to be the creator of the active object; (2) to have received the handle as a service parameter or return value; (3) to have invoked the bind() primitive with either the object identifier or the object name. Door *entrance = new Door; Door *exit = new(cluster1) Door; Butcher *butcher = Butcher::bind(``delicatessen''); ......... Various primitives are available allowing to manage the active objects: the is_remote() primitive indicates if the object is on the current cluster or on a remote one, the get_cluster() primitive returns the cluster hosting the object, etc.

We have also added migration capability to the active objects. During its execution, the object can explicitly migrate to another cluster by using the primitive migrate(Cluster~*). Implicit and automatic migration, from the simulationist point of view, can be implemented if the call is embedded in the code implementing the reification mechanism. Further to migration, the runtime system updates the context of the active object: the passive objects are copied and references on active objects are updated. The migrant object continues execution transparently, as if on its original cluster. This approach entails the migration of a requesting entity with its timestamp to the remote cluster hosting the requested server. Major advantages of migration include load balancing, memory management, synchronization optimization and increased locality reference.

if (entrance->is_remote()) migrate(entrance->get_cluster()); sync entrance->enter(...); // this is a local service call
Back to Prosit homepage
This page is maintained by Philippe Mussi and Günther Siegel