Charades
|
The source code for the Charades simulation engine is all located in src/. In order to build Charades paths to Charades and Charm++ need to be configured correctly.
src/config.mk.template
) to src/config.mk
. All submodules and models will include this config file to compile correctly.src/config.mk
to point to your Charm++ directory and Charades src/ directory. Charm++ must already be built according to instructions here: https://charm.readthedocs.io/en/latest/. Currently, support only exists for non-smp Charm builds.After the build completes, all object files and charades.a
can be found in src/build/. Models need to be linked to this library in order to work.
When compiling and linking a model, the Makefile for the model must include two things:
config.mk
as was created while building the Charades library so that the compiler has the correct paths to includes, and uses the same compile-time options as the library.Compiling will create a model executable and (possibly) a charmrun launch script (depending on the current Charm++ build you used).
Example models and the associated Makefiles can be found in the models/ directory.
To run a compiled model, use the charmrun
launch script to run the model on the desired number of processors. For example, to run on N
processors use the +p
argument to the charmrun
script:
./charmrun +pN ./model <model-args>
Depending on the Charm++
build and execution environment, you may also need to include the ++local
command-line flag to charmrun
as well. More information on launching Charm applications can be found in the Charm++ manual: https://charm.readthedocs.io/en/latest/.
There are three basic steps for writing models in Charades. First, you must define your Logical Processes (LPs) and events. LPs and events dictate how your simulation runs, and make up the bulk of your models workload. Second, you must tell the simulator how to create and initially place your LPs. This is done by defining LPFactory and LPMapper subclasses. Finally, a simple main function must be written to initialize the Charades library and configuration settings before running the simulation.
These three steps are described below.
LPs are objects in your simulation, which are defined by inheriting from the base LP class, and overriding methods for setup, teardown, and event handling for the various types of events the LP will need to handle. Events are simply C++ objects.
The base LP class in Charades utilizes the Curiously Recurring Template Pattern (CRTP), and takes a variable number of template parameters depending on the number of different event types the LP will have to handle. For example, to define an LP, SampleLP
, that can handle both FooEvent
and BarEvent
event types, the class header would look as follows:
There are five methods which determine the behavior of an LP and can be overridden/defined by derived classes:
forward(...)
, reverse(...)
, and commit(...)
event handlers: A derived LP class must define these methods for each event type they wish to handle. They are not overrides of the methods in the LP class, but rather overloads which are dispatched to by LP based on event type. These methods are called during the simulation to actually process events as they are scheduled by the simulator. At minimum, an LP needs a forward(EventType* e, tw_bf* bf)
definition for each event type it can handle according to its declaration. This method is called when an event is ready to be executed by the LP.If you want your model to run with optimistic synchronization by using speculative event execution, then you will also need to define reverse(EventType* e, tw_bf* bf)
and possibly commit(EventType* e, tw_bf* bf)
for each relevant event type. The reverse(...)
method is called when the effects of an incorrectly executed event need to be reversed. The commit(...)
method is called when an event that was previously executed is known to have been correctly scheduled and is therefore being committed. If the event would have any irreversible side effects such as writing to file or stdout, those can be done at this time.
The tw_bf fields are bitfields that can be used in conjunction with optimistic synchronization to save information about the forward execution path in order to correctly reverse it if need be.
A complete skeleton for our previously mentioned SampleLP
would therefore look like this:
Example models showing LP and event definition can be found in the models/ subdirectory of the Charades distribution. Specifically, the Example model and the PHOLD models are good places to start looking.
The public API exposed by Charades for creating/sending events, generating random numbers, etc is documented here: Public API.
In order for the simulator to be able to create and map LPs for the simulation, the model must define a subclass of the LPFactory class and, optionally, a subclass of the LPMapper class.
The LPFactory interface defines a single method: LPBase* LPFactory::create_lp(uint64_t gid) const. This method take a global LP id and creates and returns a pointer to the LP which has that ID. Continuing with our above example, a basic LPFactory would look like:
For models with more than one LP type, or more complex LPs, create_lp can use the gid to determine which LP to create. This can be seen in both the PHOLD and Example models in the 'models' subdirectory.
The LPMapper object is used to map LPs to execution units, which in Charades are Charm++ objects called chares. LPs mapped to the same chare will always exist on the same hardware resources, will be migrated together, and share queues for pending and processed events. Communication between LPs on the same chare is instantaneous and involves simply enqueueing an event into their shared queue. Communication between LPs on different chares is handled by the Charm++ runtime system and may involve network communication if the chares are not currently on the same computational node.
The LPMapper interface declares 4 pure virtual methods which must be overridden by a concrete derived class:
gid
is mapped to.gid
within the chare it is mapped to.cid
) and local id (lid
), returns the corresponding global id. Must be consistent with the results of LPMapper::get_chare_id and LPMapper::get_local_id.cid
), returns the number of LPs that that chare contains.The default mapper in Charades is a simple block mapping of chares, and can be found in src/setup/mapper.h
. It uses the global parameter g_lps_per_chare to determine how many LPs per chare. Other relevant global parameters for determining simulation size and mapping are: g_total_lps and g_num_chares.
The purpose of the main() function is to set a few global variables used for simulation configuration, and make a few API calls that hook into the engine before starting the simulation.
Many of the globals for configuration can be set using the command line as well and running the model with --help
will show the available options, so in this guide we will mostly focus on other requirements of the main function.
The first (or second thing) you main function should do is call tw_init(...) and pass in the command line arguments. This sets up the simulator and parses configuration options from the command line. The only thing that may be done before this is adding model specific command line options to be parsed by using ArgumentSets and tw_add_arguments(...).
After tw_init(...) is called, event types must be registered, and global configuration variables may be set. For each event type in your model, call register_msg_type to make the simulator aware of the different types of events that will be sent during execution. Globals such as g_total_lps can also be set at this point, although keep in mind that any set here will overwrite values passed in on the command line.
Once the events have been registered and the configuration variables are set, call tw_create_simulation(LPFactory*, LPMapper*). A pointer to an LPFactory must be passed as the first argument, but the second argument for an LPMapper is optional. If ignored, the default BlockMapper will be used. This creates all of the LPs for the simulation according to the configuration chosen, as well as the simulation scheduler and any other simulation components, such as a GVTManager, depending on how the simulation is being run.
Finally, call tw_run() and tw_end(). The call to tw_run() first calls the LPBase::initialize() method on all LPs, then runs the simulation to completion. The tw_end() call cleans up the simulation resources and exits the application.