From 964d0acc0675eb2fc656b5a541f6ea796761256f Mon Sep 17 00:00:00 2001 From: Adam Lugowski Date: Sun, 12 Nov 2023 17:55:11 -0800 Subject: [PATCH] Setup workflows and tests Fix any bugs that fail initial cross-platform tests --- .github/dependabot.yml | 8 +++ .github/workflows/tests.yml | 93 ++++++++++++++++++++++++++++++ README.md | 5 +- benchmark/algorithm_bench.cpp | 8 +-- benchmark/main.cpp | 10 ++++ benchmark/utils.hpp | 6 ++ include/poolstl/algorithm | 8 +-- include/poolstl/execution | 20 +++---- include/poolstl/internal/utils.hpp | 22 ++++--- tests/CMakeLists.txt | 11 +++- tests/cpp11_test.cpp | 17 ++++++ tests/poolstl_test.cpp | 4 +- tests/utils.hpp | 20 ------- 13 files changed, 182 insertions(+), 50 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/tests.yml create mode 100644 tests/cpp11_test.cpp diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..aa3d8f3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + # See https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot + interval: "weekly" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..587b18c --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,93 @@ +name: tests + +on: + push: + pull_request: + +jobs: + build: + name: ${{matrix.os}} ${{ matrix.description }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-20.04 + # oldest LLVM that the install-llvm-action action is able to install + description: "LLVM 5" + 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" + + - os: ubuntu-latest + description: "GCC 13" + gcc-version: "13" + + - os: ubuntu-latest + # default GCC, which has gcov + + - os: macos-latest + # uses Apple Clang + + - os: windows-latest + # uses MSVC + + steps: + - uses: actions/checkout@v4 + + - name: Install TBB (Ubuntu) + if: contains(matrix.os, 'ubuntu') + run: sudo apt-get install -y libtbb-dev + + - name: Install TBB (macOS) + if: contains(matrix.os, 'macos') + run: brew install tbb + + - name: Setup GCC + uses: egor-tensin/setup-gcc@v1 + if: ${{ matrix.gcc-version != '' }} + with: + version: ${{ matrix.gcc-version }} + + - name: Setup LLVM and Clang + uses: KyleMayes/install-llvm-action@v1 + if: ${{ matrix.llvm-version != '' }} + with: + version: ${{ matrix.llvm-version }} + env: true + + - name: Setup cmake + uses: jwlawson/actions-setup-cmake@v1 + if: ${{ matrix.cmake-version != '' }} + with: + cmake-version: ${{ matrix.cmake-version }} + + - name: Build and Test + run: | + cmake -S . -B build/ -DCMAKE_BUILD_TYPE=Debug -DPOOLSTL_TEST=ON -DPOOLSTL_TEST_COVERAGE=ON + cmake --build build/ --config Debug + cd build/tests + ctest -C Debug --output-on-failure --verbose + cd ../.. + find . + shell: bash + + - name: Benchmark + if: ${{ matrix.gcc-version != '7' }} + run: | + cmake -S . -B bench_build/ -DCMAKE_BUILD_TYPE=Release -DPOOLSTL_BENCH=ON + cmake --build bench_build/ --config Release + cd bench_build/benchmark/ + ./poolstl_bench || ./Release/poolstl_bench.exe + shell: bash + + - name: Upload Coverage to Codecov + if: contains(matrix.os, 'ubuntu') + uses: codecov/codecov-action@v3 + with: + gcov: true + gcov_include: include/* diff --git a/README.md b/README.md index ef5525c..9cc932e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ +[![tests](https://github.com/alugowski/poolSTL/actions/workflows/tests.yml/badge.svg)](https://github.com/alugowski/poolSTL/actions/workflows/tests.yml) +[![codecov](https://codecov.io/gh/alugowski/poolSTL/branch/main/graph/badge.svg?token=)](https://codecov.io/gh/alugowski/poolSTL) + # poolSTL -Thread pool-based implementation of [parallel standard library algorithms](https://en.cppreference.com/w/cpp/algorithm#Execution_policies). +Thread pool-based implementation of [parallel standard library algorithms](https://en.cppreference.com/w/cpp/algorithm). Those algorithms are great, but compiler support is inconsistent. PoolSTL is a *supplement* to fill in the support gaps so we can use parallel algorithms now. diff --git a/benchmark/algorithm_bench.cpp b/benchmark/algorithm_bench.cpp index f2bcd7e..7fea150 100644 --- a/benchmark/algorithm_bench.cpp +++ b/benchmark/algorithm_bench.cpp @@ -45,9 +45,9 @@ void for_each(benchmark::State& state) { for ([[maybe_unused]] auto _ : state) { if constexpr (is_policy::value) { - std::for_each(policy::get(), values.begin(), values.end(), [&](auto v) {dest[v] = v;}); + std::for_each(policy::get(), values.begin(), values.end(), [&](auto v) { slow(); dest[v] = v; }); } else { - std::for_each(values.begin(), values.end(), [&](auto v) {dest[v] = v;}); + std::for_each(values.begin(), values.end(), [&](auto v) {slow(); dest[v] = v;}); } benchmark::DoNotOptimize(dest); benchmark::ClobberMemory(); @@ -69,9 +69,9 @@ void transform(benchmark::State& state) { for ([[maybe_unused]] auto _ : state) { if constexpr (is_policy::value) { - std::transform(policy::get(), values.begin(), values.end(), dest.begin(), [&](auto v) { return v; }); + std::transform(policy::get(), values.begin(), values.end(), dest.begin(), [&](auto v) { slow(); return v; }); } else { - std::transform(values.begin(), values.end(), dest.begin(), [&](auto v) { return v; }); + std::transform(values.begin(), values.end(), dest.begin(), [&](auto v) { slow(); return v; }); } benchmark::DoNotOptimize(dest); benchmark::ClobberMemory(); diff --git a/benchmark/main.cpp b/benchmark/main.cpp index d74f4e0..551c5a4 100644 --- a/benchmark/main.cpp +++ b/benchmark/main.cpp @@ -14,4 +14,14 @@ bool static_init() { } static bool initialized = static_init(); +void slow() { + int sum = 0; + + for (int i = 0; i < 10; ++i) { + sum += i*31 + sum; + } + + benchmark::DoNotOptimize(sum); +} + BENCHMARK_MAIN(); diff --git a/benchmark/utils.hpp b/benchmark/utils.hpp index db4ce81..f5980f4 100644 --- a/benchmark/utils.hpp +++ b/benchmark/utils.hpp @@ -8,7 +8,9 @@ #define POOLSTL_BENCH_UTILS_HPP #include +#ifdef POOLSTL_BENCH_STD_PAR #include +#endif #include #include #include @@ -40,11 +42,13 @@ template <> struct policy { return poolstl::execution::par_pool(pool); }; }; +#ifdef POOLSTL_BENCH_STD_PAR template <> struct policy { constexpr static const std::execution::parallel_policy& get() { return std::execution::par; }; }; +#endif template std::vector iota_vector(size_t size, T init=0) { @@ -53,4 +57,6 @@ std::vector iota_vector(size_t size, T init=0) { return ret; } +void slow(); + #endif diff --git a/include/poolstl/algorithm b/include/poolstl/algorithm index 76dc87c..88a1383 100644 --- a/include/poolstl/algorithm +++ b/include/poolstl/algorithm @@ -24,11 +24,11 @@ namespace std { auto chunk_size = get_chunk_size(first, last, pool(policy).get_num_threads()); while (first < last) { - InputIt loop_end = ::std::min(first + chunk_size, last); + InputIt loop_end = first + ::std::min(chunk_size, std::distance(first, last)); - futures.emplace_back(pool(policy).submit([&](InputIt chunk_start, InputIt chunk_stop) { - for (; chunk_start != chunk_stop; ++chunk_start) { - f(*chunk_start); + futures.emplace_back(pool(policy).submit([&f](InputIt chunk_first, InputIt chunk_last) { + for (; chunk_first != chunk_last; ++chunk_first) { + f(*chunk_first); } }, first, loop_end)); diff --git a/include/poolstl/execution b/include/poolstl/execution index dd18158..81777e5 100644 --- a/include/poolstl/execution +++ b/include/poolstl/execution @@ -41,26 +41,24 @@ namespace poolstl { } template - ttp::task_thread_pool& pool(ExecutionPolicy& policy) { - if constexpr (ExecutionPolicy::use_default_pool()) { - return *get_default_pool(); - } else { - return policy.pool; - } + ttp::task_thread_pool& pool(ExecutionPolicy&) { + return *get_default_pool(); + } + + template <> + inline ttp::task_thread_pool& pool(par_pool& policy) { + return policy.pool; } template struct is_poolstl_execution_policy : std::false_type {}; template <> struct is_poolstl_execution_policy<::poolstl::execution::parallel_policy> : std::true_type {}; template <> struct is_poolstl_execution_policy<::poolstl::execution::par_pool> : std::true_type {}; - template - inline constexpr bool is_poolstl_execution_policy_v = is_poolstl_execution_policy::value; - template using enable_if_poolstl_execution_policy = typename std::enable_if< - is_poolstl_execution_policy_v< - typename std::remove_cv::type>::type>, + is_poolstl_execution_policy< + typename std::remove_cv::type>::type>::value, Tp>::type; } } diff --git a/include/poolstl/internal/utils.hpp b/include/poolstl/internal/utils.hpp index 296dbd2..6059b98 100644 --- a/include/poolstl/internal/utils.hpp +++ b/include/poolstl/internal/utils.hpp @@ -12,16 +12,24 @@ #define POOLSTL_VERSION_PATCH 0 #include +#include -namespace poolstl::internal { +namespace poolstl { + namespace internal { - template - std::size_t get_chunk_size(Iterator first, Iterator last, unsigned int num_threads) { - std::size_t num_steps = (last - first); - auto remainder = num_steps % num_threads; - return (num_steps / num_threads) + (remainder > 0 ? 1 : 0); - } + inline 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); + } + template + typename std::iterator_traits::difference_type + get_chunk_size(Iterator first, Iterator last, unsigned int num_threads) { + using diff_t = typename std::iterator_traits::difference_type; + std::size_t num_steps = std::distance(first, last); + return static_cast(get_chunk_size(num_steps, num_threads)); + } + } } #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1462f1b..2d6c1d6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -22,7 +22,7 @@ Include(FetchContent) FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v3.3.2 + GIT_TAG v3.4.0 GIT_SHALLOW TRUE EXCLUDE_FROM_ALL ) @@ -35,4 +35,13 @@ include(Catch) 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 +target_compile_features(poolstl_test PUBLIC cxx_std_14) + catch_discover_tests(poolstl_test) + +# Dedicated target to ensure C++11 builds work +add_executable(cpp11_test cpp11_test.cpp) +target_link_libraries(cpp11_test PUBLIC poolSTL::poolSTL) +target_compile_features(cpp11_test PUBLIC cxx_std_11) diff --git a/tests/cpp11_test.cpp b/tests/cpp11_test.cpp new file mode 100644 index 0000000..e266ed6 --- /dev/null +++ b/tests/cpp11_test.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2023 Adam Lugowski. All rights reserved. +// Use of this source code is governed by: +// the BSD 2-clause license, the MIT license, or at your choosing the BSL-1.0 license found in the LICENSE.*.txt files. +// SPDX-License-Identifier: BSD-2-Clause OR MIT OR BSL-1.0 + +#include + +#include + +int main() { + std::vector v = {0, 1, 2, 3, 4, 5}; + std::for_each(poolstl::par, v.cbegin(), v.cend(), [](int x) { + std::cout << x << " "; + }); + std::cout << std::endl; + return 0; +} diff --git a/tests/poolstl_test.cpp b/tests/poolstl_test.cpp index b6bfcef..bce95f1 100644 --- a/tests/poolstl_test.cpp +++ b/tests/poolstl_test.cpp @@ -13,7 +13,6 @@ #include "utils.hpp" namespace ttp = task_thread_pool; -using it = i_iter; TEST_CASE("for_each", "[alg]") { std::atomic sum{0}; @@ -40,8 +39,9 @@ TEST_CASE("for_each", "[alg]") { TEST_CASE("default_pool", "[execution]") { std::atomic sum{0}; for (auto num_iters : test_arr_sizes) { + auto v = iota_vector(num_iters); sum = 0; - std::for_each(poolstl::par, it(0), it(num_iters), [&](auto) { ++sum; }); + std::for_each(poolstl::par, v.cbegin(), v.cend(), [&](auto) { ++sum; }); REQUIRE(sum == num_iters); } } diff --git a/tests/utils.hpp b/tests/utils.hpp index 41b1ec8..8b3fa31 100644 --- a/tests/utils.hpp +++ b/tests/utils.hpp @@ -10,26 +10,6 @@ constexpr std::array test_thread_counts = {0, 1, 2, 3, 5, 10}; constexpr std::array test_arr_sizes = {0, 1, 2, 3, 4, 8, 9, 10, 11, 20, 23, 77, 101}; -template -struct i_iter { - T value; - - explicit i_iter(T v) : value(v) {} - - T operator*() const { return value; } - - T operator++() { return ++value; } - - template - i_iter operator+(const U &other) { return i_iter(value + other); } - - T operator-(const i_iter &other) const { return value - other.value; } - - bool operator<(const i_iter &other) const { return value < other.value; } - bool operator==(const i_iter &other) const { return value == other.value; } - bool operator!=(const i_iter &other) const { return value != other.value; } -}; - template std::vector iota_vector(size_t size, T init=0) { std::vector ret(size);