Skip to content

Commit 76f21ad

Browse files
GordonGordon
authored andcommitted
Issue #50: Make execution_resource iterable.
* Replace execution_resource::resources with execution_resource::begin and execution_resource::end. * Add execution_resource::size. * Add execution_resource::operator[]. * Add aliases to execution_resource to support new interface. * Improve woring for execution resource and system topology. * Update examples to suppoort change. * Rename this_system::resources to this_system::discover_topology.
1 parent 684be18 commit 76f21ad

File tree

1 file changed

+83
-40
lines changed

1 file changed

+83
-40
lines changed

affinity/cpp-20/d0796r3.md

Lines changed: 83 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
### P0796r3 (SAN 2018)
1818

19+
* Make `execution_resource`s iterable by replacing `execution_resource::resources` with `execution_resource::begin` and `execution_resource::end`.
20+
* Add `size` and `operator[]` for `execution_resource`.
21+
* Rename `this_system::get_resources` to `this_system::discover_topology`.
22+
1923
### P0796r2 (RAP 2018)
2024

2125
* Introduce a free function for retrieving the execution resource underlying the current thread of execution.
@@ -211,26 +215,39 @@ Below *(Listing 2)* is an example of executing a parallel task over 8 threads us
211215

212216
## Execution resource topology
213217

214-
### Execution resources
218+
### System topology
215219

216-
An `execution_resource` is a lightweight structure which acts as an identifier to particular piece of hardware within a system. It can be queried for whether it can allocate memory via `can_place_memory`, whether it can execute work via `can_place_agents`, and for its name via `name`. An `execution_resource` can also represent other `execution_resource`s. We call these *members of* that `execution_resource`, and can be queried via `resources`. Additionally the `execution_resource` which another is a *member of* can be queried via `member_of`. An `execution_resource` can also be queried for the concurrency it can provide, the total number of *threads of execution* supported by that *execution_resource*, and all resources it represents.
220+
The **system topology** is comprised of a directed acyclic graph (DAG) of **execution resources**, representing all unique hardware and software components available within the system capable of executing work. The root node of the DAG is the **system execution resource** and represents the entire system. Each **execution resource** within the DAG may have any number of child **execution resources** representing a finer granularity of the parent **execution resource**. Every **execution resource** within the **system topology** is exposed via an `execution_resource` object.
217221

218-
> [*Note:* Note that an execution resource is not limited to resources which execute work, but also a general resource where no execution can take place but memory can be allocated such as off-chip memory. *--end note*]
222+
The **system topology** can be discovered by calling `this_system::discover_topology`. This will discover all **execution resources** available within the system and construct the **system topology** DAG, describing a read-only snapshot at the point of the call, and then return an `execution_resource` object exposing the **system execution resource**.
219223

220-
> [*Note:* The intention is that the actual implementation details of a resource topology are described in an execution context when required. This allows the execution resource objects to be lightweight objects that serve as identifiers that are only referenced. *--end note*]
224+
> [*Note:* A call to `this_system::discover_topology` may invoke C++ library calls, system calls or third party library APIs required to discover certain **execution resources**. *--end note*]
221225
222-
### System topology
226+
### Execution resources
227+
228+
An `execution_resource` is a lightweight structure which identifies a particular **execution resource** within a snapshot of the **system topology**. It can be queried for whether the associated **execution resource** can allocate memory via `can_place_memory`, whether the associated **execution resource** can execute work via `can_place_agents`, and for a name via `name`.
223229

224-
The system topology is made up of a number of system-level `execution_resource`s, which can be queried through `this_system::get_resources` which returns a `std::vector`. A run-time library may initialize the `execution_resource`s available within the system dynamically. However, this must be done before `main` is called, given that after that point, the system topology may not change.
230+
An `execution_resource` object can be queried for a pointer to it's parent `execution_resource` via `member_of`, and can also be iterated over for it's child `execution_resource`s via `begin` and `end`.
225231

226-
Below *(Listing 3)* is an example of iterating over the system-level resources and printing out their capabilities.
232+
An `execution_resource` object can also be queried for the amount concurrency it can provide, the total number of **threads of execution** supported by the associated **execution resource**.
233+
234+
> [*Note:* An **execution resource** is not limited to resources which execute work, but also a general resource where no execution can take place but memory can be allocated such as off-chip memory. *--end note*]
235+
236+
Below *(Listing 3)* is an example of iterating over every **execution resource** within the **system topology** and printing out their capabilities.
227237

