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:
- modularity and reusability, allowing both Prosit programmers and
end-users to develop high-level model libraries. These libraries may include
sub-models for high-level subsystems, or highly optimized simulation classes
for commonly used sub-systems. A modular and hierarchical approach in model
design and implementation is then possible;
- target independence: user programs (models and experiment
descriptions) need few modifications (in the optimal case only a new
compilation) so as to run efficiently on the different environments
(sequential or distributed) and under the different synchronization schemes
(optimistic and conservative variants). The application programmers
choose between sequential or parallel implementation by inheriting
from different system classes;
- extensibility: various tools may be incorporated, with little or no
changes in basic or user written classes. These tools include statistics
gathering and processing, automatic load-balancing, animation, sub-model
aggregation, analytical solvers, etc.
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:
- simulation classes used to build the simulation engine;
- modeling classes used to program models. The modeling classes are used
to create structurally and functionally identical entities.
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:
- the service parameters which are related to the model
semantics. They are used by the activity when executing the
service member function;
- the control parameters related to the management
of the service (eg. priority, maximum execution time,
etc.). They are used by the active object to establish the
request's service policy.
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