Quick start¶
This section is intended to get you to the point of running a basic HPX program as quickly as possible. To that end we skip many details but instead give you hints and links to more details along the way.
We assume that you are on a Unix system with access to reasonably recent
packages. You should have cmake
and make
available for the build system
(pkg-config
is also supported, see Using HPX with pkg-config).
Getting HPX¶
Download a tarball of the latest release from HPX Downloads and
unpack it or clone the repository directly using git
:
git clone https://github.com/STEllAR-GROUP/hpx.git
It is also recommended that you check out the latest stable tag:
git checkout 1.3.0
HPX dependencies¶
The minimum dependencies needed to use HPX are Boost and Portable Hardware Locality (HWLOC). If these
are not available through your system package manager, see
Installing Boost and Installing Hwloc for instructions on how
to build them yourself. In addition to Boost and Portable Hardware Locality (HWLOC), it is recommended
that you don’t use the system allocator, but instead use either tcmalloc
from google-perftools (default) or jemalloc for better performance. If you
would like to try HPX without a custom allocator at this point you can
configure HPX to use the system allocator in the next step.
A full list of required and optional dependencies, including recommended versions is available at Prerequisites.
Building HPX¶
Once you have the source code and the dependencies, set up a separate build directory and configure the project. Assuming all your dependencies are in paths known to CMake, the following gets you started:
# In the HPX source directory
mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/install/path ..
make install
This will build the core HPX libraries and examples, and install them to your
chosen location. If you want to install HPX to system folders simply leave out
the CMAKE_INSTALL_PREFIX
option. This may take a while. To speed up the
process launch more jobs by passing the -jN
option to make
.
Tip
Do not set only -j
(i.e. -j
without an explicit number of jobs)
unless you have a lot of memory available on your machine.
Tip
If you want to change CMake variables for your build it is usually a good
idea to start with a clean build directory to avoid configuration problems.
It is especially important that you use a clean build directory when changing
between Release
and Debug
modes.
If your dependencies are in custom locations you may need to tell CMake where to find them by passing one or more of the following options to CMake:
-DBOOST_ROOT=/path/to/boost
-DHWLOC_ROOT=/path/to/hwloc
-DTCMALLOC_ROOT=/path/to/tcmalloc
-DJEMALLOC_ROOT=/path/to/jemalloc
If you want to try HPX without using a custom allocator pass
-DHPX_WITH_MALLOC=system
to CMake.
Important
If you are building HPX for a system with more than 64 processing units you
must change the CMake variables HPX_WITH_MORE_THAN_64_THREADS
(to
On
) and HPX_WITH_MAX_CPU_COUNT
(to a value at least as big as the
number of (virtual) cores on your system).
To build the tests run make tests
. To run the tests run either make test
or use ctest
for more control over which tests to run. You can run single
tests for example with ctest --output-on-failure -R
tests.unit.parallel.algorithms.for_loop
or a whole group of tests with ctest
--output-on-failure -R tests.unit
.
If you did not run make install
earlier do so now or build the
hello_world_1
example by running:
make hello_world_1
HPX executables end up in the bin
directory in your build directory. You
can now run hello_world_1
and should see the following output:
./bin/hello_world_1
Hello World!
You’ve just run an example which prints Hello World!
from the HPX runtime.
The source for the example is in examples/quickstart/hello_world_1.cpp
. The
hello_world_distributed
example (also available in the
examples/quickstart
directory) is a distributed hello world program which is
described in Remote execution with actions: Hello world. It provides a gentle introduction to
the distributed aspects of HPX.
Tip
Most build targets in HPX have two names: a simple name and
a hierarchical name corresponding to what type of example or
test the target is. If you are developing HPX it is often helpful to run
make help
to get a list of available targets. For example, make help |
grep hello_world
outputs the following:
... examples.quickstart.hello_world_2
... hello_world_2
... examples.quickstart.hello_world_1
... hello_world_1
... examples.quickstart.hello_world_distributed
... hello_world_distributed
It is also possible to build e.g. all quickstart examples using make
examples.quickstart
.
Hello, World!¶
The following CMakeLists.txt
is a minimal example of what you need in order to
build an executable using CMake and HPX:
cmake_minimum_required(VERSION 3.3.2)
project(my_hpx_project CXX)
find_package(HPX REQUIRED)
add_hpx_executable(my_hpx_program
SOURCES main.cpp
COMPONENT_DEPENDENCIES iostreams)
Note
You will most likely have more than one main.cpp
file in your project.
See the section on Using HPX with CMake-based projects for more details on how to use
add_hpx_executable
.
Note
COMPONENT_DEPENDENCIES iostreams
is optional for a minimal project but
lets us use the HPX equivalent of std::cout
, i.e. the HPX
The HPX I/O-streams component functionality in our application.
Create a new project directory and a CMakeLists.txt
with the contents above.
Also create a main.cpp
with the contents below.
// Including 'hpx/hpx_main.hpp' instead of the usual 'hpx/hpx_init.hpp' enables
// to use the plain C-main below as the direct main HPX entry point.
#include <hpx/hpx_main.hpp>
#include <hpx/include/iostreams.hpp>
int main()
{
// Say hello to the world!
hpx::cout << "Hello World!\n" << hpx::flush;
return 0;
}
Then, in your project directory run the following:
mkdir build && cd build
cmake -DCMAKE_PREFIX_PATH=/path/to/hpx/installation ..
make all
./my_hpx_program
The program looks almost like a regular C++ hello world with the exception of
the two includes and hpx::cout
. When you include hpx_main.hpp
some
things will be done behind the scenes to make sure that main
actually gets
launched on the HPX runtime. So while it looks almost the same you can now use
futures, async
, parallel algorithms and more which make use of the HPX
runtime with lightweight threads. hpx::cout
is a replacement for
std::cout
to make sure printing never blocks a lightweight thread. You can
read more about hpx::cout
in The HPX I/O-streams component. If you rebuild and run your
program now you should see the familiar Hello World!
:
./my_hpx_program
Hello World!
Note
You do not have to let HPX take over your main function like in the
example. You can instead keep your normal main function, and define a
separate hpx_main
function which acts as the entry point to the HPX
runtime. In that case you start the HPX runtime explicitly by calling
hpx::init
:
// Copyright (c) 2007-2012 Hartmut Kaiser
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
///////////////////////////////////////////////////////////////////////////////
// The purpose of this example is to initialize the HPX runtime explicitly and
// execute a HPX-thread printing "Hello World!" once. That's all.
//[hello_world_2_getting_started
#include <hpx/hpx_init.hpp>
#include <hpx/include/iostreams.hpp>
int hpx_main(int, char**)
{
// Say hello to the world!
hpx::cout << "Hello World!\n" << hpx::flush;
return hpx::finalize();
}
int main(int argc, char* argv[])
{
return hpx::init(argc, argv);
}
//]
You can also use hpx::start
and hpx::stop
for a
non-blocking alternative, or use hpx::resume
and
hpx::suspend
if you need to combine HPX with other runtimes.
See Starting the HPX runtime for more details on how to initialize and run the HPX runtime.
Caution
When including hpx_main.hpp
the user-defined main
gets renamed and
the real main
function is defined by HPX. This means that the
user-defined main
must include a return statement, unlike the real
main
. If you do not include the return statement you may end up with
confusing compile time errors mentioning user_main
or even runtime
errors.
Writing task-based applications¶
So far we haven’t done anything that can’t be done using the C++ standard library. In this section we will give a short overview of what you can do with HPX on a single node. The essence is to avoid global synchronization and break up your application into small, composable tasks whose dependencies control the flow of your application. Remember, however, that HPX allows you to write distributed applications similarly to how you would write applications for a single node (see Why HPX? and Writing distributed HPX applications).
If you are already familiar with async
and future
s from the C++ standard
library, the same functionality is available in HPX.
The following terminology is essential when talking about task-based C++ programs:
- lightweight thread: Essential for good performance with task-based programs. Lightweight refers to smaller stacks and faster context switching compared to OS-threads. Smaller overheads allow the program to be broken up into smaller tasks, which in turns helps the runtime fully utilize all processing units.
async
: The most basic way of launching tasks asynchronously. Returns afuture<T>
.future<T>
: Represents a value of typeT
that will be ready in the future. The value can be retrieved withget
(blocking) and one can check if the value is ready withis_ready
(non-blocking).shared_future<T>
: Same asfuture<T>
but can be copied (similar tostd::unique_ptr
vsstd::shared_ptr
).- continuation: A function that is to be run after a previous task has run
(represented by a future).
then
is a method offuture<T>
that takes a function to run next. Used to build up dataflow DAGs (directed acyclic graphs).shared_future
s help you split up nodes in the DAG and functions likewhen_all
help you join nodes in the DAG.
The following example is a collection of the most commonly used functionality in HPX:
#include <hpx/hpx_main.hpp>
#include <hpx/include/iostreams.hpp>
#include <hpx/include/lcos.hpp>
#include <hpx/include/parallel_generate.hpp>
#include <hpx/include/parallel_sort.hpp>
#include <random>
#include <vector>
void final_task(hpx::future<hpx::util::tuple<hpx::future<double>, hpx::future<void>>>)
{
hpx::cout << "in final_task" << hpx::endl;
}
// Avoid ABI incompatibilities between C++11/C++17 as std::rand has exception
// specification in libstdc++.
int rand_wrapper()
{
return std::rand();
}
int main(int, char**)
{
// A function can be launched asynchronously. The program will not block
// here until the result is available.
hpx::future<int> f = hpx::async([]() { return 42; });
hpx::cout << "Just launched a task!" << hpx::endl;
// Use get to retrieve the value from the future. This will block this task
// until the future is ready, but the HPX runtime will schedule other tasks
// if there are tasks available.
hpx::cout << "f contains " << f.get() << hpx::endl;
// Let's launch another task.
hpx::future<double> g = hpx::async([]() { return 3.14; });
// Tasks can be chained using the then method. The continuation takes the
// future as an argument.
hpx::future<double> result = g.then([](hpx::future<double>&& gg)
{
// This function will be called once g is ready. gg is g moved
// into the continuation.
return gg.get() * 42.0 * 42.0;
});
// You can check if a future is ready with the is_ready method.
hpx::cout << "Result is ready? " << result.is_ready() << hpx::endl;
// You can launch other work in the meantime. Let's sort a vector.
std::vector<int> v(1000000);
// We fill the vector synchronously and sequentially.
hpx::parallel::generate(hpx::parallel::execution::seq,
std::begin(v), std::end(v), &rand_wrapper);
// We can launch the sort in parallel and asynchronously.
hpx::future<void> done_sorting =
hpx::parallel::sort(
hpx::parallel::execution::par( // In parallel.
hpx::parallel::execution::task), // Asynchronously.
std::begin(v),
std::end(v));
// We launch the final task when the vector has been sorted and result is
// ready using when_all.
auto all = hpx::when_all(result, done_sorting).then(&final_task);
// We can wait for all to be ready.
all.wait();
// all must be ready at this point because we waited for it to be ready.
hpx::cout <<
(all.is_ready() ? "all is ready!" : "all is not ready...") << hpx::endl;
return hpx::finalize();
}
Try copying the contents to your main.cpp
file and look at the output. It can
be a good idea to go through the program step by step with a debugger. You can
also try changing the types or adding new arguments to functions to make sure
you can get the types to match. The type of the then
method can be especially
tricky to get right (the continuation needs to take the future as an argument).
Note
HPX programs accept command line arguments. The most important one is
--hpx:threads
=N
to set the number of OS-threads used by
HPX. HPX uses one thread per core by default. Play around with the
example above and see what difference the number of threads makes on the
sort
function. See Launching and configuring HPX applications for more details on
how and what options you can pass to HPX.
Tip
The example above used the construction hpx::when_all(...).then(...)
. For
convenience and performance it is a good idea to replace uses of
hpx::when_all(...).then(...)
with dataflow
. See
Dataflow: Interest calculator for more details on dataflow
.
Tip
If possible, prefer to use the provided parallel algorithms instead of writing your own implementation. This can save you time and the resulting program is often faster.
Next steps¶
If you haven’t done so already, reading the Terminology section will help you get familiar with the terms used in HPX.
The Examples section contains small, self-contained walkthroughs of example HPX programs. The Local to remote: 1D stencil example is a thorough, realistic example starting from a single node implementation and going stepwise to a distributed implementation.
The Manual contains detailed information on writing, building and running HPX applications.