GSoC 2017
@STEllAR-GROUP/hpx
Project:
Re-implementing hpx::util::unwrapped
and unifying the API of hpx::wait
and hpx::when
by Denis Blank
Supervised by: Hartmut Kaiser
Overview
Abstract
In HPX we express dependencies between future results through the when_*, wait_* and dataflow functions, which are mostly used in combination with the unwrapped function that allows to work with plain values directly, rather than futures of values. My project added the ability to those functions to also accept arbitrary containers, tuple-like objects and move only types as well as the capability to deal with futures nested inside such types. For my GSoC project, I re-implemented and extended the existing solution of unwrapped into an independent implementation, which provide those functionalities for synchronous and asynchronous usage. This features will make it much easier to express dependencies between futures in HPX in the future while access its result in a convenient way.
Overview
The following report describes the progress that I’ve made during my GSoC 2017 project: “Re-implementing hpx::util::unwrapped and unifying the API of hpx::wait and hpx::when”.
The main task of this project was to provide two utility APIs for traversing and mapping objects in arbitrary variadic packs. In order to solve this task, I implemented a synchronous and an asynchronous version of the traversal API. Support for mapping across types is supported synchronously.
Both utility APIs are used to remove code duplication as well as making the APIs which unwrap or wait for futures more uniform. Additionally, the APIs introduce support for move only types as well as better support for objects which are deeply nested inside homogeneous containers (std::vector
, std::list
)
and tuple like containers (std::tuple
, hpx::util::tuple
, std::array
).
Below you can see, for which APIs the traversal and mapping APIs replace the internal implementation:
Through this contribution, many issues in the HPX issue tracker were approached or closed, for instance: #1126 #1132, #1400, #1404 and #2456.
A detailed description of my original project description is available here.
May
Community Bonding
During the community bonding phase, I approached some light defects in order to get used to the codebase as well as getting to know the HPX community.
- Documentation improvements:
e890af420c
Add documentation to invoke_fused and invoke9085f6b499
Add documentation to hpx::util::unwrapped and hpx::util::unwrapped2. * Also add some internal comments for improved guidance
- CMake improvements:
7952bcb596
Generatecmake_variables.qbk
andcmake_toolchains.qbk
outside of the source tree * Prevents built system caused working tree pollution0bb983f1d8
Fix cmake soft errors due to failing creation of links on windows * Prevents errors when generating the documentation on windows since cmake doesn’t provide thecreate_symlink
there. * We use cmd under windows to create links instead
- Doxygen improvements:
b993029f51
Extract all documentation entities in order to autolink functions correctly * Closes #26158f71f2a4e6
Hide thedetail
namespace in doxygen per default
June
Phase I - June
At the beginning of the first phase, I focused on fixing the requirements of my project.
Due to this I discovered the need of a spread
or explode
helper function which could invoke a callable object with the content of a given tuple directly, while passing non tuple like types through. A detailed description of my research can be found here.
In the remaining time of the first phase, I implemented the synchronous mapping and traversal API map_pack
and traverse_pack
, which was submitted as part of pull request #2704.
A minimalistic example of a typical use case of this API might be the following, where we map all values to floats:
1
2
3
4
5
hpx::util::tuple<float, std::vector<float>,
hpx::util::tuple<float, float>> res =
map_pack([](int i) {
return float(i);
}, 1, std::vector<int>{2, 3, 4}, hpx::util::make_tuple(5, 6));
The API fully supports all requirements mentioned above like arbitrarily nested containers
and move only types. Thus it’s a real improvement over the previous internal code
of the unwrapped
function.
Since the API isn’t fixed on mapping hpx::lcos::future
objects we can test it, with more generic unit tests. Also, we are able to test the mapping API and the future unwrap functionality independently from each other.
Additionally, the capability to test for compiler features directly with header only parts of HPX was added to CMake.
This is used to provide a generic feature test, that uses the traverse_pack
API, which tests whether the currently used compiler is capable of full expression SFINAE support.
- #2704 Implement the synchronous mapping and traversal API:
2de39aa622
Add support of std::array to hpx::util::tuple_size and tuple_elementaed3ff37d6
Add an is_tuple_like trait for sequenceable type detectione2fe00b6e2
Remove slashes before CMAKE_FILES_DIRECTORY variables * As stated by https://cmake.org/Wiki/CMake_Useful_Variables CMAKE_FILES_DIRECTORY contains a leading slash already, and should be used as following with the current binary directory:${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}
. Otherwise this will lead to paths with double slashes.bcf25f41bd
Fix custom include and link directories in config tests880cfb77f6
Append -D to COMPILE_DEFINITIONS passed to add_hpx_config_test7586970e85
Make it possible to run feature tests on header only parts of HPX * add_hpx_in_framework_config_test makes HPX and its dependencies available to the corresponding feature test.4788bdb6db
Add a synchronous mapping API * Can be used to traverse or map elements contained in any nested container or tuple-like type. * Meant for replacing the internal API of hpx::util::unwrapped and some functions which are used to wait on futures.7108fff97d
Add a feature test for full expression sfinae support * Tests whether the compiler provides full expression SFINAE supports so all parts of HPX can be compiled. * Currently MSVC is the only compiler that doesn’t implement it.
July
Phase II - July
Based on the mapping API which was implemented in phase one, unwrapped
was reimplemented. Pull request #2741 contributed the rewritten implementation of unwrapped
as well as the whole conversion of the HPX codebase.
Due to the requirement, that the new unwrapped should pass none future arguments through, it was considered to split the deferred and the immediate unwrapped into two separated interfaces, since the implementation selection of unwrapped
would be broken otherwise:
unwrap
: Unwraps a variadic pack of futures directlyunwrapping
: Creates a callable object that unwraps the futures
Also, multiple versions of unwrap
and unwrapping
are provided to unwrap
until a particular future depth of a given pack:
unwrap_n
andunwrapping_n
: Unwraps futures recursively until depthn
.unwrap_all
andunwrapping_all
: Unwraps all futures recursively which occur inside the pack.
A simple usage case of unwrap looks like the one below, where we map futures to their real values:
1
2
3
4
5
6
7
// Single arguments
int i1 = hpx:util::unwrap(hpx::lcos::make_ready_future(0));
// Multiple arguments
hpx::tuple<int, int> i2 =
hpx:util::unwrap(hpx::lcos::make_ready_future(1),
hpx::lcos::make_ready_future(2));
Currently, the old unwrapped
function forwards its input to unwrap
and unwrapping
,
so we are able to test the behavior of the new implementation.
The next step was to replace all occurrences of unwrapped
through
unwrap
and unwrapping
accordingly while officially deprecating the unwrapped
API.
Overall this step took me longer than expected, mainly because the exact behavior
of unwrapped
was unknown and thus it had to be derived from many unit tests and examples.
Due to the previously named reasons and because a future
- #2741 Deprecate unwrapped and implement unwrap and unwrapping:
6bd34c5f51
Move the unwrapped tests from lcos to util5c208ae687
Correctly return the exit code when the unwrapped unit test faileddcfb50363a
Fix tuple_cat when using l-value references * Add basic unit tests to ensure the correctness of tuple_cat.db4f99e61b
Fix tuple_cat when using r-value references (and move only types) * Add a unit tests for this.a0b96c0c4d
Fix the mapping of containers holding move only types2ef735bff6
Fix the mapping of arrray like heterogeneous containers444b431f89
Fix the mapping of l-value references (hpx::lcos::shared_future) * Add some unit tests to ensure the behaviour7c23554788
Fix the mapping of containers when mapping to references341025a131
Allow map_pack to apply 1:n mappingsac831b6375
Propagate empty mappings back to the caller * Required for returning empty mappings from vector<future> 0c6265e1d8
Implement the rewritten version of unwrapped (called unwrap and unwrapping) * unwrapped will be deprecated, until it’s removed unwrapped will use unwrap as underlying implementation. * The re-implementation and deprecation was required due to unresolvable issues in the old implementation, which mainly arised from the requirement to route non future values through.0a50f9c486
Remove the old implementation of unwrapped * Add a proxy which emulates its behaviour using the new unwrap API * The proxy will be flagged as deprecated once the usage of unwrapped was replaced inside all unit tests and examples.93c55adab0
Remove unsupported tests of unwrap * Disable deprecation warnings inside the legacy unwrapped tests1ef7985cb4
Map futureto zero arguments when unwrapping c093f475ac
Implement unit tests for unwrap and unwrapping * Partially re-uses the previous tests of tests/unit/util/unwrapped.cpp35bd75b730
Remove using namespace in the pack_traversal unit test * Correct the naming of some functionsc708feafe5
Rename the internal mapper invoke to invoke_mapper * Prevents wrong function selection caused through ADL * Also namespace a call to make_tuple1ba82eea86
Add constexpr and noexcept to the pack_traversal APIb6545ba263
Useunwrap
andunwrapping
in the core librarye4465fc968
Useunwrap
andunwrapping
in the examples347be2f5f5
Useunwrap
andunwrapping
in the tests6a32d0cee5
Replace the usage of boost::atomic in the unwrap test * Ref #27825982cf22a1
Deprecate hpx::util::unwrapped * Use hpx::util::unwrap and hpx::util::unwrapping instead, that clearify which underlying implementation should be used (the immediate or the deferred one). The automatic implementation selection was broken since unwrapped allowed to pass non future arguments through. * Closes #1400 * Closes #1404 * Closes #2456 * Ref #1126 * Ref #1132
August
Phase III - August
For the third phase of my GSoC project, I implemented the asynchronous traversal API, which makes it possible to traverse futures inside a variadic pack while being able to suspend the current execution context.
We need to provide a special visitor object for this API:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
struct my_async_visitor
{
/// The synchronous overload is called for each object,
/// it may return false to suspend the current control.
/// In that case the overload below is called.
template <typename T>
bool operator()(async_traverse_visit_tag, T&& element)
{
return true;
}
/// The asynchronous overload this is called when the
/// synchronous overload returned false.
/// In addition to the current visited element the overload is
/// called with a contnuation callable object which resumes the
/// traversal when it's called later.
/// The continuation next may be stored and called later or
/// dropped completely to abort the traversal early.
template <typename T, typename N>
void operator()(async_traverse_detach_tag, T&& element, N&& next)
{
}
/// The overload is called when the traversal was finished.
/// As argument the whole pack is passed over which we
/// traversed asynchrnously.
template <typename T>
void operator()(async_traverse_complete_tag, T&& pack)
{
}
};
Then we are able to traverse an arbitrary pack of futureswhile being able to suspend the current execution context at any time to resume it later.
1
2
3
4
hpx::future<void> f1;
hpx::future<int> f2;
util::traverse_pack_async(my_async_visitor{}, f1, 2, f2);
This implementation is equal to a stackless coroutine, however we may re-implement this API with the synchronous one as soon as co_await
is introduced and available in C++20.
Finally, I replaced the internal API of wait_all
and dataflow
with the synchronous and asynchronous traversal API, so both interfaces are able to deal with the same set or arguments (unification).
The capabilities of both functions were extended to the one of unwrapped
to deal with:
- nested types
- move only types
- arbitrary homogeneous and tuple-like containers
- passing non future types through
As positive side effect duplicated code in wait_all
and dataflow
was removed.
- Fixes for the unwrapped transition:
320f515335
Fix a parsing error with Visual Studio 2015 which occurred in unwrap * Ref #2741c442ec06e9
Fix a potential unconditial moves in hpx::util::tuple_cat * Thanks to K-ballo for pointing this out * We use hpx::util::get now for retrieving a specific element at index i instead of the internal tuple_element API. * Ref #27414ea728d97a
Fix a unit test failure on GCC in tuple_cat * Add a test which targets the same issue on other toolchainsf9ec761db1
Remove constexpr from a void function
- Improve the continous integration testing:
af4fdf501a
Add Visual Studio 2015 to the tested toolchains in Appveyor * Closes #2844
- #2829 Implement an API for asynchronous pack traversal:
Conclusion
Conclusion
For me personally, Google Summer of Code was a great experience because I learned a lot about Open-Source, collaboration, and development (C++ and high-performance computing in particular).
Also I really liked, that I had the chance to contribute to a major Open-Source project, while receiving professional advice.
The HPX community and maintainers (STEllAR-GROUP) were really helpful due to the whole program and provided me great support.
Special thanks go to my mentor Hartmut Kaiser who always was supportive.
I would definitely recommend others to apply for a Google Summer of Code stipend at @STEllAR-GROUP/hpx.