Skip to content

Commit

Permalink
Add std::reduce
Browse files Browse the repository at this point in the history
  • Loading branch information
alugowski committed Nov 14, 2023
1 parent 41e5eae commit 7250c8b
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 20 deletions.
9 changes: 7 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,19 @@ jobs:
llvm-version: "5"

- os: ubuntu-20.04
# oldest GCC that the setup-gcc action is able to install
description: "GCC 7"
gcc-version: "7"
cmake-version: "3.18"
test-amalgamation: true

- os: ubuntu-20.04
description: "GCC 9"
gcc-version: "9"

- os: ubuntu-latest
description: "GCC 13"
gcc-version: "13"
test-amalgamation: true

- os: ubuntu-latest
# default GCC, which has gcov
Expand Down Expand Up @@ -78,7 +82,8 @@ jobs:
shell: bash

- name: Benchmark
if: ${{ matrix.gcc-version != '7' }}
# Earliest GCC with all benchmarked methods is 9
if: matrix.gcc-version != '7' && matrix.gcc-version != '8'
run: |
cmake -S . -B bench_build/ -DCMAKE_BUILD_TYPE=Release -DPOOLSTL_BENCH=ON
cmake --build bench_build/ --config Release
Expand Down
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ target_include_directories(
$<INSTALL_INTERFACE:include>)

set(HEADER_FILES
include/poolstl/poolSTL.hpp
include/poolstl/poolstl.hpp
include/poolstl/algorithm
include/poolstl/execution
include/poolstl/numeric
include/poolstl/internal/utils.hpp
include/poolstl/internal/ttp_impl.hpp
include/poolstl/internal/task_thread_pool.hpp)

