HPX - High Performance ParalleX

PrevUpHomeNext

Accumulator

The accumulator example demonstrates the use of components. Components are C++ classes that expose methods as a type of HPX action. These actions are called component actions.

Components are globally named, meaning that a component action can be called remotely (e.g. from another machine). There are two accumulator examples in HPX; managed_accumulator and simple_accumulator (we will talk more about the differences between the two later). This tutorial will examine the managed_accumulator variant.

In the Fibonacci Example and the Hello World Example, we introduced plain actions, which wrapped global functions. The target of a plain action is an identifier which refers to a particular machine involved in the computation. For plain actions, the target is the machine where the action will be executed.

Component actions, however, do not target machines. Instead, they target component instances. The instance may live on the machine that we've invoked the component action from, or it may live on another machine.

The component in this example exposes three different functions:

This example creates an instance of the accumulator, and then allows the user to enter commands at a prompt, which subsequently invoke actions on the accumulator instance.

Setup

The source code for this example can be found here: managed_accumulator_client.cpp.

To compile this program, go to your HPX build directory (see Getting Started for information on configuring and building HPX) and enter:

make examples.accumulator.managed_accumulator

To run the program type:

./bin/managed_accumulator_client

Once the program starts running, it will print the following prompt and then wait for input. An example session is given below:

commands: reset, add [amount], query, help, quit
> add 5
> add 10
> query
15
> add 2
> query
17
> reset
> add 1
> query
1
> quit
Walkthrough

Now, let's take a look at the source code of the managed_accumulator example. This example consists of two parts: an HPX component library (a library that exposes an HPX component) and a client application which uses the library. This walkthrough will cover the HPX component library. The code for the client application can be found here: managed_accumulator_client.cpp.

An HPX component is represented by three C++ classes:

Typically, these three classes all have the same name, but stubs and server classes usually live in different sub-namespaces (server and stubs respectively). For example, the full names of the three classes in managed_accumulator are:

The Server Class

The following code is from: server/managed_accumulator.hpp.

All HPX component server classes must inherit publicly from an HPX component base class. There are currently two component base classes:

The managed_accumulator component inherits from hpx::components::locking_hock<>. This allows the runtime system to ensure that all action invocations are serialized. That means that the system ensures that no two actions are invoked at the same time on a given component instance. This makes the component thread safe and no additional locking has to be implemented by the user. Moreover, managed_accumulator component is a managed component, because it also inherits from hpx::components::managed_component_base<> (the template argument passed to locking_hook is used as its base class). The following snippet shows the corresponding code:

class managed_accumulator
  : public hpx::components::locking_hook<
        hpx::components::managed_component_base<managed_accumulator>
    >

Our accumulator class will need a data member to store its value in, so let's declare a data member:

private:
    argument_type value_;

The constructor for this class simply initializes value_ to 0:

managed_accumulator() : value_(0) {}

Next, let's look at the three methods of this component that we will be exposing as component actions:

/// Reset the value to 0.
void reset()
{
    // set value_ to 0.
    value_= 0;
}

/// Add the given number to the accumulator.
void add(argument_type arg)
{
    // add value_ to arg, and store the result in value_.
    value_ += arg;
}

/// Return the current value to the caller.
argument_type query() const
{
    // Get the value of value_.
    return value_;
}

Here are the action types. These types wrap the methods we're exposing. The wrapping technique is very similar to the one used in the Fibonacci Example and the Hello World Example:

HPX_DEFINE_COMPONENT_ACTION(managed_accumulator, reset);
HPX_DEFINE_COMPONENT_ACTION(managed_accumulator, add);
HPX_DEFINE_COMPONENT_CONST_ACTION(managed_accumulator, query);

The last piece of code in the server class header is the declaration of the action type registration code:

HPX_REGISTER_ACTION_DECLARATION(
    examples::server::managed_accumulator::reset_action,
    managed_accumulator_reset_action);

HPX_REGISTER_ACTION_DECLARATION(
    examples::server::managed_accumulator::add_action,
    managed_accumulator_add_action);

HPX_REGISTER_ACTION_DECLARATION(
    examples::server::managed_accumulator::query_action,
    managed_accumulator_query_action);
[Note] Note

The code above must be placed in the global namespace.

The rest of the registration code is in managed_accumulator.cpp.

///////////////////////////////////////////////////////////////////////////////
// Add factory registration functionality.
HPX_REGISTER_COMPONENT_MODULE();

///////////////////////////////////////////////////////////////////////////////
typedef hpx::components::managed_component<
    examples::server::managed_accumulator
> accumulator_type;

HPX_REGISTER_MINIMAL_COMPONENT_FACTORY(accumulator_type, managed_accumulator);

///////////////////////////////////////////////////////////////////////////////
// Serialization support for managed_accumulator actions.
HPX_REGISTER_ACTION(
    accumulator_type::wrapped_type::reset_action,
    managed_accumulator_reset_action);
HPX_REGISTER_ACTION(
    accumulator_type::wrapped_type::add_action,
    managed_accumulator_add_action);
HPX_REGISTER_ACTION(
    accumulator_type::wrapped_type::query_action,
    managed_accumulator_query_action);
[Note] Note

The code above must be placed in the global namespace.

The Stubs Class

The following code is from stubs/managed_accumulator.hpp.

All stubs classes must inherit from the stubs base class, hpx::components::stub_base<>:

struct managed_accumulator
  : hpx::components::stub_base<server::managed_accumulator>

The stubs class contains helper functions which invoke actions on component instances. There are a few different ways of invoking actions:

static void reset_non_blocking(hpx::naming::id_type const& gid)
{
    typedef server::managed_accumulator::reset_action action_type;
    hpx::apply<action_type>(gid);
}
static hpx::lcos::future<argument_type>
query_async(hpx::naming::id_type const& gid)
{
    typedef server::managed_accumulator::query_action action_type;
    return hpx::async<action_type>(gid);
}
static void
add_sync(hpx::naming::id_type const& gid, argument_type arg)
{
    typedef server::managed_accumulator::add_action action_type;
    hpx::async<action_type>(gid, arg).get();
}

hpx::naming::id_type is a type which represents a global identifier in HPX. This is the type that is returned by hpx::find_here(). This type specifies the target of an action.

The Client Class

The following code is from managed_accumulator.hpp.

The client class is the primary interface to a component instance. Client classes are used to create components:

examples::managed_accumulator c;
c.create(hpx::find_here()); // Create a component on this machine.

and to invoke component actions:

c.add_sync(4);

Clients, like stubs and servers, need to inherit from a base class, this time, hpx::components::client_base<>:

class managed_accumulator
  : public hpx::components::client_base<
        managed_accumulator, stubs::managed_accumulator
    >

For readability, we typedef the base class like so:

typedef hpx::components::client_base<
    managed_accumulator, stubs::managed_accumulator
> base_type;

Here are examples of how to expose actions through a client class:

void reset_non_blocking()
{
    HPX_ASSERT(this->get_gid());
    this->base_type::reset_non_blocking(this->get_gid());
}
hpx::lcos::future<argument_type> query_async()
{
    HPX_ASSERT(this->get_gid());
    return this->base_type::query_async(this->get_gid());
}
void add_sync(argument_type arg)
{
    HPX_ASSERT(this->get_gid());
    this->base_type::add_sync(this->get_gid(), arg);
}

Note that this->gid_ references a data member of the hpx::components::client_base<> base class.


PrevUpHomeNext