The ResourceSet Class

While a multi-process program operates within a potentially massive runtime environment, the reality is that each process only has direct access to the resources which are physically local to that process. Using other resources requires sending data and/or tasks to another process. The ResourceSet class provides an abstraction for interacting with the resources local to a given process.

Getting a ResourceSet

Each RuntimeView functions as a container of ResourceSet objects. The number of ResourceSet objects will be equal to the number of processes being managed by the RuntimeView. Assuming rv is a RuntimeView object then we can get the ResourceSet for process 0 via:

// Get the ResourceSet for rank 0
const auto& rank_0_rs = rv.at(0);

It is important to understand that rank_0_rs is the set of resources local to process 0. So trying to use the resources in rank_0_rs from any process other than rank 0 will usually involve communication. To avoid communication one should work with their local ResourceSet which can be obtained by:

// Get the resource set containing resources local to the current process
const auto& my_rs = rv.my_resource_set();

You can quickly figure out if a ResourceObject describes local or remote resources by:

// Determine if the rank_0_rs is local or not
const bool is_local = rank_0_rs.is_mine();

Accessing Hardware

ResourceSet objects are containers of resources. As an example, let’s say we wanted to access the local RAM or the RAM local to rank 0. Then one could do:

// Get the local RAM
const auto& my_ram = my_rs.ram();

// Get a view of the RAM owned by rank 0
const auto& ram_0 = rank_0_rs.ram();

Using the RAM is described The RAM Class. Similar methods exist for accessing other hardware such as the CPUs and GPUs. It is worth noting that not all computers have all types of hardware. While we expect that just about every computer has RAM, we can verify this by:

// Do I locally have RAM?
const bool do_i_have_ram = my_rs.has_ram();

// Does rank 0 have RAM?
const bool does_0_have_ram = rank_0_rs.has_ram();

Again similar methods are available for determining if other hardware is present or not. These methods can be a convenient mechanism for dispatching based on whether certain hardware is available or not.

Process-Local Logging

Program-wide logging is done through the RuntimeView, process-local logging is done through the ResourceSet class. Process-local logging works nearly identical to program-wide logging. For example, if we want each process to log whether its value of is_local this is done by:

// Have each resource set print whether it's rank 0 or not
my_rs.logger().debug("Am I rank 0: " + std::to_string(is_local));

Compared to program-wide logging the main difference is that with process-local logging each process can (but doesn’t necessarily) log to a different sink. This makes it much easier to figure out what each process did when going back over the logs.

Warning

Care has been taken to ensure that replicated data can always be written to the process-local logger. The reverse is not true. In particular, deadlock can occur if the program-wide logger is called from a block of source code that is not executed by all processes. Sinks in general try to avoid implementations which can deadlock, but it is not guaranteed.

Typically process local-logging uses file sinks, with one file per process. Especially if there’s more than one process per filesystem this can have notable performance degradation. For this reason, the process local sinks are by default directed to null sinks (they don’t print). Generally speaking, log messages for process-local logs should have a severity of trace or debug as most users will only turn them on when debugging is needed.