-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update README with content from brief announcement. (#616)
* Start updating README. * Add sorting example. * Fix link. * Actually fix the link. * Add plot to README. * Comparision table. * Complete table. * Aligment. * Fix GCC build for sorting example. * Update README.md Co-authored-by: Florian Kurpicz <[email protected]> * Update README.md Co-authored-by: Florian Kurpicz <[email protected]> * Update README.md Co-authored-by: Florian Kurpicz <[email protected]> * Update README.md Co-authored-by: Florian Kurpicz <[email protected]> * Also fill vector in kamping gatherv example. * More details on resize policies. * Better logo usage in README. --------- Co-authored-by: Florian Kurpicz <[email protected]>
- Loading branch information
1 parent
7fcd6c5
commit d1dd3ff
Showing
7 changed files
with
822 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,145 @@ | ||
# KaMPIng: Karlsruhe MPI next generation | ||
# KaMPIng: Karlsruhe MPI next generation :rocket: | ||
|
||
![KaMPIng logo](./docs/images/logo.svg) | ||
|
||
This is KaMPIng [kampɪŋ], an MPI wrapper which makes using MPI feel like real C++ | ||
|
||
KaMPIng is developed at the [Algorithm Engineering Group](https://algo2.iti.kit.edu/english/index.php) at Karlsruhe Institute of Technology. | ||
|
||
## Goals | ||
- ban `man MPI_*` from your command line history | ||
- zero-overhead whenever possible | ||
- if not possible this must be clear to the user | ||
- easy to use when simple communication is required, but powerful enough for finely tuned behavior | ||
- useful defaults | ||
- compile-time checks for incorrect usage | ||
- accumulate knowledge/algorithms of our group in a single place | ||
- ensure that the library outlives multiple generations of our group | ||
|
||
## Platform | ||
- compiles with GCC, Clang and ICC on SuperMUC-NG, bwUniCluster, HoreKa | ||
- C++17 | ||
- we do not want to rely on C++20 features, because Intel does not support it yet | ||
- easy inclusion into other projects by using modern CMake | ||
This is KaMPIng [kampɪŋ], a (near) zero-overhead MPI wrapper for modern C++. | ||
|
||
It covers the whole range of abstraction levels from low-level MPI calls to | ||
convenient STL-style bindings, where most parameters are inferred from a small | ||
subset of the full parameter set. This allows for both rapid prototyping and | ||
fine-tuning of distributed code with predictable runtime behavior and memory | ||
management. | ||
|
||
Using template-metaprogramming, only code paths required for computing | ||
parameters not provided by the user are generated at compile time, which results in (near) zero-overhead | ||
bindings. | ||
|
||
KaMPIng is developed at the [Algorithm Engineering | ||
Group](https://ae.iti.kit.edu/english/index.php) at Karlsruhe Institute of | ||
Technology. | ||
|
||
## Features :sparkles: | ||
### Named Parameters :speech_balloon: | ||
Using plain MPI, operations like `MPI_Allgatherv` often lead to verbose and error-prone boilerplate code: | ||
|
||
``` c++ | ||
std::vector<T> v = ...; // Fill with data | ||
int size; | ||
MPI_Comm_size(comm, &size); | ||
int n = static_cast<int>(v.size()); | ||
std::vector<int> rc(size), rd(size); | ||
MPI_Allgather(&n, 1, MPI_INT, rc.data(), 1, MPI_INT, comm); | ||
std::exclusive_scan(rc.begin(), rc.end(), rd.begin(), 0); | ||
int n_glob = rc.back() + rd.back(); | ||
std::vector<T> v_glob(v_global_size); | ||
MPI_Allgatherv(v.data(), v_size, MPI_TYPE, v_glob.data(), rc.data(), rd.data(), MPI_TYPE, comm); | ||
|
||
``` | ||
In contrast, KaMPIng introduces a streamlined syntax inspired by Python's named parameters. For example, the `allgatherv` operation becomes more intuitive and concise: | ||
```c++ | ||
std::vector<T> v = ...; // Fill with data | ||
std::vector<T> v_glob = comm.allgatherv(send_buf(v)); | ||
``` | ||
|
||
Empowered by named parameters, KaMPIng allows users to name and pass parameters in arbitrary order, computing default values only for the missing ones. This not only improves readability but also streamlines the code, providing a user-friendly and efficient way of writing MPI applications. | ||
|
||
### Controlling memory allocation :floppy_disk: | ||
KaMPIng's *resize policies* allow for fine-grained control over when allocation happens: | ||
|
||
| resize policy | | | ||
|--------------------------|-------------------------------------------------------------------------| | ||
| `kamping::resize_to_fit` | resize the container to exactly accommodate the data | | ||
| `kamping::no_resize` | assume that the container has enough memory available to store the data | | ||
| `kamping::grow_only` | only resize the container if it not large enough | | ||
|
||
|
||
``` c++ | ||
// easy to use with sane defaults | ||
std::vector<int> v = comm.recv<int>(source(kamping::rank::any)); | ||
|
||
// flexible memory control | ||
std::vector<int> v_out; | ||
v_out.resize(enough_memory_to_fit); | ||
// already_known_counts are the recv_counts that may have been computed already earlier and thus do not need to be computed again | ||
comm.recv<int>(recv_buf<kamping::no_resize>(v_out), recv_count(i_know_already_know_that), source(kamping::rank::any)); | ||
``` | ||
|
||
### STL support :books: | ||
- KaMPIng works with everything that is a `std::contiguous_range`, everywhere. | ||
- Builtin C++ types are automatically mapped to their corresponding MPI types. | ||
- All internally used containers can be altered via template parameters. | ||
### Expandability :jigsaw: | ||
- Don't like the performance of your MPI implementation's reduce algorithm? Just override it using our plugin architecture. | ||
- Add additional functionality to communicator objects, without altering any application code. | ||
- Easy to integrate with existing MPI code. | ||
- Flexible core library for a new toolbox :toolbox: of distributed datastructures and algorithms | ||
|
||
### And much more ... :arrow_upper_right: | ||
- Easy non-blocking communication via request pools. | ||
- Compile time and runtime error checking (which can be completely deactivated). | ||
- Collective hierarchical timers to speed up your evaluation workflow. | ||
- ... | ||
|
||
Dive into the documentation or tests to find out more ... | ||
|
||
### (Near) zero overhead - for development and performance :chart_with_upwards_trend: | ||
Using template-metaprogramming, KaMPIng only generates the code paths required for computing parameters not provided by the user. | ||
The following shows a complete implementation of distributed sample sort with KaMPIng. | ||
|
||
```c++ | ||
void sort(MPI_Comm comm_, std::vector<T>& data, size_t seed) { | ||
Communicator<> comm(comm_); | ||
size_t const oversampling_ratio = 16 * static_cast<size_t>(std::log2(comm.size())) + 1; | ||
std::vector<T> local_samples(oversampling_ratio); | ||
std::sample(data.begin(), data.end(), local_samples.begin(), oversampling_ratio, std::mt19937{seed}); | ||
auto global_samples = comm.allgather(send_buf(local_samples)).extract_recv_buffer(); | ||
std::sort(global_samples.begin(), global_samples.end()); | ||
for (size_t i = 0; i < comm.size() - 1; i++) { | ||
global_samples[i] = global_samples[oversampling_ratio * (i + 1)]; | ||
} | ||
global_samples.resize(num_splitters); | ||
std::vector<std::vector<T>> buckets(global_samples.size() + 1); | ||
for (auto& element: data) { | ||
auto const bound = std::upper_bound(global_samples.begin(), global_samples.end(), element); | ||
buckets[static_cast<size_t>(bound - global_samples.begin())].push_back(element); | ||
} | ||
data.clear(); | ||
std::vector<int> scounts; | ||
for (auto& bucket: buckets) { | ||
data.insert(data.end(), bucket.begin(), bucket.end()); | ||
scounts.push_back(static_cast<int>(bucket.size())); | ||
} | ||
data = comm.alltoallv(send_buf(data), send_counts(scounts)).extract_recv_buffer(); | ||
std::sort(data.begin(), data.end()); | ||
} | ||
``` | ||
It is a lot more concise than the [(verbose) plain MPI implementation](./examples/applications/sample-sort/mpi.hpp), but also introduces no additional overhead to achieve this, as can be seen the following experiment. There we compare the sorting implementation in KaMPIng to other MPI bindings. | ||
![](./plot.svg) | ||
## Platform :desktop_computer: | ||
- intensively tested with GCC and Clang and OpenMPI | ||
- requires a C++17 ready compiler | ||
- easy integration into other projects using modern CMake | ||
## LICENSE | ||
## Other MPI bindings | ||
| | [MPI](https://www.mpi-forum.org/) | [Boost.MPI](https://www.boost.org/doc/libs/1_84_0/doc/html/mpi.html) | [RWTH MPI](https://github.com/VRGroupRWTH/mpi) | [MPL](https://github.com/rabauke/mpl) | ![KaMPIng](./docs/images/icon.svg) | | ||
|------------------------------------------------------|:---------------------------------:|:--------------------------------------------------------------------:|:----------------------------------------------:|:-------------------------------------:|:-----------------------------------------------:| | ||
| STL support | :x: | :heavy_check_mark:[^2] | :heavy_check_mark:[^3] | :heavy_check_mark:[^2] | :white_check_mark: | | ||
| computation of defaults via additional communication | :x: | :x: | :white_check_mark: | :x: | :white_check_mark: | | ||
| custom reduce operations via lambdas | :x: | :white_check_mark: | :x: | :heavy_check_mark:[^4] | :white_check_mark: | | ||
| containers can be resized automatically | :x: | :heavy_check_mark:[^1] | :heavy_check_mark:[^3] | :x: | :white_check_mark: | | ||
| error handling | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | | ||
| actively maintained | :white_check_mark: | :x: | :heavy_check_mark: | :white_check_mark: | :white_check_mark: | | ||
[^1]: partial | ||
[^2]: only `std::vector` | ||
[^3]: only for send and receive buffers | ||
[^4]: not mapped to builtin operations | ||
## LICENSE | ||
KaMPIng is released under the GNU Lesser General Public License. See [COPYING](COPYING) and [COPYING.LESSER](COPYING.LESSER) for details |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// This file is part of KaMPIng. | ||
// | ||
// Copyright 2024 The KaMPIng Authors | ||
// | ||
// KaMPIng is free software : you can redistribute it and/or modify it under the terms of the GNU Lesser General Public | ||
// License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later | ||
// version. KaMPIng is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the | ||
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License | ||
// for more details. | ||
// | ||
// You should have received a copy of the GNU Lesser General Public License along with KaMPIng. If not, see | ||
// <https://www.gnu.org/licenses/>. | ||
#pragma once | ||
#include <algorithm> | ||
#include <cstddef> | ||
#include <vector> | ||
template <typename T> | ||
void pick_splitters(size_t num_splitters, size_t oversampling_ratio, std::vector<T>& global_samples) { | ||
std::sort(global_samples.begin(), global_samples.end()); | ||
for (size_t i = 0; i < num_splitters; i++) { | ||
global_samples[i] = global_samples[oversampling_ratio * (i + 1)]; | ||
} | ||
global_samples.resize(num_splitters); | ||
} | ||
|
||
template <typename T> | ||
auto build_buckets(std::vector<T>& data, std::vector<T>& splitters) -> std::vector<std::vector<T>> { | ||
std::vector<std::vector<T>> buckets(splitters.size() + 1); | ||
for (auto& element: data) { | ||
auto const bound = std::upper_bound(splitters.begin(), splitters.end(), element); | ||
buckets[static_cast<size_t>(bound - splitters.begin())].push_back(element); | ||
} | ||
data.clear(); | ||
return buckets; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// This file is part of KaMPIng. | ||
// | ||
// Copyright 2024 The KaMPIng Authors | ||
// | ||
// KaMPIng is free software : you can redistribute it and/or modify it under the terms of the GNU Lesser General Public | ||
// License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later | ||
// version. KaMPIng is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the | ||
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License | ||
// for more details. | ||
// | ||
// You should have received a copy of the GNU Lesser General Public License along with KaMPIng. If not, see | ||
// <https://www.gnu.org/licenses/>. | ||
#pragma once | ||
#include <random> | ||
|
||
#include <kamping/collectives/allgather.hpp> | ||
#include <kamping/collectives/alltoall.hpp> | ||
#include <kamping/communicator.hpp> | ||
|
||
#include "./common.hpp" | ||
namespace kamping { | ||
template <typename T> | ||
void sort(MPI_Comm comm_, std::vector<T>& data, size_t seed) { | ||
Communicator<> comm(comm_); | ||
size_t const oversampling_ratio = 16 * static_cast<size_t>(std::log2(comm.size())) + 1; | ||
std::vector<T> local_samples(oversampling_ratio); | ||
std::sample(data.begin(), data.end(), local_samples.begin(), oversampling_ratio, std::mt19937{seed}); | ||
auto global_samples = comm.allgather(send_buf(local_samples)).extract_recv_buffer(); | ||
pick_splitters(comm.size() - 1, oversampling_ratio, global_samples); | ||
auto buckets = build_buckets(data, global_samples); | ||
std::vector<int> scounts; | ||
for (auto& bucket: buckets) { | ||
data.insert(data.end(), bucket.begin(), bucket.end()); | ||
scounts.push_back(static_cast<int>(bucket.size())); | ||
} | ||
data = comm.alltoallv(send_buf(data), send_counts(scounts)).extract_recv_buffer(); | ||
std::sort(data.begin(), data.end()); | ||
} | ||
} // namespace kamping |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// This file is part of KaMPIng. | ||
// | ||
// Copyright 2024 The KaMPIng Authors | ||
// | ||
// KaMPIng is free software : you can redistribute it and/or modify it under the terms of the GNU Lesser General Public | ||
// License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later | ||
// version. KaMPIng is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the | ||
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License | ||
// for more details. | ||
// | ||
// You should have received a copy of the GNU Lesser General Public License along with KaMPIng. If not, see | ||
// <https://www.gnu.org/licenses/>. | ||
#pragma once | ||
#include <random> | ||
|
||
#include "./common.hpp" | ||
#include "kamping/mpi_datatype.hpp" | ||
namespace mpi { | ||
template <typename T> | ||
void sort(MPI_Comm comm, std::vector<T>& data, size_t seed) { | ||
int rank, size; | ||
MPI_Comm_rank(comm, &rank); | ||
MPI_Comm_size(comm, &size); | ||
size_t const oversampling_ratio = 16 * static_cast<size_t>(std::log2(size)) + 1; | ||
std::vector<T> local_samples(oversampling_ratio); | ||
std::sample(data.begin(), data.end(), local_samples.begin(), oversampling_ratio, std::mt19937{seed}); | ||
std::vector<T> global_samples(local_samples.size() * static_cast<size_t>(size)); | ||
MPI_Allgather( | ||
local_samples.data(), | ||
static_cast<int>(local_samples.size()), | ||
kamping::mpi_datatype<T>(), | ||
global_samples.data(), | ||
static_cast<int>(local_samples.size()), | ||
kamping::mpi_datatype<T>(), | ||
comm | ||
); | ||
|
||
pick_splitters(static_cast<size_t>(size) - 1, oversampling_ratio, global_samples); | ||
auto buckets = build_buckets(data, global_samples); | ||
std::vector<int> sCounts, sDispls, rCounts(static_cast<size_t>(size)), rDispls(static_cast<size_t>(size)); | ||
size_t send_pos = 0; | ||
for (auto& bucket: buckets) { | ||
data.insert(data.end(), bucket.begin(), bucket.end()); | ||
sCounts.push_back(static_cast<int>(bucket.size())); | ||
sDispls.push_back(static_cast<int>(send_pos)); | ||
send_pos += bucket.size(); | ||
} | ||
MPI_Alltoall(sCounts.data(), 1, MPI_INT, rCounts.data(), 1, MPI_INT, comm); | ||
|
||
// exclusive prefix sum of recv displacements | ||
std::exclusive_scan(rCounts.begin(), rCounts.end(), rDispls.begin(), 0); | ||
std::vector<T> rData(static_cast<size_t>(rDispls.back() + rCounts.back())); | ||
MPI_Alltoallv( | ||
data.data(), | ||
sCounts.data(), | ||
sDispls.data(), | ||
kamping::mpi_datatype<T>(), | ||
rData.data(), | ||
rCounts.data(), | ||
rDispls.data(), | ||
kamping::mpi_datatype<T>(), | ||
comm | ||
); | ||
std::sort(rData.begin(), rData.end()); | ||
rData.swap(data); | ||
} | ||
|
||
} // namespace mpi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// This file is part of KaMPIng. | ||
// | ||
// Copyright 2024 The KaMPIng Authors | ||
// | ||
// KaMPIng is free software : you can redistribute it and/or modify it under the terms of the GNU Lesser General Public | ||
// License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later | ||
// version. KaMPIng is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the | ||
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License | ||
// for more details. | ||
// | ||
// You should have received a copy of the GNU Lesser General Public License along with KaMPIng. If not, see | ||
// <https://www.gnu.org/licenses/>. | ||
#include <algorithm> | ||
#include <iostream> | ||
#include <random> | ||
#include <vector> | ||
|
||
#include <kamping/collectives/gather.hpp> | ||
#include <kamping/communicator.hpp> | ||
#include <kamping/measurements/printer.hpp> | ||
#include <kamping/measurements/timer.hpp> | ||
#include <mpi.h> | ||
|
||
#include "./kamping.hpp" | ||
#include "./mpi.hpp" | ||
|
||
template <typename T> | ||
bool globally_sorted(MPI_Comm comm, std::vector<T> const& data, std::vector<T>& original_data) { | ||
kamping::Communicator kamping_comm(comm); | ||
auto global_data = kamping_comm.gatherv(kamping::send_buf(data)).extract_recv_buffer(); | ||
auto global_data_original = kamping_comm.gatherv(kamping::send_buf(original_data)).extract_recv_buffer(); | ||
std::sort(global_data_original.begin(), global_data_original.end()); | ||
return global_data_original == global_data; | ||
} | ||
|
||
template <typename T> | ||
auto generate_data(size_t n_local, size_t seed) -> std::vector<T> { | ||
std::mt19937 eng(seed + kamping::world_rank()); | ||
std::uniform_int_distribution<T> dist(0, std::numeric_limits<T>::max()); | ||
std::vector<T> data(n_local); | ||
auto gen = [&] { | ||
return dist(eng); | ||
}; | ||
std::generate(data.begin(), data.end(), gen); | ||
return data; | ||
} | ||
|
||
int main(int argc, char* argv[]) { | ||
kamping::Environment env; | ||
size_t n_local; | ||
size_t seed = 42; | ||
if (argc < 2) { | ||
std::cerr << "Usage: " << argv[0] << " <n_local> [seed]" << std::endl; | ||
kamping::comm_world().abort(); | ||
return 1; | ||
} | ||
n_local = std::stoul(argv[1]); | ||
if (argc > 2) { | ||
seed = std::stoul(argv[2]); | ||
} | ||
using element_type = uint64_t; | ||
size_t local_seed = seed + kamping::world_rank() + kamping::world_size(); | ||
{ | ||
std::vector<element_type> data = generate_data<element_type>(n_local, seed); | ||
kamping::sort(MPI_COMM_WORLD, data, local_seed); | ||
} | ||
{ | ||
std::vector<element_type> data = generate_data<element_type>(n_local, seed); | ||
mpi::sort(MPI_COMM_WORLD, data, local_seed); | ||
} | ||
return 0; | ||
} |
Oops, something went wrong.