set_target_properties(${PROJECT_NAME} PROPERTIES PUBLIC_HEADER "${HEADER_FILES}")
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Use this if:
* you cannot link against TBB for whatever reason
* the [Parallel STL](https://www.intel.com/content/www/us/en/developer/articles/guide/get-started-with-parallel-stl.html) is too heavy

CI tests on GCC 7+, Clang/LLVM 5+, Apple Clang, MSVC
## Usage

PoolSTL defines `poolstl::par` and `poolstl::par_pool` execution policies.
Expand Down Expand Up @@ -69,6 +70,7 @@ Algorithms are added on an as-needed basis. If you need one that is not present
* [std::for_each_n](https://en.cppreference.com/w/cpp/algorithm/for_each_n)

### `<numeric>`
* [std::reduce](https://en.cppreference.com/w/cpp/algorithm/reduce)

Note: All iterators must be random access.

Expand Down
2 changes: 1 addition & 1 deletion benchmark/numeric_bench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ void reduce(benchmark::State& state) {
}

BENCHMARK(reduce<seq>)->UseRealTime();
//BENCHMARK(reduce<poolstl::par>)->UseRealTime();
BENCHMARK(reduce<poolstl_par>)->UseRealTime();
#ifdef POOLSTL_BENCH_STD_PAR
BENCHMARK(reduce<std_par>)->UseRealTime();
#endif
Expand Down
21 changes: 20 additions & 1 deletion include/poolstl/internal/ttp_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define POOLSTL_INTERNAL_TTP_IMPL_HPP

#include <future>
#include <numeric>
#include <vector>

#include "utils.hpp"
Expand All @@ -16,7 +17,7 @@ namespace poolstl {

template <class ExecPolicy, class RandIt, class UnaryFunction>
void parallel_for(ExecPolicy &&policy, RandIt first, RandIt last, UnaryFunction f) {
::std::vector<::std::future<void>> futures;
std::vector<std::future<void>> futures;
auto chunk_size = get_chunk_size(first, last, pool(policy).get_num_threads());

while (first < last) {
Expand All @@ -35,6 +36,24 @@ namespace poolstl {
future.get();
}
}

template <class ExecPolicy, class RandIt, class T, class BinaryOp>
T parallel_reduce(ExecPolicy &&policy, RandIt first, RandIt last, T init, BinaryOp binop) {
std::vector<std::future<T>> futures;
auto chunk_size = get_chunk_size(first, last, pool(policy).get_num_threads());

while (first < last) {
RandIt loop_end = chunk_advance(first, last, chunk_size);

futures.emplace_back(pool(policy).submit([init, binop](RandIt chunk_first, RandIt chunk_last) {
return cpp17::reduce(chunk_first, chunk_last, init, binop);
}, first, loop_end));

first = loop_end;
}

return cpp17::reduce(get_wrap(futures.begin()), get_wrap(futures.end()), init, binop);
}
}
}

Expand Down
77 changes: 71 additions & 6 deletions include/poolstl/internal/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,96 @@
#define POOLSTL_VERSION_PATCH 0

#include <cstddef>
#include <functional>
#include <iterator>

#if (__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)) \
&& (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE >= 9)
#define POOLSTL_HAVE_CXX17_LIB 1
#else
#define POOLSTL_HAVE_CXX17_LIB 0
#endif

#if __cplusplus >= 201402L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)
#define POOLSTL_HAVE_CXX14 1
#else
#define POOLSTL_HAVE_CXX14 0
#endif

namespace poolstl {
namespace internal {

inline constexpr std::size_t get_chunk_size(std::size_t num_steps, unsigned int num_threads) {
auto remainder = num_steps % num_threads;
return (num_steps / num_threads) + (remainder > 0 ? 1 : 0);
return (num_steps / num_threads) + ((num_steps % num_threads) > 0 ? 1 : 0);
}

template<typename Iterator>
constexpr typename std::iterator_traits<Iterator>::difference_type
get_chunk_size(Iterator first, Iterator last, unsigned int num_threads) {
using diff_t = typename std::iterator_traits<Iterator>::difference_type;
std::size_t num_steps = std::distance(first, last);
return static_cast<diff_t>(get_chunk_size(num_steps, num_threads));
return static_cast<diff_t>(get_chunk_size((std::size_t)std::distance(first, last), num_threads));
}

template<typename Iterator>
constexpr Iterator chunk_advance(const Iterator& iter, const Iterator& last,
Iterator chunk_advance(const Iterator& iter, const Iterator& last,
typename std::iterator_traits<Iterator>::difference_type chunk_size) {
Iterator chunk_end = iter;
std::advance(chunk_end, ::std::min(chunk_size, std::distance(iter, last)));
std::advance(chunk_end, std::min(chunk_size, std::distance(iter, last)));
return chunk_end;
}

/**
* An iterator wrapper that calls std::future<>::get().
* @tparam Iterator
*/
template<typename Iterator>
class getting_iter : public Iterator {
public:
using value_type = decltype((*std::declval<Iterator>()).get());
using difference_type = typename std::iterator_traits<Iterator>::difference_type;
using pointer = value_type*;
using reference = value_type&;
explicit getting_iter(Iterator iter) : iter(iter) {}

getting_iter operator++() { ++iter; return *this; }
getting_iter operator++(int) { getting_iter ret(*this); ++iter; return ret; }

value_type operator*() { return (*iter).get(); }
value_type operator[](difference_type offset) { return iter[offset].get(); }

bool operator==(const getting_iter<Iterator> &other) const { return iter == other.iter; }
bool operator!=(const getting_iter<Iterator> &other) const { return iter != other.iter; }

protected:
Iterator iter;
};

template<typename Iterator>
getting_iter<Iterator> get_wrap(Iterator iter) {
return getting_iter<Iterator>(iter);
}

/*
* Some methods are only available with C++17 and up. Reimplement on older standards.
*/
#if POOLSTL_HAVE_CXX17_LIB
namespace cpp17 = std;
#else
namespace cpp17 {
template <class InputIt, class Tp, class BinOp>
Tp reduce(InputIt first, InputIt last, Tp init, BinOp b) {
for (; first != last; ++first)
init = b(init, *first);
return init;
}
template <class InputIt>
typename std::iterator_traits<InputIt>::value_type reduce(InputIt first, InputIt last) {
return reduce(first, last,
typename std::iterator_traits<InputIt>::value_type{},
std::plus<typename std::iterator_traits<InputIt>::value_type>());
}
}
#endif
}
}

Expand Down
35 changes: 35 additions & 0 deletions include/poolstl/numeric
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,45 @@
#ifndef POOLSTL_NUMERIC_HPP
#define POOLSTL_NUMERIC_HPP

#include <functional>

#include "execution"
#include "internal/ttp_impl.hpp"

namespace std {

/**
* NOTE: Iterators are expected to be random access.
* See std::for_each https://en.cppreference.com/w/cpp/algorithm/reduce
*/
template <class ExecPolicy, class RandIt, class T, class BinaryOp>
poolstl::internal::enable_if_poolstl_execution_policy<ExecPolicy, T>
reduce(ExecPolicy &&policy, RandIt first, RandIt last, T init, BinaryOp binop) {
return poolstl::internal::parallel_reduce(std::forward<ExecPolicy>(policy), first, last, init, binop);
}

/**
* NOTE: Iterators are expected to be random access.
* See std::for_each https://en.cppreference.com/w/cpp/algorithm/reduce
*/
template <class ExecPolicy, class RandIt, class T>
poolstl::internal::enable_if_poolstl_execution_policy<ExecPolicy, T>
reduce(ExecPolicy &&policy, RandIt first, RandIt last, T init) {
return std::reduce(std::forward<ExecPolicy>(policy), first, last, init, std::plus<T>());
}

/**
* NOTE: Iterators are expected to be random access.
* See std::for_each https://en.cppreference.com/w/cpp/algorithm/reduce
*/
template <class ExecPolicy, class RandIt>
poolstl::internal::enable_if_poolstl_execution_policy<
ExecPolicy, typename std::iterator_traits<RandIt>::value_type>
reduce(ExecPolicy &&policy, RandIt first, RandIt last) {
return std::reduce(std::forward<ExecPolicy>(policy), first, last,
typename std::iterator_traits<RandIt>::value_type{});
}

}

#endif
2 changes: 1 addition & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ add_executable(poolstl_test poolstl_test.cpp)
target_link_libraries(poolstl_test PRIVATE Catch2::Catch2WithMain poolSTL::poolSTL)
target_compile_definitions(poolstl_test PRIVATE CATCH_CONFIG_FAST_COMPILE)
# Catch2 requires C++14, some implemented methods were only added in C++17
target_compile_features(poolstl_test PUBLIC cxx_std_17)
target_compile_features(poolstl_test PUBLIC cxx_std_14)

catch_discover_tests(poolstl_test)

Expand Down
4 changes: 2 additions & 2 deletions tests/cpp11_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
#include <poolstl/poolstl.hpp>

int main() {
std::vector<int> v = {0, 1, 2, 3, 4, 5};
std::vector<int> v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
std::for_each(poolstl::par, v.cbegin(), v.cend(), [](int x) {
std::cout << x << " ";
std::cout << x;
});
std::cout << std::endl;
return 0;
Expand Down
24 changes: 18 additions & 6 deletions tests/poolstl_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@

namespace ttp = task_thread_pool;

#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
#define HAVE_CXX17 1
#else
#define HAVE_CXX17 0
#endif

TEST_CASE("for_each", "[alg]") {
std::atomic<int> sum{0};
for (auto num_threads : test_thread_counts) {
Expand Down Expand Up @@ -59,6 +53,24 @@ TEST_CASE("for_each_n", "[alg]") {
}
}

TEST_CASE("reduce", "[alg][numeric]") {
for (auto num_threads : test_thread_counts) {
ttp::task_thread_pool pool(num_threads);

for (auto num_iters : test_arr_sizes) {
auto v = iota_vector(num_iters);

#if POOLSTL_HAVE_CXX17_LIB
auto seq = std::reduce(v.cbegin(), v.cend());
#else
auto seq = poolstl::internal::cpp17::reduce(v.cbegin(), v.cend());
#endif
auto par = std::reduce(poolstl::par_pool(pool), v.cbegin(), v.cend());
REQUIRE(seq == par);
}
}
}

TEST_CASE("default_pool", "[execution]") {
std::atomic<int> sum{0};
for (auto num_iters : test_arr_sizes) {
Expand Down

0 comments on commit 7250c8b

Please sign in to comment.