228238
```cpp
229-
for (auto res : execution::this_system::get_resources()) {
230-
std::cout << res.name() `\n`;
231-
std::cout << res.can_place_memory() << `\n`;
232-
std::cout << res.can_place_agents() << `\n`;
233-
std::cout << res.concurrency() << `\n`;
239+
void print_topology(const execution::execution_resource &resource, int indent = 0) {
240+
for (int i = 0; i < indent; i++) { std::cout << " "; }
241+
std::cout << resource.name() << ": " << resource.can_place_memory() << ", "
242+
<< resource.can_place_agents() << ", " << resource.concurrency() << "\n";
243+
for (const execution::execution_resource child : resource) {
244+
print_topology(child, indent + 1);
245+
}
246+
}
247+
248+
int main(int argc, char * argv[]) {
249+
auto systemResource = this_system::discover_topology();
250+
print_topology(systemResource);
234251
}
235252
```
236253
*Listing 3: Example of querying all the system level execution resources*
@@ -242,14 +259,13 @@ The `affinity_query` class template provides an abstraction for a relative affin
242259
Below *(listing 4)* is an example of how to query the relative affinity between two `execution_resource`s.
243260
244261
```cpp
245-
auto systemLevelResources = execution::this_system::get_resources();
246-
auto memberResources = systemLevelResources.resources();
262+
auto systemResource = this_system::discover_topology();
247263
248264
auto relativeLatency01 = execution::affinity_query<execution::affinity_operation::read,
249-
execution::affinity_metric::latency>(memberResources[0], memberResources[1]);
265+
execution::affinity_metric::latency>(systemResource[0], systemResource[1]);
250266
251267
auto relativeLatency02 = execution::affinity_query<execution::affinity_operation::read,
252-
execution::affinity_metric::latency>(memberResources[0], memberResources[2]);
268+
execution::affinity_metric::latency>(systemResource[0], systemResource[2]);
253269
254270
auto relativeLatency = relativeLatency01 > relativeLatency02;
255271
```
@@ -264,27 +280,27 @@ The `execution_context` class provides an abstraction for managing a number of l
264280
Below *(Listing 5)* is an example of how this extended interface could be used to construct an *execution context* from an *execution resource* which is retrieved from the *system’s resource topology*. Once an *execution context* is constructed it can then still be queried for its *execution resource*, and that *execution resource* can be further partitioned.
265281

266282
```cpp
267-
auto &resources = execution::this_system::get_resources();
283+
auto systemResource = std::this_system::discover_topology();
268284

269-
execution::execution_context execContext(resources[0]);
285+
execution::execution_context execContext(systemResource[0]);
270286

271-
auto &systemLevelResource = execContext.resource();
287+
auto &execResource = execContext.resource();
272288

273-
// resource[0] should be equal to execResource
289+
// systemResource[0] should be equal to execResource
274290

275-
for (auto res : systemLevelResource.resources()) {
276-
std::cout << res.name() << `\n`;
291+
for (const execution::execution_resource res : execResource()) {
292+
std::cout << res.name() << "\n";
277293
}
278294
```
279295
*Listing 5: Example of constructing an execution context from an execution resource*
280296
281297
When creating an `execution_context` from a given `execution_resource`, the executors and allocators associated with it are bound to that `execution_resource`. For example, when creating an `execution_resource` from a CPU socket resource, all executors associated with the given socket will spawn execution agents with affinity to the socket partition of the system *(Listing 6)*.
282298
283299
```cpp
284-
auto cList = std::execution::this_system::get_resources();
300+
auto systemResource = std::this_system::discover_topology();
285301
// FindASocketResource is a user-defined function that finds a
286302
// resource that is a CPU socket in the given resource list
287-
auto& socket = findASocketResource(cList);
303+
auto& socket = findASocketResource(systemResource);
288304
execution_contextC{socket} // Associated with the socket
289305
auto executor = eC.executor(); // By transitivity, associated with the socket too
290306
auto socketAllocator = eC.allocator(); // Retrieve an allocator to the closest memory node
@@ -326,18 +342,31 @@ A *thread of execution* can be requested to bind to a particular `execution_reso
326342
class execution_resource {
327343
public:
328344

345+
using pointer = execution_resource *;
346+
using const_pointer = const execution_resource *;
347+
using iterator = execution_resource *;
348+
using const_iterator = const execution_resource *;
349+
using reference = execution_resource &;
350+
using const_reference = const execution_resource &;
351+
using size_type = std::size_t;
352+
329353
execution_resource() = delete;
330354
execution_resource(const execution_resource &);
331355
execution_resource(execution_resource &&);
332356
execution_resource &operator=(const execution_resource &);
333357
execution_resource &operator=(execution_resource &&);
334358
~execution_resource();
335359

336-
size_t concurrency() const noexcept;
360+
size_type size() const noexcept;
361+
362+
const_iterator begin() const noexcept;
363+
const_iterator end() const noexcept;
364+
365+
const_reference operator[](int child) const noexcept;
337366

338-
std::vector<resource> resources() const noexcept;
367+
const_pointer member_of() const noexcept;
339368

340-
const execution_resource member_of() const noexcept;
369+
size_t concurrency() const noexcept;
341370

342371
std::string name() const noexcept;
343372

@@ -403,7 +432,7 @@ A *thread of execution* can be requested to bind to a particular `execution_reso
403432
/* This system */
404433

405434
namespace this_system {
406-
std::vector<execution_resource> resources() noexcept;
435+
const execution_resource discover_topology();
407436
}
408437

409438
/* This thread */
@@ -447,7 +476,7 @@ The `execution_resource` class provides an abstraction over a system's hardware,
447476
448477
### `execution_resource` constructors
449478

450-
execution_resource();
479+
execution_resource() = delete;
451480

452481
> [*Note:* An implementation of `execution_resource` is permitted to provide non-public constructors to allow other objects to construct them. *--end note*]
453482
@@ -468,25 +497,37 @@ The `execution_resource` class provides an abstraction over a system's hardware,
468497

469498
*Returns:* The total concurrency available to this resource. More specifically, the number of *threads of execution* collectively available to this `execution_resource` and any resources which are *members of*, recursively.
470499

471-
std::vector<resource> resources() const noexcept;
500+
size_type size() const noexcept;
501+
502+
*Returns:* The number of child `execution_resource`s.
503+
504+
const_iterator begin() const noexcept;
472505

473-
*Returns:* All `execution_resource`s which are *members of* this resource.
506+
*Returns:* A const iterator to the beggining of the child `execution_resource`s.
507+
508+
const_iterator end() const noexcept;
509+
510+
*Returns:* A const iterator to the end of the child `execution_resource`s.
511+
512+
const_reference operator[](int child) const noexcept;
513+
514+
*Returns:* A const reference to the specified child `execution_resource`s.
474515

475516
const execution_resource &member_of() const noexcept;
476517

477-
*Returns:* The `execution_resource` which this resource is a *member of*.
518+
*Returns:* The parent `execution_resource`.
478519

479520
std::string name() const noexcept;
480521

481522
*Returns:* An implementation defined string.
482523

483524
bool can_place_memory() const noexcept;
484525

485-
*Returns:* If this resource is capable of allocating memory with affinity, 'true'.
526+
*Returns:* If the associated **execution resource* is capable of allocating memory with affinity, 'true'.
486527

487528
bool can_place_agent() const noexcept;
488529

489-
*Returns:* If this resource is capable of execute with affinity, 'true'.
530+
*Returns:* If the associated **execution resource* is capable of execute with affinity, 'true'.
490531

491532
## Class `execution_context`
492533

@@ -589,17 +630,19 @@ The `affinity_query` class template provides an abstraction for a relative affin
589630
590631
## Free functions
591632

592-
### `this_system::get_resources`
633+
### `this_system::discover_topology`
634+
635+
The free function `this_system::discover_topology` is provided for discovering the **system topology**.
593636

594-
The free function `this_system::get_resources` is provided for retrieving the `execution_resource`s which encapsulate the hardware platforms available within the system. We refer to these resources as the *system level resources*.
637+
const execution_resource discover_topology();
595638

596-
std::vector<execution_resource> resources() noexcept;
639+
*Returns:* An `execution_resource` object exposing the **system execution resource**.
597640

598-
*Returns:* An `std::vector` containing all *system level resources*.
641+
*Requires:* If `this_system::discover_topology().size() > 0`, `this_system::discover_topology()[0]` be the `execution_resource` use by `std::thread`. Calls to `this_system::discover_topology()` may not introduce a data race with any other call to `this_system::discover_topology()`.
599642

600-
*Requires:* If `this_system::get_resources().size() > 0`, `this_system::get_resources()[0]` be the `execution_resource` use by `std::thread`. The value returned by `this_system::get_resources()` be the same at any point after the invocation of `main`.
643+
*Effects:* Discovers all **execution resources** available within the system and constructs the **system topology** DAG, describing a read-only snapshot at the point of the call.
601644

602-
> [*Note:* Returning a `std::vector` allows users to potentially manipulate the container of `execution_resource`s after it is returned. We may want to replace this at a later date with an alternative type which is more restrictive, such as a range or span. *--end note*]
645+
*Throws:* Any exception thrown as a result of **system topology** discovery.
603646

604647
### `this_thread::bind` & `this_thread::unbind`
605648

0 commit comments

Comments
 (0)