From 15aaf1e8230fe8b167843c19e11ac718884c89a3 Mon Sep 17 00:00:00 2001 From: Jared Hoberock Date: Mon, 28 Jan 2019 16:28:19 -0600 Subject: [PATCH 01/14] Implement for_each_n in terms of P0443R10 & P1019R2 --- examples/for_each/for_each_1.cpp | 144 -------------------- examples/for_each/for_each_2.cpp | 146 --------------------- examples/for_each/for_each_3.cpp | 175 ------------------------- examples/for_each/for_each_n_1.cpp | 88 +++++++++++++ examples/for_each/for_each_n_2.cpp | 139 ++++++++++++++++++++ examples/for_each/for_each_n_3.cpp | 193 +++++++++++++++++++++++++++ examples/for_each/for_each_n_4.cpp | 203 +++++++++++++++++++++++++++++ 7 files changed, 623 insertions(+), 465 deletions(-) delete mode 100644 examples/for_each/for_each_1.cpp delete mode 100644 examples/for_each/for_each_2.cpp delete mode 100644 examples/for_each/for_each_3.cpp create mode 100644 examples/for_each/for_each_n_1.cpp create mode 100644 examples/for_each/for_each_n_2.cpp create mode 100644 examples/for_each/for_each_n_3.cpp create mode 100644 examples/for_each/for_each_n_4.cpp diff --git a/examples/for_each/for_each_1.cpp b/examples/for_each/for_each_1.cpp deleted file mode 100644 index 76815f9..0000000 --- a/examples/for_each/for_each_1.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include -#include -#include -#include -#include -#include - -namespace execution = std::experimental::execution; -using std::experimental::static_thread_pool; - -namespace impl -{ - -static_thread_pool system_thread_pool{std::max(1u,std::thread::hardware_concurrency())}; - -class system_thread_pool_bulk_executor -{ - public: - using shape_type = execution::executor_shape_t; - using index_type = execution::executor_index_t; - - template - using future = execution::executor_future_t; - - auto& query(execution::context_t) const noexcept { return system_thread_pool; } - - friend bool operator==(const system_thread_pool_bulk_executor&, const system_thread_pool_bulk_executor&) noexcept - { - return true; - } - - friend bool operator!=(const system_thread_pool_bulk_executor&, const system_thread_pool_bulk_executor&) noexcept - { - return false; - } - - system_thread_pool_bulk_executor require(execution::bulk_guarantee_t::parallel_t) const { return *this; } - - template - auto bulk_twoway_execute(Function f, size_t n, ResultFactory rf, SharedFactory sf) const - { - return system_thread_pool.executor().bulk_twoway_execute(std::move(f), n, std::move(rf), std::move(sf)); - } - - template - auto bulk_execute(Function f, size_t n, SharedFactory sf) const - { - return system_thread_pool.executor().bulk_execute(std::move(f), n, std::move(sf)); - } -}; - -template -class basic_execution_policy -{ - public: - //static_assert(is_weaker_than< - // BulkForwardProgressRequirement, - // executor_bulk_forward_progress_guarantee_t - // >::value, - // "basic_execution_policy: BulkForwardProgressRequirement cannot be satisfied by Executor's guarantee." - //); - - using executor_type = Executor; - using bulk_forward_progress_requirement = BulkForwardProgressRequirement; - - basic_execution_policy() = default; - - basic_execution_policy(const basic_execution_policy&) = default; - - basic_execution_policy(executor_type&& exec) - : executor_(std::move(exec)) - {} - - basic_execution_policy(const executor_type& exec) - : executor_(exec) - {} - - template::type> - // >::value - //>::type - > - basic_execution_policy on(OtherExecutor&& exec) const - { - return basic_execution_policy( - execution::require(std::forward(exec), BulkForwardProgressRequirement{})); - } - - executor_type executor() const - { - return executor_; - } - - private: - executor_type executor_; -}; - -constexpr struct ignored {} ignore; - -} // end impl - -class parallel_policy : public impl::basic_execution_policy -{ - using super_t = impl::basic_execution_policy; - - public: - using super_t::super_t; -}; - -constexpr parallel_policy par{}; - -template -void for_each(ExecutionPolicy&& policy, RandomAccessIterator first, RandomAccessIterator last, Function f) -{ - auto n = last - first; - - auto twoway_bulk_exec = execution::require(policy.executor(), execution::bulk, execution::twoway); - - twoway_bulk_exec.bulk_twoway_execute([=](size_t idx, impl::ignored&) - { - f(first[idx]); - }, - n, - []{}, - []{ return impl::ignore; } - ).get(); -} - -int main() -{ - std::vector vec(10); - - for_each(par, vec.begin(), vec.end(), [](int& x) - { - x = 42; - }); - - assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); - - std::cout << "OK" << std::endl; -} diff --git a/examples/for_each/for_each_2.cpp b/examples/for_each/for_each_2.cpp deleted file mode 100644 index 0f1d7ce..0000000 --- a/examples/for_each/for_each_2.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include -#include -#include -#include -#include -#include - -namespace execution = std::experimental::execution; -using std::experimental::static_thread_pool; - -namespace impl -{ - -static_thread_pool system_thread_pool{std::max(1u,std::thread::hardware_concurrency())}; - -class system_thread_pool_bulk_executor -{ - public: - using shape_type = execution::executor_shape_t; - using index_type = execution::executor_index_t; - - template - using future = execution::executor_future_t; - - auto& query(execution::context_t) const noexcept { return system_thread_pool; } - - friend bool operator==(const system_thread_pool_bulk_executor&, const system_thread_pool_bulk_executor&) noexcept - { - return true; - } - - friend bool operator!=(const system_thread_pool_bulk_executor&, const system_thread_pool_bulk_executor&) noexcept - { - return false; - } - - system_thread_pool_bulk_executor require(execution::bulk_guarantee_t::parallel_t) const { return *this; } - - template - auto bulk_twoway_execute(Function f, size_t n, ResultFactory rf, SharedFactory sf) const - { - return system_thread_pool.executor().bulk_twoway_execute(std::move(f), n, std::move(rf), std::move(sf)); - } - - template - auto bulk_execute(Function f, size_t n, SharedFactory sf) const - { - return system_thread_pool.executor().bulk_execute(std::move(f), n, std::move(sf)); - } -}; - -template -class basic_execution_policy -{ - public: - //static_assert(is_weaker_than< - // BulkForwardProgressRequirement, - // executor_bulk_forward_progress_guarantee_t - // >::value, - // "basic_execution_policy: BulkForwardProgressRequirement cannot be satisfied by Executor's guarantee." - //); - - using executor_type = Executor; - using bulk_forward_progress_requirement = BulkForwardProgressRequirement; - - basic_execution_policy() = default; - - basic_execution_policy(const basic_execution_policy&) = default; - - basic_execution_policy(executor_type&& exec) - : executor_(std::move(exec)) - {} - - basic_execution_policy(const executor_type& exec) - : executor_(exec) - {} - - template::type> - // >::value - //>::type - > - basic_execution_policy on(OtherExecutor&& exec) const - { - return basic_execution_policy( - execution::require(std::forward(exec), BulkForwardProgressRequirement{})); - } - - executor_type executor() const - { - return executor_; - } - - private: - executor_type executor_; -}; - -constexpr struct ignored {} ignore; - -} // end impl - -class parallel_policy : public impl::basic_execution_policy -{ - using super_t = impl::basic_execution_policy; - - public: - using super_t::super_t; -}; - -constexpr parallel_policy par{}; - -template -void for_each(ExecutionPolicy&& policy, RandomAccessIterator first, RandomAccessIterator last, Function f) -{ - auto n = last - first; - - auto twoway_bulk_exec = execution::require(policy.executor(), execution::bulk, execution::twoway); - - twoway_bulk_exec.bulk_twoway_execute([=](size_t idx, impl::ignored&) - { - f(first[idx]); - }, - n, - []{}, - []{ return impl::ignore; } - ).get(); -} - -int main() -{ - static_thread_pool pool{1}; - - std::vector vec(10); - - for_each(par.on(pool.executor()), vec.begin(), vec.end(), [](int& x) - { - x = 42; - }); - - assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); - - std::cout << "OK" << std::endl; -} diff --git a/examples/for_each/for_each_3.cpp b/examples/for_each/for_each_3.cpp deleted file mode 100644 index 627bc91..0000000 --- a/examples/for_each/for_each_3.cpp +++ /dev/null @@ -1,175 +0,0 @@ -#include -#include -#include -#include -#include -#include - -namespace execution = std::experimental::execution; -using std::experimental::static_thread_pool; - -namespace impl -{ - -static_thread_pool system_thread_pool{std::max(1u,std::thread::hardware_concurrency())}; - -class system_thread_pool_bulk_executor -{ - public: - using shape_type = execution::executor_shape_t; - using index_type = execution::executor_index_t; - - template - using future = execution::executor_future_t; - - auto& query(execution::context_t) const noexcept { return system_thread_pool; } - - friend bool operator==(const system_thread_pool_bulk_executor&, const system_thread_pool_bulk_executor&) noexcept - { - return true; - } - - friend bool operator!=(const system_thread_pool_bulk_executor&, const system_thread_pool_bulk_executor&) noexcept - { - return false; - } - - system_thread_pool_bulk_executor require(execution::bulk_guarantee_t::parallel_t) const { return *this; } - bool query(execution::bulk_guarantee_t::parallel_t) const { return true; } - - template - auto bulk_twoway_execute(Function f, size_t n, ResultFactory rf, SharedFactory sf) const - { - return system_thread_pool.executor().bulk_twoway_execute(std::move(f), n, std::move(rf), std::move(sf)); - } - - template - auto bulk_execute(Function f, size_t n, SharedFactory sf) const - { - return system_thread_pool.executor().bulk_execute(std::move(f), n, std::move(sf)); - } -}; - -template -class basic_execution_policy -{ - public: - //static_assert(is_weaker_than< - // BulkForwardProgressRequirement, - // executor_bulk_forward_progress_guarantee_t - // >::value, - // "basic_execution_policy: BulkForwardProgressRequirement cannot be satisfied by Executor's guarantee." - //); - - using executor_type = Executor; - using bulk_forward_progress_requirement = BulkForwardProgressRequirement; - - basic_execution_policy() = default; - - basic_execution_policy(const basic_execution_policy&) = default; - - basic_execution_policy(executor_type&& exec) - : executor_(std::move(exec)) - {} - - basic_execution_policy(const executor_type& exec) - : executor_(exec) - {} - - template::type> - // >::value - //>::type - > - basic_execution_policy on(OtherExecutor&& exec) const - { - return basic_execution_policy( - execution::require(std::forward(exec), BulkForwardProgressRequirement{})); - } - - executor_type executor() const - { - return executor_; - } - - private: - executor_type executor_; -}; - -constexpr struct ignored {} ignore; - -} // end impl - -class parallel_policy : public impl::basic_execution_policy -{ - using super_t = impl::basic_execution_policy; - - public: - using super_t::super_t; -}; - -constexpr parallel_policy par{}; - -template -void for_each(ExecutionPolicy&& policy, RandomAccessIterator first, RandomAccessIterator last, Function f) -{ - auto n = last - first; - - auto twoway_bulk_exec = execution::require(policy.executor(), execution::blocking_adaptation.allowed, execution::bulk, execution::twoway); - - twoway_bulk_exec.bulk_twoway_execute([=](size_t idx, impl::ignored&) - { - f(first[idx]); - }, - n, - []{}, - []{ return impl::ignore; } - ).get(); -} - -class inline_executor -{ -public: - friend bool operator==(const inline_executor&, const inline_executor&) noexcept - { - return true; - } - - friend bool operator!=(const inline_executor&, const inline_executor&) noexcept - { - return false; - } - - inline_executor require(execution::bulk_guarantee_t::parallel_t) const - { - return *this; - } - - bool query(execution::bulk_guarantee_t::parallel_t) const - { - return true; - } - - template - void execute(Function f) const noexcept - { - f(); - } -}; - -int main() -{ - std::vector vec(10); - - for_each(par.on(inline_executor()), vec.begin(), vec.end(), [](int& x) - { - x = 42; - }); - - assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); - - std::cout << "OK" << std::endl; -} diff --git a/examples/for_each/for_each_n_1.cpp b/examples/for_each/for_each_n_1.cpp new file mode 100644 index 0000000..58de48c --- /dev/null +++ b/examples/for_each/for_each_n_1.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include +#include + + +namespace execution = std::experimental::execution; +using std::experimental::static_thread_pool; + + +namespace impl +{ + + +static_thread_pool system_thread_pool{std::max(1u,std::thread::hardware_concurrency())}; + + +constexpr struct ignored {} ignore; + + +} // end impl + + +class parallel_policy +{ + public: + static constexpr execution::bulk_guarantee_t::parallel_t execution_requirement{}; +}; + + +constexpr parallel_policy par{}; + + +template +void for_each_n(const parallel_policy&, RandomAccessIterator first, Size n, Function f) +{ + auto ex = execution::require(impl::system_thread_pool.executor(), execution::bulk, execution::oneway, execution::blocking.always); + + try + { + // this only throws upon failure to create EAs + ex.bulk_execute( + [=](size_t idx, impl::ignored&) + { + try + { + f(first[idx]); + } + catch(...) + { + std::terminate(); + } + }, + n, + []{ return impl::ignore; } + ); + } + catch(...) + { + // sequential fallback + for(Size i = 0; i < n; ++i, ++first) + { + f(*first); + } + } +} + + +int main() +{ + std::vector vec(10); + + // test with par + + for_each_n(par, vec.begin(), vec.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + + std::cout << "OK" << std::endl; + + return 0; +} + diff --git a/examples/for_each/for_each_n_2.cpp b/examples/for_each/for_each_n_2.cpp new file mode 100644 index 0000000..644b013 --- /dev/null +++ b/examples/for_each/for_each_n_2.cpp @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include +#include + + +namespace execution = std::experimental::execution; +using std::experimental::static_thread_pool; + + +namespace impl +{ + + +template class basic_execution_policy; + + +template +basic_execution_policy make_basic_execution_policy(const E& ex); + + +template +class basic_execution_policy +{ + public: + static_assert(execution::can_require_v); + + static constexpr ExecutionRequirement execution_requirement{}; + + Executor executor() const + { + return executor_; + } + + private: + template + friend basic_execution_policy make_basic_execution_policy(const E& ex); + + basic_execution_policy(const Executor& executor) + : executor_(executor) + {} + + Executor executor_; +}; + + +template +basic_execution_policy make_basic_execution_policy(const E& ex) +{ + return basic_execution_policy{ex}; +} + + +constexpr struct ignored {} ignore; + + +template +constexpr bool satisfies_cpp20_on_requirements_v = + execution::can_require_v< + Executor + , ExecutionRequirement + , execution::bulk_t + , execution::oneway_t + , execution::blocking_t::always_t + , execution::mapping_t::thread_t +>; + + +} // end impl + + +class parallel_policy +{ + public: + static constexpr execution::bulk_guarantee_t::parallel_t execution_requirement{}; + + template + >> + impl::basic_execution_policy on(const Executor& ex) const + { + return impl::make_basic_execution_policy(ex); + } +}; + + +constexpr parallel_policy par{}; + + +template +void for_each_n(ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Function f) +{ + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); + + try + { + // this only throws upon failure to create EAs + ex.bulk_execute( + [=](size_t idx, impl::ignored&) + { + f(first[idx]); + }, + n, + []{ return impl::ignore; } + ); + } + catch(...) + { + // sequential fallback + for(Size i = 0; i < n; ++i, ++first) + { + f(*first); + } + } +} + + +int main() +{ + std::vector vec(10); + + // test with static_thread_pool::executor_type + std::experimental::static_thread_pool pool(4); + + for_each_n(par.on(pool.executor()), vec.begin(), vec.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + + std::cout << "OK" << std::endl; + + return 0; +} + diff --git a/examples/for_each/for_each_n_3.cpp b/examples/for_each/for_each_n_3.cpp new file mode 100644 index 0000000..88d08d0 --- /dev/null +++ b/examples/for_each/for_each_n_3.cpp @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include +#include + + +namespace execution = std::experimental::execution; +using std::experimental::static_thread_pool; + + +namespace impl +{ + + +template class basic_execution_policy; + + +template +basic_execution_policy make_basic_execution_policy(const E& ex); + + +template +class basic_execution_policy +{ + public: + static_assert(execution::can_require_v); + + static constexpr ExecutionRequirement execution_requirement{}; + + Executor executor() const + { + return executor_; + } + + private: + template + friend basic_execution_policy make_basic_execution_policy(const E& ex); + + basic_execution_policy(const Executor& executor) + : executor_(executor) + {} + + Executor executor_; +}; + + +template +basic_execution_policy make_basic_execution_policy(const E& ex) +{ + return basic_execution_policy{ex}; +} + + +constexpr struct ignored {} ignore; + + +template +constexpr bool satisfies_cpp20_on_requirements_v = + execution::can_require_v< + Executor + , ExecutionRequirement + , execution::bulk_t + , execution::oneway_t + , execution::blocking_t::always_t + , execution::mapping_t::thread_t +>; + + +} // end impl + + +class parallel_policy +{ + public: + static constexpr execution::bulk_guarantee_t::parallel_t execution_requirement{}; + + template + >> + impl::basic_execution_policy on(const Executor& ex) const + { + return impl::make_basic_execution_policy(ex); + } +}; + + +constexpr parallel_policy par{}; + + +template +void for_each_n(const parallel_policy&, RandomAccessIterator first, Size n, Function f) +{ + throw std::runtime_error("for_each_n(parallel_policy): Unimplemented."); +} + + +template +void for_each_n(ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Function f) +{ + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); + + try + { + // this only throws upon failure to create EAs + ex.bulk_execute( + [=](size_t idx, impl::ignored&) + { + // XXX terminate if this throws? + f(first[idx]); + }, + n, + []{ return impl::ignore; } + ); + } + catch(...) + { + // sequential fallback + for(Size i = 0; i < n; ++i, ++first) + { + f(*first); + } + } +} + + +class inline_executor +{ + public: + friend bool operator==(const inline_executor&, const inline_executor&) noexcept + { + return true; + } + + friend bool operator!=(const inline_executor&, const inline_executor&) noexcept + { + return false; + } + + inline_executor require(execution::oneway_t) const + { + return *this; + } + + constexpr static execution::bulk_guarantee_t::parallel_t query(execution::bulk_guarantee_t) + { + return execution::bulk_guarantee.parallel; + } + + constexpr static execution::blocking_t::always_t query(execution::blocking_t) + { + return execution::blocking.always; + } + + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) + { + return execution::mapping.thread; + } + + template + void execute(Function f) const noexcept + { + try + { + f(); + } + catch(...) + { + } + } +}; + + +int main() +{ + std::vector vec(10); + + // test with inline_executor + + for_each_n(par.on(inline_executor()), vec.begin(), vec.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + + std::cout << "OK" << std::endl; + + return 0; +} + diff --git a/examples/for_each/for_each_n_4.cpp b/examples/for_each/for_each_n_4.cpp new file mode 100644 index 0000000..03a8933 --- /dev/null +++ b/examples/for_each/for_each_n_4.cpp @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include + + +namespace execution = std::experimental::execution; +using std::experimental::static_thread_pool; + + +namespace impl +{ + + +template class basic_execution_policy; + + +template +basic_execution_policy make_basic_execution_policy(const E& ex); + + +template +class basic_execution_policy +{ + public: + static_assert(execution::can_require_v); + + static constexpr ExecutionRequirement execution_requirement{}; + + Executor executor() const + { + return executor_; + } + + private: + template + friend basic_execution_policy make_basic_execution_policy(const E& ex); + + basic_execution_policy(const Executor& executor) + : executor_(executor) + {} + + Executor executor_; +}; + + +template +basic_execution_policy make_basic_execution_policy(const E& ex) +{ + return basic_execution_policy{ex}; +} + + +constexpr struct ignored {} ignore; + + +template +constexpr bool satisfies_cpp20_on_requirements_v = + execution::can_require_v< + Executor + , ExecutionRequirement + , execution::bulk_t + , execution::oneway_t + , execution::blocking_t::always_t + , execution::mapping_t::thread_t +>; + + +} // end impl + + +class parallel_policy +{ + public: + static constexpr execution::bulk_guarantee_t::parallel_t execution_requirement{}; + + template + >> + impl::basic_execution_policy on(const Executor& ex) const + { + return impl::make_basic_execution_policy(ex); + } +}; + + +constexpr parallel_policy par{}; + + +template +void for_each_n(const parallel_policy&, RandomAccessIterator first, Size n, Function f) +{ + throw std::runtime_error("for_each_n(parallel_policy): Unimplemented."); +} + + +template +void for_each_n(ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Function f) +{ + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); + + try + { + // this only throws upon failure to create EAs + ex.bulk_execute( + [=](size_t idx, impl::ignored&) + { + // XXX terminate if this throws? + f(first[idx]); + }, + n, + []{ return impl::ignore; } + ); + } + catch(...) + { + // sequential fallback + for(Size i = 0; i < n; ++i, ++first) + { + f(*first); + } + } +} + + +class bulk_inline_executor +{ + public: + friend bool operator==(const bulk_inline_executor&, const bulk_inline_executor&) noexcept + { + return true; + } + + friend bool operator!=(const bulk_inline_executor&, const bulk_inline_executor&) noexcept + { + return false; + } + + bulk_inline_executor require(execution::oneway_t) const + { + return *this; + } + + bulk_inline_executor require(execution::bulk_t) const + { + return *this; + } + + constexpr static execution::bulk_guarantee_t::parallel_t query(execution::bulk_guarantee_t) + { + return execution::bulk_guarantee.parallel; + } + + constexpr static execution::blocking_t::always_t query(execution::blocking_t) + { + return execution::blocking.always; + } + + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) + { + return execution::mapping.thread; + } + + template + void bulk_execute(Function f, size_t n, Factory shared_factory) const noexcept + { + try + { + auto shared = shared_factory(); + + for(size_t i = 0; i < n; ++i) + { + f(i, shared); + } + } + catch(...) + { + } + } +}; + + +int main() +{ + std::vector vec(10); + + // test with bulk_inline_executor + + for_each_n(par.on(bulk_inline_executor()), vec.begin(), vec.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + + std::cout << "OK" << std::endl; + + return 0; +} + From cb0f063acd5e5e2401c52223d5173e7943544ad5 Mon Sep 17 00:00:00 2001 From: Jared Hoberock Date: Wed, 30 Jan 2019 15:04:25 -0600 Subject: [PATCH 02/14] Introduce support for execution::occupancy_t --- include/experimental/bits/occupancy.h | 35 +++++++++++++++++++ .../experimental/bits/static_thread_pool.h | 3 ++ include/experimental/execution | 4 +++ 3 files changed, 42 insertions(+) create mode 100644 include/experimental/bits/occupancy.h diff --git a/include/experimental/bits/occupancy.h b/include/experimental/bits/occupancy.h new file mode 100644 index 0000000..e5e58e0 --- /dev/null +++ b/include/experimental/bits/occupancy.h @@ -0,0 +1,35 @@ +#ifndef STD_EXPERIMENTAL_BITS_OCCUPANCY_H +#define STD_EXPERIMENTAL_BITS_OCCUPANCY_H + +#include + +namespace std { +namespace experimental { +inline namespace executors_v1 { +namespace execution { +namespace occupancy_impl { + +template +struct property_base +{ + static constexpr bool is_requirable = false; + static constexpr bool is_preferable = false; + + using polymorphic_query_result_type = std::size_t; + + template(0)))> + static constexpr Type static_query_v = Executor::query(Derived()); +}; + +} // end namespace occupancy_impl + +struct occupancy_t : occupancy_impl::property_base {}; + +constexpr occupancy_t occupancy; + +} // namespace execution +} // inline namespace executors_v1 +} // namespace experimental +} // namespace std + +#endif // STD_EXPERIMENTAL_BITS_OCCUPANCY_H diff --git a/include/experimental/bits/static_thread_pool.h b/include/experimental/bits/static_thread_pool.h index 1ddfcea..120ad84 100644 --- a/include/experimental/bits/static_thread_pool.h +++ b/include/experimental/bits/static_thread_pool.h @@ -77,6 +77,9 @@ class static_thread_pool ProtoAllocator query(const execution::allocator_t&) const noexcept { return allocator_; } ProtoAllocator query(const execution::allocator_t&) const noexcept { return allocator_; } + // Occupancy. + std::size_t query(const execution::occupancy_t&) const noexcept { return pool_->threads_.size(); } + bool running_in_this_thread() const noexcept { return pool_->running_in_this_thread(); } friend bool operator==(const executor_impl& a, const executor_impl& b) noexcept diff --git a/include/experimental/execution b/include/experimental/execution index 2bcb177..9b82c05 100644 --- a/include/experimental/execution +++ b/include/experimental/execution @@ -72,6 +72,9 @@ struct mapping_t; // Memory allocations. template struct allocator_t; +// Occupancy. +struct occupancy_t; + // Type traits to determine conformance to executor type requirements. template struct is_oneway_executor; template struct is_twoway_executor; @@ -118,6 +121,7 @@ template struct prefer_only; #include #include #include +#include #include #include #include From 472777695083f4448876548f90f96be31016a482 Mon Sep 17 00:00:00 2001 From: Jared Hoberock Date: Wed, 30 Jan 2019 16:16:31 -0600 Subject: [PATCH 03/14] Handle ForwardIterators in for_each_n --- examples/for_each/for_each_n_1.cpp | 130 +++++++++++++++++++++++-- examples/for_each/for_each_n_2.cpp | 139 ++++++++++++++++++++++++--- examples/for_each/for_each_n_3.cpp | 146 +++++++++++++++++++++++++---- examples/for_each/for_each_n_4.cpp | 146 +++++++++++++++++++++++++---- 4 files changed, 507 insertions(+), 54 deletions(-) diff --git a/examples/for_each/for_each_n_1.cpp b/examples/for_each/for_each_n_1.cpp index 58de48c..f8b5d18 100644 --- a/examples/for_each/for_each_n_1.cpp +++ b/examples/for_each/for_each_n_1.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -34,13 +35,15 @@ constexpr parallel_policy par{}; template -void for_each_n(const parallel_policy&, RandomAccessIterator first, Size n, Function f) +void for_each_n_impl(std::random_access_iterator_tag, RandomAccessIterator first, Size n, Function f) { auto ex = execution::require(impl::system_thread_pool.executor(), execution::bulk, execution::oneway, execution::blocking.always); try { // this only throws upon failure to create EAs + // XXX will also throw if f or first's cctor throws + // we could wrap the lambda in a thing that terminates if anything throws ex.bulk_execute( [=](size_t idx, impl::ignored&) { @@ -68,18 +71,131 @@ void for_each_n(const parallel_policy&, RandomAccessIterator first, Size n, Func } +template +std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) +{ + ForwardIterator first_ = first; + + // how large should each subrange be? + std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; + + // store an iterator pointing to the beginning of each subrange + // the final element of this vector points is first + n + std::vector result(num_subranges); + + Size subrange_idx = 0; + for(Size i = 0; i < n; i += subrange_size, ++subrange_idx) + { + result[subrange_idx] = first; + + // are we at the last tile? + std::size_t distance = (subrange_idx == num_subranges - 1) ? (n - i) : subrange_size; + + std::advance(first, distance); + } + + // finally, add the end of the range + result.push_back(first); + + return result; +} + + +template + >> +auto query_or(const T& query_me, const Property& prop, Default&&) +{ + return execution::query(query_me, prop); +} + + +template + >> +Default&& query_or(const T&, const Property&, Default&& result) +{ + return std::forward(result); +} + + + +template +void for_each_n_impl(std::forward_iterator_tag, ForwardIterator first, Size n, Function f) +{ + try + { + // enforce requirements + auto ex = execution::require(impl::system_thread_pool.executor(), execution::bulk, execution::oneway, execution::blocking.always); + + // choose a number of subranges + size_t num_subranges = std::min(n, query_or(ex, execution::occupancy, 1)); + + // create agents + ex.bulk_execute( + [=](size_t subrange_idx, const std::vector& subranges_begin) + { + ForwardIterator end = subranges_begin[subrange_idx+1]; + + for(ForwardIterator iter = subranges_begin[subrange_idx]; iter != end; ++iter) + { + f(*iter); + } + }, + num_subranges, + [=] + { + return partition_into_subranges(first, n, num_subranges); + } + ); + } + catch(...) + { + // sequential fallback + for(Size i = 0; i < n; ++i, ++first) + { + f(*first); + } + } +} + + +template +void for_each_n(const parallel_policy&, Iterator first, Size n, Function f) +{ + for_each_n_impl(typename std::iterator_traits::iterator_category(), first, n, f); +} + + int main() { - std::vector vec(10); + { + // test with par, vector - // test with par + std::vector vec(10); + + for_each_n(par, vec.begin(), vec.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } - for_each_n(par, vec.begin(), vec.size(), [](int& x) { - x = 42; - }); + // test with par, list + + std::list lst(10); - assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + for_each_n(par, lst.begin(), lst.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); + } std::cout << "OK" << std::endl; diff --git a/examples/for_each/for_each_n_2.cpp b/examples/for_each/for_each_n_2.cpp index 644b013..b9486dc 100644 --- a/examples/for_each/for_each_n_2.cpp +++ b/examples/for_each/for_each_n_2.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -91,13 +92,14 @@ constexpr parallel_policy par{}; template -void for_each_n(ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Function f) +void for_each_n_impl(std::random_access_iterator_tag, ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Function f) { - auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); - try { - // this only throws upon failure to create EAs + // enforce requirements + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); + + // create agents ex.bulk_execute( [=](size_t idx, impl::ignored&) { @@ -118,19 +120,134 @@ void for_each_n(ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Fu } +template + >> +auto query_or(const T& query_me, const Property& prop, Default&&) +{ + return execution::query(query_me, prop); +} + + +template + >> +Default&& query_or(const T&, const Property&, Default&& result) +{ + return std::forward(result); +} + + +template +std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) +{ + ForwardIterator first_ = first; + + // how large should each subrange be? + std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; + + // store an iterator pointing to the beginning of each subrange + // the final element of this vector points is first + n + std::vector result(num_subranges); + + Size subrange_idx = 0; + for(Size i = 0; i < n; i += subrange_size, ++subrange_idx) + { + result[subrange_idx] = first; + + // are we at the last tile? + std::size_t distance = (subrange_idx == num_subranges - 1) ? (n - i) : subrange_size; + + std::advance(first, distance); + } + + // finally, add the end of the range + result.push_back(first); + + return result; +} + + +template +void for_each_n_impl(std::forward_iterator_tag, ExecutionPolicy&& policy, ForwardIterator first, Size n, Function f) +{ + try + { + // enforce requirements + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); + + // choose a number of subranges + size_t num_subranges = std::min(n, query_or(ex, execution::occupancy, 1)); + + // create agents + ex.bulk_execute( + [=](size_t subrange_idx, const std::vector& subranges_begin) + { + ForwardIterator end = subranges_begin[subrange_idx+1]; + + for(ForwardIterator iter = subranges_begin[subrange_idx]; iter != end; ++iter) + { + f(*iter); + } + }, + num_subranges, + [=] + { + return partition_into_subranges(first, n, num_subranges); + } + ); + } + catch(...) + { + // sequential fallback + for(Size i = 0; i < n; ++i, ++first) + { + f(*first); + } + } +} + + +template +void for_each_n(ExecutionPolicy&& policy, Iterator first, Size n, Function f) +{ + for_each_n_impl(typename std::iterator_traits::iterator_category(), std::forward(policy), first, n, f); +} + + int main() { - std::vector vec(10); + { + // test with static_thread_pool, vector + + std::vector vec(10); - // test with static_thread_pool::executor_type - std::experimental::static_thread_pool pool(4); + std::experimental::static_thread_pool pool(4); + + for_each_n(par.on(pool.executor()), vec.begin(), vec.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } - for_each_n(par.on(pool.executor()), vec.begin(), vec.size(), [](int& x) { - x = 42; - }); + // test with static_thread_pool, list + + std::list lst(10); + + std::experimental::static_thread_pool pool(4); - assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + for_each_n(par.on(pool.executor()), lst.begin(), lst.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); + } std::cout << "OK" << std::endl; diff --git a/examples/for_each/for_each_n_3.cpp b/examples/for_each/for_each_n_3.cpp index 88d08d0..55c57f1 100644 --- a/examples/for_each/for_each_n_3.cpp +++ b/examples/for_each/for_each_n_3.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -90,25 +91,18 @@ class parallel_policy constexpr parallel_policy par{}; -template -void for_each_n(const parallel_policy&, RandomAccessIterator first, Size n, Function f) -{ - throw std::runtime_error("for_each_n(parallel_policy): Unimplemented."); -} - - template -void for_each_n(ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Function f) +void for_each_n_impl(std::random_access_iterator_tag, ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Function f) { - auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); - try { - // this only throws upon failure to create EAs + // enforce requirements + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); + + // create agents ex.bulk_execute( [=](size_t idx, impl::ignored&) { - // XXX terminate if this throws? f(first[idx]); }, n, @@ -126,6 +120,103 @@ void for_each_n(ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Fu } +template + >> +auto query_or(const T& query_me, const Property& prop, Default&&) +{ + return execution::query(query_me, prop); +} + + +template + >> +Default&& query_or(const T&, const Property&, Default&& result) +{ + return std::forward(result); +} + + +template +std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) +{ + ForwardIterator first_ = first; + + // how large should each subrange be? + std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; + + // store an iterator pointing to the beginning of each subrange + // the final element of this vector points is first + n + std::vector result(num_subranges); + + Size subrange_idx = 0; + for(Size i = 0; i < n; i += subrange_size, ++subrange_idx) + { + result[subrange_idx] = first; + + // are we at the last tile? + std::size_t distance = (subrange_idx == num_subranges - 1) ? (n - i) : subrange_size; + + std::advance(first, distance); + } + + // finally, add the end of the range + result.push_back(first); + + return result; +} + + +template +void for_each_n_impl(std::forward_iterator_tag, ExecutionPolicy&& policy, ForwardIterator first, Size n, Function f) +{ + try + { + // enforce requirements + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); + + // choose a number of subranges + size_t num_subranges = std::min(n, static_cast(query_or(ex, execution::occupancy, 1))); + + // create agents + ex.bulk_execute( + [=](size_t subrange_idx, const std::vector& subranges_begin) + { + ForwardIterator end = subranges_begin[subrange_idx+1]; + + for(ForwardIterator iter = subranges_begin[subrange_idx]; iter != end; ++iter) + { + f(*iter); + } + }, + num_subranges, + [=] + { + return partition_into_subranges(first, n, num_subranges); + } + ); + } + catch(...) + { + // sequential fallback + for(Size i = 0; i < n; ++i, ++first) + { + f(*first); + } + } +} + + +template +void for_each_n(ExecutionPolicy&& policy, Iterator first, Size n, Function f) +{ + for_each_n_impl(typename std::iterator_traits::iterator_category(), std::forward(policy), first, n, f); +} + + class inline_executor { public: @@ -175,16 +266,35 @@ class inline_executor int main() { - std::vector vec(10); + { + // test with inline_executor, vector + + std::vector vec(10); + + std::experimental::static_thread_pool pool(4); - // test with inline_executor + for_each_n(par.on(inline_executor()), vec.begin(), vec.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } - for_each_n(par.on(inline_executor()), vec.begin(), vec.size(), [](int& x) { - x = 42; - }); + // test with inline_executor, list + + std::list lst(10); - assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + std::experimental::static_thread_pool pool(4); + + for_each_n(par.on(inline_executor()), lst.begin(), lst.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); + } std::cout << "OK" << std::endl; diff --git a/examples/for_each/for_each_n_4.cpp b/examples/for_each/for_each_n_4.cpp index 03a8933..31c531a 100644 --- a/examples/for_each/for_each_n_4.cpp +++ b/examples/for_each/for_each_n_4.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -90,25 +91,18 @@ class parallel_policy constexpr parallel_policy par{}; -template -void for_each_n(const parallel_policy&, RandomAccessIterator first, Size n, Function f) -{ - throw std::runtime_error("for_each_n(parallel_policy): Unimplemented."); -} - - template -void for_each_n(ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Function f) +void for_each_n_impl(std::random_access_iterator_tag, ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Function f) { - auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); - try { - // this only throws upon failure to create EAs + // enforce requirements + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); + + // create agents ex.bulk_execute( [=](size_t idx, impl::ignored&) { - // XXX terminate if this throws? f(first[idx]); }, n, @@ -126,6 +120,103 @@ void for_each_n(ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Fu } +template + >> +auto query_or(const T& query_me, const Property& prop, Default&&) +{ + return execution::query(query_me, prop); +} + + +template + >> +Default&& query_or(const T&, const Property&, Default&& result) +{ + return std::forward(result); +} + + +template +std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) +{ + ForwardIterator first_ = first; + + // how large should each subrange be? + std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; + + // store an iterator pointing to the beginning of each subrange + // the final element of this vector points is first + n + std::vector result(num_subranges); + + Size subrange_idx = 0; + for(Size i = 0; i < n; i += subrange_size, ++subrange_idx) + { + result[subrange_idx] = first; + + // are we at the last tile? + std::size_t distance = (subrange_idx == num_subranges - 1) ? (n - i) : subrange_size; + + std::advance(first, distance); + } + + // finally, add the end of the range + result.push_back(first); + + return result; +} + + +template +void for_each_n_impl(std::forward_iterator_tag, ExecutionPolicy&& policy, ForwardIterator first, Size n, Function f) +{ + try + { + // enforce requirements + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); + + // choose a number of subranges + size_t num_subranges = std::min(n, static_cast(query_or(ex, execution::occupancy, 1))); + + // create agents + ex.bulk_execute( + [=](size_t subrange_idx, const std::vector& subranges_begin) + { + ForwardIterator end = subranges_begin[subrange_idx+1]; + + for(ForwardIterator iter = subranges_begin[subrange_idx]; iter != end; ++iter) + { + f(*iter); + } + }, + num_subranges, + [=] + { + return partition_into_subranges(first, n, num_subranges); + } + ); + } + catch(...) + { + // sequential fallback + for(Size i = 0; i < n; ++i, ++first) + { + f(*first); + } + } +} + + +template +void for_each_n(ExecutionPolicy&& policy, Iterator first, Size n, Function f) +{ + for_each_n_impl(typename std::iterator_traits::iterator_category(), std::forward(policy), first, n, f); +} + + class bulk_inline_executor { public: @@ -185,16 +276,35 @@ class bulk_inline_executor int main() { - std::vector vec(10); + { + // test with bulk_inline_executor, vector - // test with bulk_inline_executor + std::vector vec(10); + + std::experimental::static_thread_pool pool(4); + + for_each_n(par.on(bulk_inline_executor()), vec.begin(), vec.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } - for_each_n(par.on(bulk_inline_executor()), vec.begin(), vec.size(), [](int& x) { - x = 42; - }); + // test with bulk_inline_executor, list + + std::list lst(10); - assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + std::experimental::static_thread_pool pool(4); + + for_each_n(par.on(bulk_inline_executor()), lst.begin(), lst.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); + } std::cout << "OK" << std::endl; From 42bbd754e72a151ee9e64fcf3fdd18f95251b0d5 Mon Sep 17 00:00:00 2001 From: Jared Hoberock Date: Wed, 30 Jan 2019 16:24:19 -0600 Subject: [PATCH 04/14] Eliminate vestigial debugging code --- examples/for_each/for_each_n_1.cpp | 2 -- examples/for_each/for_each_n_2.cpp | 2 -- examples/for_each/for_each_n_3.cpp | 2 -- examples/for_each/for_each_n_4.cpp | 2 -- 4 files changed, 8 deletions(-) diff --git a/examples/for_each/for_each_n_1.cpp b/examples/for_each/for_each_n_1.cpp index f8b5d18..3eb4ce7 100644 --- a/examples/for_each/for_each_n_1.cpp +++ b/examples/for_each/for_each_n_1.cpp @@ -74,8 +74,6 @@ void for_each_n_impl(std::random_access_iterator_tag, RandomAccessIterator first template std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) { - ForwardIterator first_ = first; - // how large should each subrange be? std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; diff --git a/examples/for_each/for_each_n_2.cpp b/examples/for_each/for_each_n_2.cpp index b9486dc..925a362 100644 --- a/examples/for_each/for_each_n_2.cpp +++ b/examples/for_each/for_each_n_2.cpp @@ -143,8 +143,6 @@ Default&& query_or(const T&, const Property&, Default&& result) template std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) { - ForwardIterator first_ = first; - // how large should each subrange be? std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; diff --git a/examples/for_each/for_each_n_3.cpp b/examples/for_each/for_each_n_3.cpp index 55c57f1..fab12e5 100644 --- a/examples/for_each/for_each_n_3.cpp +++ b/examples/for_each/for_each_n_3.cpp @@ -143,8 +143,6 @@ Default&& query_or(const T&, const Property&, Default&& result) template std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) { - ForwardIterator first_ = first; - // how large should each subrange be? std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; diff --git a/examples/for_each/for_each_n_4.cpp b/examples/for_each/for_each_n_4.cpp index 31c531a..afca3e4 100644 --- a/examples/for_each/for_each_n_4.cpp +++ b/examples/for_each/for_each_n_4.cpp @@ -143,8 +143,6 @@ Default&& query_or(const T&, const Property&, Default&& result) template std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) { - ForwardIterator first_ = first; - // how large should each subrange be? std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; From 062f141c9bef4f231d2660a54de6f5af009fcdeb Mon Sep 17 00:00:00 2001 From: Jared Hoberock Date: Thu, 31 Jan 2019 16:40:17 -0600 Subject: [PATCH 05/14] Call std::terminate when an exception escapes an element access function invocation --- examples/for_each/for_each_n.hpp | 144 ++++++++++++ examples/for_each/for_each_n_1.cpp | 226 ++++++------------- examples/for_each/for_each_n_2.cpp | 274 ++++++---------------- examples/for_each/for_each_n_3.cpp | 288 ++++++------------------ examples/for_each/for_each_n_4.cpp | 288 ++++++------------------ examples/for_each/noexcept_function.hpp | 51 +++++ examples/for_each/noexcept_iterator.hpp | 169 ++++++++++++++ examples/for_each/par.hpp | 99 ++++++++ examples/for_each/query_or.hpp | 23 ++ examples/for_each/throwing_iterator.hpp | 145 ++++++++++++ 10 files changed, 906 insertions(+), 801 deletions(-) create mode 100644 examples/for_each/for_each_n.hpp create mode 100644 examples/for_each/noexcept_function.hpp create mode 100644 examples/for_each/noexcept_iterator.hpp create mode 100644 examples/for_each/par.hpp create mode 100644 examples/for_each/query_or.hpp create mode 100644 examples/for_each/throwing_iterator.hpp diff --git a/examples/for_each/for_each_n.hpp b/examples/for_each/for_each_n.hpp new file mode 100644 index 0000000..da2bdeb --- /dev/null +++ b/examples/for_each/for_each_n.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include "query_or.hpp" +#include "noexcept_function.hpp" +#include "noexcept_iterator.hpp" +#include +#include +#include + + +namespace impl +{ + + +struct ignored {}; +constexpr ignored ignore{}; + + +template +void for_each_n(std::random_access_iterator_tag, ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Function f) +{ + // terminate if f throws for any reason + auto noexcept_f = impl::make_noexcept_function(f); + + // terminate if an iterator operation throws for any reason + auto noexcept_first = impl::make_noexcept_iterator(first); + + try + { + namespace execution = std::experimental::execution; + + // enforce requirements + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always); + + // create agents + // note that this throws only upon failure to create EAs + ex.bulk_execute( + [=](size_t idx, impl::ignored&) + { + noexcept_f(noexcept_first[idx]); + }, + n, + []{ return impl::ignore; } + ); + } + catch(...) + { + // sequential fallback + for(Size i = 0; i < n; ++i, ++noexcept_first) + { + noexcept_f(*noexcept_first); + } + } +} + + +template +std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) +{ + // how large should each subrange be? + std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; + + // store an iterator pointing to the beginning of each subrange + // the final element of this vector points is first + n + std::vector result; + result.reserve(num_subranges + 1); + + Size subrange_idx = 0; + for(Size i = 0; i < n; i += subrange_size, ++subrange_idx) + { + // append the beginning of the subrange + result.push_back(first); + + // are we at the last subrange? + std::size_t distance = (subrange_idx == num_subranges - 1) ? (n - i) : subrange_size; + + std::advance(first, distance); + } + + // finally, add the end of the range + result.push_back(first); + + return result; +} + + +template +void for_each_n(std::forward_iterator_tag, ExecutionPolicy&& policy, ForwardIterator first, Size n, Function f) +{ + // terminate if f throws for any reason + auto noexcept_f = impl::make_noexcept_function(f); + + // terminate if an iterator operation throws for any reason + auto noexcept_first = impl::make_noexcept_iterator(first); + + try + { + namespace execution = std::experimental::execution; + + // enforce requirements + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always); + + // choose a number of subranges + size_t num_subranges = std::min(n, query_or(ex, execution::occupancy, Size(1))); + + // create agents + // note that this throws only upon failure to create EAs + ex.bulk_execute( + [=](size_t subrange_idx, const std::vector>& subranges_begin) + { + noexcept_iterator end = subranges_begin[subrange_idx+1]; + + for(noexcept_iterator iter = subranges_begin[subrange_idx]; iter != end; ++iter) + { + noexcept_f(*iter); + } + }, + num_subranges, + [=] + { + return impl::partition_into_subranges(noexcept_first, n, num_subranges); + } + ); + } + catch(...) + { + // sequential fallback + for(Size i = 0; i < n; ++i, ++noexcept_first) + { + noexcept_f(*noexcept_first); + } + } +} + + +} // end impl + + +template +void for_each_n(ExecutionPolicy&& policy, Iterator first, Size n, Function f) +{ + impl::for_each_n(typename std::iterator_traits::iterator_category(), std::forward(policy), first, n, f); +} + diff --git a/examples/for_each/for_each_n_1.cpp b/examples/for_each/for_each_n_1.cpp index 3eb4ce7..c65a4e8 100644 --- a/examples/for_each/for_each_n_1.cpp +++ b/examples/for_each/for_each_n_1.cpp @@ -1,199 +1,103 @@ -#include -#include +#include "for_each_n.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" #include #include #include #include #include - -namespace execution = std::experimental::execution; -using std::experimental::static_thread_pool; - - -namespace impl -{ - - -static_thread_pool system_thread_pool{std::max(1u,std::thread::hardware_concurrency())}; - - -constexpr struct ignored {} ignore; - - -} // end impl - - -class parallel_policy -{ - public: - static constexpr execution::bulk_guarantee_t::parallel_t execution_requirement{}; -}; - - -constexpr parallel_policy par{}; - - -template -void for_each_n_impl(std::random_access_iterator_tag, RandomAccessIterator first, Size n, Function f) +int main() { - auto ex = execution::require(impl::system_thread_pool.executor(), execution::bulk, execution::oneway, execution::blocking.always); - - try { - // this only throws upon failure to create EAs - // XXX will also throw if f or first's cctor throws - // we could wrap the lambda in a thing that terminates if anything throws - ex.bulk_execute( - [=](size_t idx, impl::ignored&) - { - try - { - f(first[idx]); - } - catch(...) - { - std::terminate(); - } - }, - n, - []{ return impl::ignore; } - ); - } - catch(...) - { - // sequential fallback - for(Size i = 0; i < n; ++i, ++first) - { - f(*first); - } - } -} + // test with par, vector + std::vector vec(10); -template -std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) -{ - // how large should each subrange be? - std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; + for_each_n(par, vec.begin(), vec.size(), [](int& x) + { + x = 42; + }); - // store an iterator pointing to the beginning of each subrange - // the final element of this vector points is first + n - std::vector result(num_subranges); + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } - Size subrange_idx = 0; - for(Size i = 0; i < n; i += subrange_size, ++subrange_idx) { - result[subrange_idx] = first; + // test with par, list - // are we at the last tile? - std::size_t distance = (subrange_idx == num_subranges - 1) ? (n - i) : subrange_size; + std::list lst(10); - std::advance(first, distance); - } + for_each_n(par, lst.begin(), lst.size(), [](int& x) + { + x = 42; + }); - // finally, add the end of the range - result.push_back(first); + assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); + } - return result; -} + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + //{ + // // test with par, vector, throwing function -template - >> -auto query_or(const T& query_me, const Property& prop, Default&&) -{ - return execution::query(query_me, prop); -} + // std::vector vec(10); + // for_each_n(par, vec.begin(), vec.size(), [](int& x) + // { + // x = 42; -template - >> -Default&& query_or(const T&, const Property&, Default&& result) -{ - return std::forward(result); -} + // throw 13; + // }); + // // this line should never be reached + // assert(false); + //} + //{ + // // test with par, list, throwing function -template -void for_each_n_impl(std::forward_iterator_tag, ForwardIterator first, Size n, Function f) -{ - try - { - // enforce requirements - auto ex = execution::require(impl::system_thread_pool.executor(), execution::bulk, execution::oneway, execution::blocking.always); - - // choose a number of subranges - size_t num_subranges = std::min(n, query_or(ex, execution::occupancy, 1)); - - // create agents - ex.bulk_execute( - [=](size_t subrange_idx, const std::vector& subranges_begin) - { - ForwardIterator end = subranges_begin[subrange_idx+1]; - - for(ForwardIterator iter = subranges_begin[subrange_idx]; iter != end; ++iter) - { - f(*iter); - } - }, - num_subranges, - [=] - { - return partition_into_subranges(first, n, num_subranges); - } - ); - } - catch(...) - { - // sequential fallback - for(Size i = 0; i < n; ++i, ++first) - { - f(*first); - } - } -} + // std::list lst(10); + // for_each_n(par, lst.begin(), lst.size(), [](int& x) + // { + // x = 42; -template -void for_each_n(const parallel_policy&, Iterator first, Size n, Function f) -{ - for_each_n_impl(typename std::iterator_traits::iterator_category(), first, n, f); -} + // throw 13; + // }); + // // this line should never be reached + // assert(false); + //} -int main() -{ - { - // test with par, vector + //{ + // // test with par, vector, throwing iterator - std::vector vec(10); + // std::vector vec(10); - for_each_n(par, vec.begin(), vec.size(), [](int& x) - { - x = 42; - }); + // for_each_n(par, make_throwing_iterator(vec.begin()), vec.size(), [](int& x) + // { + // x = 42; + // }); - assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); - } + // // this line should never be reached + // assert(false); + //} - { - // test with par, list + //{ + // // test with par, list, throwing iterator - std::list lst(10); + // std::list lst(10); - for_each_n(par, lst.begin(), lst.size(), [](int& x) - { - x = 42; - }); + // for_each_n(par, make_throwing_iterator(lst.begin()), lst.size(), [](int& x) + // { + // x = 42; + // }); - assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); - } + // // this line should never be reached + // assert(false); + //} std::cout << "OK" << std::endl; diff --git a/examples/for_each/for_each_n_2.cpp b/examples/for_each/for_each_n_2.cpp index 925a362..b319f83 100644 --- a/examples/for_each/for_each_n_2.cpp +++ b/examples/for_each/for_each_n_2.cpp @@ -1,5 +1,7 @@ +#include "for_each_n.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" #include -#include #include #include #include @@ -7,245 +9,109 @@ #include -namespace execution = std::experimental::execution; -using std::experimental::static_thread_pool; - - -namespace impl +int main() { + { + // test with static_thread_pool, vector + std::vector vec(10); -template class basic_execution_policy; - - -template -basic_execution_policy make_basic_execution_policy(const E& ex); - - -template -class basic_execution_policy -{ - public: - static_assert(execution::can_require_v); - - static constexpr ExecutionRequirement execution_requirement{}; + std::experimental::static_thread_pool pool(4); - Executor executor() const + for_each_n(par.on(pool.executor()), vec.begin(), vec.size(), [](int& x) { - return executor_; - } - - private: - template - friend basic_execution_policy make_basic_execution_policy(const E& ex); - - basic_execution_policy(const Executor& executor) - : executor_(executor) - {} - - Executor executor_; -}; - - -template -basic_execution_policy make_basic_execution_policy(const E& ex) -{ - return basic_execution_policy{ex}; -} - - -constexpr struct ignored {} ignore; - + x = 42; + }); -template -constexpr bool satisfies_cpp20_on_requirements_v = - execution::can_require_v< - Executor - , ExecutionRequirement - , execution::bulk_t - , execution::oneway_t - , execution::blocking_t::always_t - , execution::mapping_t::thread_t ->; + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } + { + // test with static_thread_pool, list -} // end impl + std::list lst(10); + std::experimental::static_thread_pool pool(4); -class parallel_policy -{ - public: - static constexpr execution::bulk_guarantee_t::parallel_t execution_requirement{}; - - template - >> - impl::basic_execution_policy on(const Executor& ex) const + for_each_n(par.on(pool.executor()), lst.begin(), lst.size(), [](int& x) { - return impl::make_basic_execution_policy(ex); - } -}; - - -constexpr parallel_policy par{}; - + x = 42; + }); -template -void for_each_n_impl(std::random_access_iterator_tag, ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Function f) -{ - try - { - // enforce requirements - auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); - - // create agents - ex.bulk_execute( - [=](size_t idx, impl::ignored&) - { - f(first[idx]); - }, - n, - []{ return impl::ignore; } - ); - } - catch(...) - { - // sequential fallback - for(Size i = 0; i < n; ++i, ++first) - { - f(*first); - } + assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); } -} - -template - >> -auto query_or(const T& query_me, const Property& prop, Default&&) -{ - return execution::query(query_me, prop); -} + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + //{ + // // test with static_thread_pool, vector, throwing function -template - >> -Default&& query_or(const T&, const Property&, Default&& result) -{ - return std::forward(result); -} + // std::vector vec(10); + // std::experimental::static_thread_pool pool(4); -template -std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) -{ - // how large should each subrange be? - std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; - - // store an iterator pointing to the beginning of each subrange - // the final element of this vector points is first + n - std::vector result(num_subranges); - - Size subrange_idx = 0; - for(Size i = 0; i < n; i += subrange_size, ++subrange_idx) - { - result[subrange_idx] = first; - - // are we at the last tile? - std::size_t distance = (subrange_idx == num_subranges - 1) ? (n - i) : subrange_size; + // for_each_n(par.on(pool.executor()), vec.begin(), vec.size(), [](int& x) + // { + // x = 42; - std::advance(first, distance); - } + // throw 13; + // }); - // finally, add the end of the range - result.push_back(first); + // // this line should never be reached + // assert(false); + //} - return result; -} + //{ + // // test with static_thread_pool, list, throwing function + // std::list lst(10); -template -void for_each_n_impl(std::forward_iterator_tag, ExecutionPolicy&& policy, ForwardIterator first, Size n, Function f) -{ - try - { - // enforce requirements - auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); - - // choose a number of subranges - size_t num_subranges = std::min(n, query_or(ex, execution::occupancy, 1)); - - // create agents - ex.bulk_execute( - [=](size_t subrange_idx, const std::vector& subranges_begin) - { - ForwardIterator end = subranges_begin[subrange_idx+1]; - - for(ForwardIterator iter = subranges_begin[subrange_idx]; iter != end; ++iter) - { - f(*iter); - } - }, - num_subranges, - [=] - { - return partition_into_subranges(first, n, num_subranges); - } - ); - } - catch(...) - { - // sequential fallback - for(Size i = 0; i < n; ++i, ++first) - { - f(*first); - } - } -} + // std::experimental::static_thread_pool pool(4); + // for_each_n(par.on(pool.executor()), lst.begin(), lst.size(), [](int& x) + // { + // x = 42; -template -void for_each_n(ExecutionPolicy&& policy, Iterator first, Size n, Function f) -{ - for_each_n_impl(typename std::iterator_traits::iterator_category(), std::forward(policy), first, n, f); -} + // throw 13; + // }); + // // this line should never be reached + // assert(false); + //} -int main() -{ - { - // test with static_thread_pool, vector + //{ + // // test with static_thread_pool, vector, throwing iterator - std::vector vec(10); + // std::vector vec(10); - std::experimental::static_thread_pool pool(4); + // std::experimental::static_thread_pool pool(4); - for_each_n(par.on(pool.executor()), vec.begin(), vec.size(), [](int& x) - { - x = 42; - }); + // for_each_n(par.on(pool.executor()), make_throwing_iterator(vec.begin()), vec.size(), [](int& x) + // { + // x = 42; + // }); - assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); - } + // // this line should never be reached + // assert(false); + //} - { - // test with static_thread_pool, list + //{ + // // test with static_thread_pool, list, throwing iterator - std::list lst(10); + // std::list lst(10); - std::experimental::static_thread_pool pool(4); + // std::experimental::static_thread_pool pool(4); - for_each_n(par.on(pool.executor()), lst.begin(), lst.size(), [](int& x) - { - x = 42; - }); + // for_each_n(par.on(pool.executor()), make_throwing_iterator(lst.begin()), lst.size(), [](int& x) + // { + // x = 42; + // }); - assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); - } + // // this line should never be reached + // assert(false); + //} std::cout << "OK" << std::endl; diff --git a/examples/for_each/for_each_n_3.cpp b/examples/for_each/for_each_n_3.cpp index fab12e5..ce49138 100644 --- a/examples/for_each/for_each_n_3.cpp +++ b/examples/for_each/for_each_n_3.cpp @@ -1,218 +1,14 @@ -#include -#include +#include +#include "for_each_n.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" #include #include #include #include -#include namespace execution = std::experimental::execution; -using std::experimental::static_thread_pool; - - -namespace impl -{ - - -template class basic_execution_policy; - - -template -basic_execution_policy make_basic_execution_policy(const E& ex); - - -template -class basic_execution_policy -{ - public: - static_assert(execution::can_require_v); - - static constexpr ExecutionRequirement execution_requirement{}; - - Executor executor() const - { - return executor_; - } - - private: - template - friend basic_execution_policy make_basic_execution_policy(const E& ex); - - basic_execution_policy(const Executor& executor) - : executor_(executor) - {} - - Executor executor_; -}; - - -template -basic_execution_policy make_basic_execution_policy(const E& ex) -{ - return basic_execution_policy{ex}; -} - - -constexpr struct ignored {} ignore; - - -template -constexpr bool satisfies_cpp20_on_requirements_v = - execution::can_require_v< - Executor - , ExecutionRequirement - , execution::bulk_t - , execution::oneway_t - , execution::blocking_t::always_t - , execution::mapping_t::thread_t ->; - - -} // end impl - - -class parallel_policy -{ - public: - static constexpr execution::bulk_guarantee_t::parallel_t execution_requirement{}; - - template - >> - impl::basic_execution_policy on(const Executor& ex) const - { - return impl::make_basic_execution_policy(ex); - } -}; - - -constexpr parallel_policy par{}; - - -template -void for_each_n_impl(std::random_access_iterator_tag, ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Function f) -{ - try - { - // enforce requirements - auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); - - // create agents - ex.bulk_execute( - [=](size_t idx, impl::ignored&) - { - f(first[idx]); - }, - n, - []{ return impl::ignore; } - ); - } - catch(...) - { - // sequential fallback - for(Size i = 0; i < n; ++i, ++first) - { - f(*first); - } - } -} - - -template - >> -auto query_or(const T& query_me, const Property& prop, Default&&) -{ - return execution::query(query_me, prop); -} - - -template - >> -Default&& query_or(const T&, const Property&, Default&& result) -{ - return std::forward(result); -} - - -template -std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) -{ - // how large should each subrange be? - std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; - - // store an iterator pointing to the beginning of each subrange - // the final element of this vector points is first + n - std::vector result(num_subranges); - - Size subrange_idx = 0; - for(Size i = 0; i < n; i += subrange_size, ++subrange_idx) - { - result[subrange_idx] = first; - - // are we at the last tile? - std::size_t distance = (subrange_idx == num_subranges - 1) ? (n - i) : subrange_size; - - std::advance(first, distance); - } - - // finally, add the end of the range - result.push_back(first); - - return result; -} - - -template -void for_each_n_impl(std::forward_iterator_tag, ExecutionPolicy&& policy, ForwardIterator first, Size n, Function f) -{ - try - { - // enforce requirements - auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); - - // choose a number of subranges - size_t num_subranges = std::min(n, static_cast(query_or(ex, execution::occupancy, 1))); - - // create agents - ex.bulk_execute( - [=](size_t subrange_idx, const std::vector& subranges_begin) - { - ForwardIterator end = subranges_begin[subrange_idx+1]; - - for(ForwardIterator iter = subranges_begin[subrange_idx]; iter != end; ++iter) - { - f(*iter); - } - }, - num_subranges, - [=] - { - return partition_into_subranges(first, n, num_subranges); - } - ); - } - catch(...) - { - // sequential fallback - for(Size i = 0; i < n; ++i, ++first) - { - f(*first); - } - } -} - - -template -void for_each_n(ExecutionPolicy&& policy, Iterator first, Size n, Function f) -{ - for_each_n_impl(typename std::iterator_traits::iterator_category(), std::forward(policy), first, n, f); -} class inline_executor @@ -251,13 +47,7 @@ class inline_executor template void execute(Function f) const noexcept { - try - { - f(); - } - catch(...) - { - } + f(); } }; @@ -269,8 +59,6 @@ int main() std::vector vec(10); - std::experimental::static_thread_pool pool(4); - for_each_n(par.on(inline_executor()), vec.begin(), vec.size(), [](int& x) { x = 42; @@ -284,8 +72,6 @@ int main() std::list lst(10); - std::experimental::static_thread_pool pool(4); - for_each_n(par.on(inline_executor()), lst.begin(), lst.size(), [](int& x) { x = 42; @@ -294,6 +80,70 @@ int main() assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); } + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with inline_executor, vector, throwing function + + // std::vector vec(10); + + // for_each_n(par.on(inline_executor()), vec.begin(), vec.size(), [](int& x) + // { + // x = 42; + + // throw 13; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with inline_executor, list, throwing function + + // std::list lst(10); + + // for_each_n(par.on(inline_executor()), lst.begin(), lst.size(), [](int& x) + // { + // x = 42; + + // throw 13; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with inline_executor, vector, throwing iterator + + // std::vector vec(10); + + // for_each_n(par.on(inline_executor()), make_throwing_iterator(vec.begin()), vec.size(), [](int& x) + // { + // x = 42; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with inline_executor, list, throwing iterator + + // std::list lst(10); + + // for_each_n(par.on(inline_executor()), make_throwing_iterator(lst.begin()), lst.size(), [](int& x) + // { + // x = 42; + // }); + + // // this line should never be reached + // assert(false); + //} + std::cout << "OK" << std::endl; return 0; diff --git a/examples/for_each/for_each_n_4.cpp b/examples/for_each/for_each_n_4.cpp index afca3e4..924b3aa 100644 --- a/examples/for_each/for_each_n_4.cpp +++ b/examples/for_each/for_each_n_4.cpp @@ -1,218 +1,14 @@ -#include -#include +#include +#include "for_each_n.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" #include #include #include #include -#include namespace execution = std::experimental::execution; -using std::experimental::static_thread_pool; - - -namespace impl -{ - - -template class basic_execution_policy; - - -template -basic_execution_policy make_basic_execution_policy(const E& ex); - - -template -class basic_execution_policy -{ - public: - static_assert(execution::can_require_v); - - static constexpr ExecutionRequirement execution_requirement{}; - - Executor executor() const - { - return executor_; - } - - private: - template - friend basic_execution_policy make_basic_execution_policy(const E& ex); - - basic_execution_policy(const Executor& executor) - : executor_(executor) - {} - - Executor executor_; -}; - - -template -basic_execution_policy make_basic_execution_policy(const E& ex) -{ - return basic_execution_policy{ex}; -} - - -constexpr struct ignored {} ignore; - - -template -constexpr bool satisfies_cpp20_on_requirements_v = - execution::can_require_v< - Executor - , ExecutionRequirement - , execution::bulk_t - , execution::oneway_t - , execution::blocking_t::always_t - , execution::mapping_t::thread_t ->; - - -} // end impl - - -class parallel_policy -{ - public: - static constexpr execution::bulk_guarantee_t::parallel_t execution_requirement{}; - - template - >> - impl::basic_execution_policy on(const Executor& ex) const - { - return impl::make_basic_execution_policy(ex); - } -}; - - -constexpr parallel_policy par{}; - - -template -void for_each_n_impl(std::random_access_iterator_tag, ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Function f) -{ - try - { - // enforce requirements - auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); - - // create agents - ex.bulk_execute( - [=](size_t idx, impl::ignored&) - { - f(first[idx]); - }, - n, - []{ return impl::ignore; } - ); - } - catch(...) - { - // sequential fallback - for(Size i = 0; i < n; ++i, ++first) - { - f(*first); - } - } -} - - -template - >> -auto query_or(const T& query_me, const Property& prop, Default&&) -{ - return execution::query(query_me, prop); -} - - -template - >> -Default&& query_or(const T&, const Property&, Default&& result) -{ - return std::forward(result); -} - - -template -std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) -{ - // how large should each subrange be? - std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; - - // store an iterator pointing to the beginning of each subrange - // the final element of this vector points is first + n - std::vector result(num_subranges); - - Size subrange_idx = 0; - for(Size i = 0; i < n; i += subrange_size, ++subrange_idx) - { - result[subrange_idx] = first; - - // are we at the last tile? - std::size_t distance = (subrange_idx == num_subranges - 1) ? (n - i) : subrange_size; - - std::advance(first, distance); - } - - // finally, add the end of the range - result.push_back(first); - - return result; -} - - -template -void for_each_n_impl(std::forward_iterator_tag, ExecutionPolicy&& policy, ForwardIterator first, Size n, Function f) -{ - try - { - // enforce requirements - auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always, execution::mapping.thread); - - // choose a number of subranges - size_t num_subranges = std::min(n, static_cast(query_or(ex, execution::occupancy, 1))); - - // create agents - ex.bulk_execute( - [=](size_t subrange_idx, const std::vector& subranges_begin) - { - ForwardIterator end = subranges_begin[subrange_idx+1]; - - for(ForwardIterator iter = subranges_begin[subrange_idx]; iter != end; ++iter) - { - f(*iter); - } - }, - num_subranges, - [=] - { - return partition_into_subranges(first, n, num_subranges); - } - ); - } - catch(...) - { - // sequential fallback - for(Size i = 0; i < n; ++i, ++first) - { - f(*first); - } - } -} - - -template -void for_each_n(ExecutionPolicy&& policy, Iterator first, Size n, Function f) -{ - for_each_n_impl(typename std::iterator_traits::iterator_category(), std::forward(policy), first, n, f); -} class bulk_inline_executor @@ -256,17 +52,11 @@ class bulk_inline_executor template void bulk_execute(Function f, size_t n, Factory shared_factory) const noexcept { - try - { - auto shared = shared_factory(); + auto shared = shared_factory(); - for(size_t i = 0; i < n; ++i) - { - f(i, shared); - } - } - catch(...) + for(size_t i = 0; i < n; ++i) { + f(i, shared); } } }; @@ -304,6 +94,70 @@ int main() assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); } + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with bulk_inline_executor, vector, throwing function + + // std::vector vec(10); + + // for_each_n(par.on(bulk_inline_executor()), vec.begin(), vec.size(), [](int& x) + // { + // x = 42; + + // throw 13; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with bulk_inline_executor, list, throwing function + + // std::list lst(10); + + // for_each_n(par.on(bulk_inline_executor()), lst.begin(), lst.size(), [](int& x) + // { + // x = 42; + + // throw 13; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with bulk_inline_executor, vector, throwing iterator + + // std::vector vec(10); + + // for_each_n(par.on(bulk_inline_executor()), make_throwing_iterator(vec.begin()), vec.size(), [](int& x) + // { + // x = 42; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with bulk_inline_executor, list, throwing iterator + + // std::list lst(10); + + // for_each_n(par.on(bulk_inline_executor()), make_throwing_iterator(lst.begin()), lst.size(), [](int& x) + // { + // x = 42; + // }); + + // // this line should never be reached + // assert(false); + //} + std::cout << "OK" << std::endl; return 0; diff --git a/examples/for_each/noexcept_function.hpp b/examples/for_each/noexcept_function.hpp new file mode 100644 index 0000000..5833ded --- /dev/null +++ b/examples/for_each/noexcept_function.hpp @@ -0,0 +1,51 @@ +#pragma + +#include +#include + +namespace impl +{ + + +template +class noexcept_function +{ + public: + template + >> + noexcept_function(OtherFunction&& function) noexcept + : function_(std::forward(function)) + {} + + noexcept_function(const noexcept_function& other) noexcept + : function_(other.function_) + {} + + noexcept_function(noexcept_function&& other) noexcept + : function_(std::move(other.function_)) + {} + + ~noexcept_function() noexcept + {} + + template + auto operator()(Args&&... args) const noexcept + { + return function_(std::forward(args)...); + } + + private: + mutable Function function_; +}; + +template +noexcept_function> make_noexcept_function(Function&& f) +{ + return {std::forward(f)}; +} + + +} // end impl + diff --git a/examples/for_each/noexcept_iterator.hpp b/examples/for_each/noexcept_iterator.hpp new file mode 100644 index 0000000..2785fc5 --- /dev/null +++ b/examples/for_each/noexcept_iterator.hpp @@ -0,0 +1,169 @@ +#pragma once + +#include +#include +#include + + +namespace impl +{ + + +template +class noexcept_iterator +{ + public: + template + >> + explicit noexcept_iterator(OtherIterator&& iterator) noexcept + : iterator_(std::forward(iterator)) + {} + + // CopyConstructible requirements + noexcept_iterator(const noexcept_iterator& other) noexcept + : iterator_(other.iterator_) + {} + + // CopyAssignable requirements + noexcept_iterator& operator=(const noexcept_iterator& other) noexcept + { + iterator_ = other.iterator_; + return *this; + } + + // Destructible requirements + ~noexcept_iterator() noexcept + {} + + // Swappable requirements + void swap(const noexcept_iterator& other) noexcept + { + using std::swap; + swap(iterator_, other.iterator_); + } + + // LegacyIterator requirements + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using reference = typename std::iterator_traits::reference; + using pointer = typename std::iterator_traits::pointer; + using iterator_category = typename std::iterator_traits::iterator_category; + + noexcept_iterator& operator++() noexcept + { + ++iterator_; + return *this; + } + + // LegacyInputIterator requirements + bool operator!=(const noexcept_iterator& other) const noexcept + { + return iterator_ != other.iterator_; + } + + reference operator*() const noexcept + { + return *iterator_; + } + + pointer operator->() const noexcept + { + // XXX this doesn't seem right because it wouldn't work if Iterator is a raw pointer + return iterator_.operator->(); + } + + noexcept_iterator operator++(int) const noexcept + { + noexcept_iterator result(*this); + operator++(); + return result; + } + + // LegacyBidirectionalIterator requirements + noexcept_iterator& operator--() noexcept + { + --iterator_; + return *this; + } + + noexcept_iterator operator--(int) const noexcept + { + noexcept_iterator result(*this); + operator--(); + return result; + } + + // LegacyRandomAccessIterator requirements + noexcept_iterator& operator+=(const difference_type& n) noexcept + { + iterator_ += n; + return *this; + } + + friend noexcept_iterator operator+(const noexcept_iterator& i, const difference_type& n) noexcept + { + return {i.iterator_ + n}; + } + + friend noexcept_iterator operator+(const difference_type& n, const noexcept_iterator& i) noexcept + { + return {n + i.iterator_}; + } + + noexcept_iterator& operator-=(const difference_type& n) noexcept + { + iterator_ -=n; + return *this; + } + + noexcept_iterator operator-(const difference_type& n) const noexcept + { + return {iterator_ - n}; + } + + difference_type operator-(const noexcept_iterator& rhs) const noexcept + { + return iterator_ - rhs.iterator_; + } + + reference operator[](const difference_type& n) const noexcept + { + return iterator_[n]; + } + + bool operator<(const noexcept_iterator& rhs) const noexcept + { + return iterator_ < rhs.iterator_; + } + + bool operator>(const noexcept_iterator& rhs) const noexcept + { + return iterator_ > rhs.iterator_; + } + + bool operator>=(const noexcept_iterator& rhs) const noexcept + { + return iterator_ >= rhs.iterator_; + } + + bool operator<=(const noexcept_iterator& rhs) const noexcept + { + return iterator_ <= rhs.iterator_; + } + + private: + mutable Iterator iterator_; +}; + + +template +noexcept_iterator> make_noexcept_iterator(Iterator&& iter) +{ + return noexcept_iterator>{std::forward(iter)}; +} + + +} // end impl + diff --git a/examples/for_each/par.hpp b/examples/for_each/par.hpp new file mode 100644 index 0000000..b9313f9 --- /dev/null +++ b/examples/for_each/par.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include +#include +#include +#include + + +namespace impl +{ + + +template class basic_execution_policy; + + +template +basic_execution_policy make_basic_execution_policy(const E& ex); + + +template +class basic_execution_policy +{ + public: + static constexpr ExecutionRequirement execution_requirement{}; + + Executor executor() const + { + return executor_; + } + + private: + template + friend basic_execution_policy make_basic_execution_policy(const E& ex); + + basic_execution_policy(const Executor& executor) + : executor_(executor) + {} + + Executor executor_; +}; + + +template +basic_execution_policy make_basic_execution_policy(const E& ex) +{ + return basic_execution_policy{ex}; +} + + +template +constexpr bool satisfies_cpp20_on_requirements_v = + std::experimental::execution::can_require_v< + Executor + , ExecutionRequirement + , std::experimental::execution::bulk_t + , std::experimental::execution::oneway_t + , std::experimental::execution::blocking_t::always_t + , std::experimental::execution::mapping_t::thread_t +>; + + +} // end impl + + +class parallel_policy +{ + public: + static constexpr std::experimental::execution::bulk_guarantee_t::parallel_t execution_requirement{}; + + template + >> + impl::basic_execution_policy on(const Executor& ex) const + { + return impl::make_basic_execution_policy(ex); + } +}; + + +constexpr parallel_policy par{}; + + +namespace impl +{ + + +std::experimental::static_thread_pool system_thread_pool{std::max(1u,std::thread::hardware_concurrency())}; + + +} // end impl + + +template +void for_each_n(const parallel_policy&, Iterator first, Size n, Function f) +{ + return for_each_n(par.on(impl::system_thread_pool.executor()), first, n, f); +} + diff --git a/examples/for_each/query_or.hpp b/examples/for_each/query_or.hpp new file mode 100644 index 0000000..2887450 --- /dev/null +++ b/examples/for_each/query_or.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +template + >> +auto query_or(const T& query_me, const Property& prop, Default&&) +{ + return std::experimental::execution::query(query_me, prop); +} + + +template + >> +Default&& query_or(const T&, const Property&, Default&& result) +{ + return std::forward(result); +} diff --git a/examples/for_each/throwing_iterator.hpp b/examples/for_each/throwing_iterator.hpp new file mode 100644 index 0000000..60f4593 --- /dev/null +++ b/examples/for_each/throwing_iterator.hpp @@ -0,0 +1,145 @@ +#pragma once + +#include +#include +#include + + +template +class throwing_iterator +{ + public: + template + >> + explicit throwing_iterator(OtherIterator&& iterator) + : iterator_(std::forward(iterator)) + {} + + // CopyConstructible requirements + throwing_iterator(const throwing_iterator& other) + : iterator_(other.iterator_) + {} + + // LegacyIterator requirements + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using reference = typename std::iterator_traits::reference; + using pointer = typename std::iterator_traits::pointer; + using iterator_category = typename std::iterator_traits::iterator_category; + + throwing_iterator& operator++() + { + ++iterator_; + return *this; + } + + // LegacyInputIterator requirements + bool operator!=(const throwing_iterator& other) const + { + return iterator_ != other.iterator_; + } + + reference operator*() const + { + throw 13; + return *iterator_; + } + + pointer operator->() const + { + // XXX this doesn't seem right because it wouldn't work if Iterator is a raw pointer + return iterator_.operator->(); + } + + throwing_iterator operator++(int) const + { + throwing_iterator result(*this); + operator++(); + return result; + } + + // LegacyBidirectionalIterator requirements + throwing_iterator& operator--() + { + --iterator_; + return *this; + } + + throwing_iterator operator--(int) const + { + throwing_iterator result(*this); + operator--(); + return result; + } + + // LegacyRandomAccessIterator requirements + throwing_iterator& operator+=(const difference_type& n) + { + iterator_ += n; + return *this; + } + + friend throwing_iterator operator+(const throwing_iterator& i, const difference_type& n) + { + return {i.iterator_ + n}; + } + + friend throwing_iterator operator+(const difference_type& n, const throwing_iterator& i) + { + return {n + i.iterator_}; + } + + throwing_iterator& operator-=(const difference_type& n) + { + iterator_ -=n; + return *this; + } + + throwing_iterator operator-(const difference_type& n) const + { + return {iterator_ - n}; + } + + difference_type operator-(const throwing_iterator& rhs) const + { + return iterator_ - rhs.iterator_; + } + + reference operator[](const difference_type& n) const + { + throw 13; + return iterator_[n]; + } + + bool operator<(const throwing_iterator& rhs) const + { + return iterator_ < rhs.iterator_; + } + + bool operator>(const throwing_iterator& rhs) const + { + return iterator_ > rhs.iterator_; + } + + bool operator>=(const throwing_iterator& rhs) const + { + return iterator_ >= rhs.iterator_; + } + + bool operator<=(const throwing_iterator& rhs) const + { + return iterator_ <= rhs.iterator_; + } + + private: + mutable Iterator iterator_; +}; + +template +throwing_iterator make_throwing_iterator(const Iterator& iter) +{ + return throwing_iterator(iter); +} + From e01e2772077a57c008982b8740088b432d206b12 Mon Sep 17 00:00:00 2001 From: Jared Hoberock Date: Thu, 31 Jan 2019 16:56:22 -0600 Subject: [PATCH 06/14] Test that fallback works when an execution function throws --- examples/for_each/for_each_n_5.cpp | 97 ++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 examples/for_each/for_each_n_5.cpp diff --git a/examples/for_each/for_each_n_5.cpp b/examples/for_each/for_each_n_5.cpp new file mode 100644 index 0000000..6cd1915 --- /dev/null +++ b/examples/for_each/for_each_n_5.cpp @@ -0,0 +1,97 @@ +#include +#include "for_each_n.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" +#include +#include +#include +#include + + +namespace execution = std::experimental::execution; + + +// creating EAs with this executor always fails via exception +class throwing_executor +{ + public: + friend bool operator==(const throwing_executor&, const throwing_executor&) noexcept + { + return true; + } + + friend bool operator!=(const throwing_executor&, const throwing_executor&) noexcept + { + return false; + } + + throwing_executor require(execution::oneway_t) const + { + return *this; + } + + throwing_executor require(execution::bulk_t) const + { + return *this; + } + + constexpr static execution::bulk_guarantee_t::parallel_t query(execution::bulk_guarantee_t) + { + return execution::bulk_guarantee.parallel; + } + + constexpr static execution::blocking_t::always_t query(execution::blocking_t) + { + return execution::blocking.always; + } + + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) + { + return execution::mapping.thread; + } + + template + void bulk_execute(Function f, size_t n, Factory shared_factory) const + { + throw 13; + } +}; + + +int main() +{ + { + // test with throwing_executor, vector + + std::vector vec(10); + + std::experimental::static_thread_pool pool(4); + + for_each_n(par.on(throwing_executor()), vec.begin(), vec.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } + + { + // test with throwing_executor, list + + std::list lst(10); + + std::experimental::static_thread_pool pool(4); + + for_each_n(par.on(throwing_executor()), lst.begin(), lst.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); + } + + std::cout << "OK" << std::endl; + + return 0; +} + From 6001166624671126d83c150051e3a4d5e2283982 Mon Sep 17 00:00:00 2001 From: Jared Hoberock Date: Tue, 5 Feb 2019 15:58:36 -0600 Subject: [PATCH 07/14] Introduce initial example implementation of reduce --- examples/CMakeLists.txt | 1 + examples/reduce/CMakeLists.txt | 1 + examples/reduce/noexcept_function.hpp | 51 ++++++ examples/reduce/noexcept_iterator.hpp | 169 ++++++++++++++++++++ examples/reduce/par.hpp | 92 +++++++++++ examples/reduce/query_or.hpp | 23 +++ examples/reduce/reduce.hpp | 213 ++++++++++++++++++++++++++ examples/reduce/reduce_1.cpp | 104 +++++++++++++ 8 files changed, 654 insertions(+) create mode 100644 examples/reduce/CMakeLists.txt create mode 100644 examples/reduce/noexcept_function.hpp create mode 100644 examples/reduce/noexcept_iterator.hpp create mode 100644 examples/reduce/par.hpp create mode 100644 examples/reduce/query_or.hpp create mode 100644 examples/reduce/reduce.hpp create mode 100644 examples/reduce/reduce_1.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 9d2b3b3..143177b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -15,3 +15,4 @@ add_subdirectory(invoke) add_subdirectory(nested) add_subdirectory(simple) add_subdirectory(pipeline) +add_subdirectory(reduce) diff --git a/examples/reduce/CMakeLists.txt b/examples/reduce/CMakeLists.txt new file mode 100644 index 0000000..9f4f6fd --- /dev/null +++ b/examples/reduce/CMakeLists.txt @@ -0,0 +1 @@ +executors_add_example(reduce_1) diff --git a/examples/reduce/noexcept_function.hpp b/examples/reduce/noexcept_function.hpp new file mode 100644 index 0000000..5833ded --- /dev/null +++ b/examples/reduce/noexcept_function.hpp @@ -0,0 +1,51 @@ +#pragma + +#include +#include + +namespace impl +{ + + +template +class noexcept_function +{ + public: + template + >> + noexcept_function(OtherFunction&& function) noexcept + : function_(std::forward(function)) + {} + + noexcept_function(const noexcept_function& other) noexcept + : function_(other.function_) + {} + + noexcept_function(noexcept_function&& other) noexcept + : function_(std::move(other.function_)) + {} + + ~noexcept_function() noexcept + {} + + template + auto operator()(Args&&... args) const noexcept + { + return function_(std::forward(args)...); + } + + private: + mutable Function function_; +}; + +template +noexcept_function> make_noexcept_function(Function&& f) +{ + return {std::forward(f)}; +} + + +} // end impl + diff --git a/examples/reduce/noexcept_iterator.hpp b/examples/reduce/noexcept_iterator.hpp new file mode 100644 index 0000000..2785fc5 --- /dev/null +++ b/examples/reduce/noexcept_iterator.hpp @@ -0,0 +1,169 @@ +#pragma once + +#include +#include +#include + + +namespace impl +{ + + +template +class noexcept_iterator +{ + public: + template + >> + explicit noexcept_iterator(OtherIterator&& iterator) noexcept + : iterator_(std::forward(iterator)) + {} + + // CopyConstructible requirements + noexcept_iterator(const noexcept_iterator& other) noexcept + : iterator_(other.iterator_) + {} + + // CopyAssignable requirements + noexcept_iterator& operator=(const noexcept_iterator& other) noexcept + { + iterator_ = other.iterator_; + return *this; + } + + // Destructible requirements + ~noexcept_iterator() noexcept + {} + + // Swappable requirements + void swap(const noexcept_iterator& other) noexcept + { + using std::swap; + swap(iterator_, other.iterator_); + } + + // LegacyIterator requirements + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using reference = typename std::iterator_traits::reference; + using pointer = typename std::iterator_traits::pointer; + using iterator_category = typename std::iterator_traits::iterator_category; + + noexcept_iterator& operator++() noexcept + { + ++iterator_; + return *this; + } + + // LegacyInputIterator requirements + bool operator!=(const noexcept_iterator& other) const noexcept + { + return iterator_ != other.iterator_; + } + + reference operator*() const noexcept + { + return *iterator_; + } + + pointer operator->() const noexcept + { + // XXX this doesn't seem right because it wouldn't work if Iterator is a raw pointer + return iterator_.operator->(); + } + + noexcept_iterator operator++(int) const noexcept + { + noexcept_iterator result(*this); + operator++(); + return result; + } + + // LegacyBidirectionalIterator requirements + noexcept_iterator& operator--() noexcept + { + --iterator_; + return *this; + } + + noexcept_iterator operator--(int) const noexcept + { + noexcept_iterator result(*this); + operator--(); + return result; + } + + // LegacyRandomAccessIterator requirements + noexcept_iterator& operator+=(const difference_type& n) noexcept + { + iterator_ += n; + return *this; + } + + friend noexcept_iterator operator+(const noexcept_iterator& i, const difference_type& n) noexcept + { + return {i.iterator_ + n}; + } + + friend noexcept_iterator operator+(const difference_type& n, const noexcept_iterator& i) noexcept + { + return {n + i.iterator_}; + } + + noexcept_iterator& operator-=(const difference_type& n) noexcept + { + iterator_ -=n; + return *this; + } + + noexcept_iterator operator-(const difference_type& n) const noexcept + { + return {iterator_ - n}; + } + + difference_type operator-(const noexcept_iterator& rhs) const noexcept + { + return iterator_ - rhs.iterator_; + } + + reference operator[](const difference_type& n) const noexcept + { + return iterator_[n]; + } + + bool operator<(const noexcept_iterator& rhs) const noexcept + { + return iterator_ < rhs.iterator_; + } + + bool operator>(const noexcept_iterator& rhs) const noexcept + { + return iterator_ > rhs.iterator_; + } + + bool operator>=(const noexcept_iterator& rhs) const noexcept + { + return iterator_ >= rhs.iterator_; + } + + bool operator<=(const noexcept_iterator& rhs) const noexcept + { + return iterator_ <= rhs.iterator_; + } + + private: + mutable Iterator iterator_; +}; + + +template +noexcept_iterator> make_noexcept_iterator(Iterator&& iter) +{ + return noexcept_iterator>{std::forward(iter)}; +} + + +} // end impl + diff --git a/examples/reduce/par.hpp b/examples/reduce/par.hpp new file mode 100644 index 0000000..658eb83 --- /dev/null +++ b/examples/reduce/par.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include +#include +#include +#include + + +namespace impl +{ + + +template class basic_execution_policy; + + +template +basic_execution_policy make_basic_execution_policy(const E& ex); + + +template +class basic_execution_policy +{ + public: + static constexpr ExecutionRequirement execution_requirement{}; + + Executor executor() const + { + return executor_; + } + + private: + template + friend basic_execution_policy make_basic_execution_policy(const E& ex); + + basic_execution_policy(const Executor& executor) + : executor_(executor) + {} + + Executor executor_; +}; + + +template +basic_execution_policy make_basic_execution_policy(const E& ex) +{ + return basic_execution_policy{ex}; +} + + +template +constexpr bool satisfies_cpp20_on_requirements_v = + std::experimental::execution::can_require_v< + Executor + , ExecutionRequirement + , std::experimental::execution::bulk_t + , std::experimental::execution::oneway_t + , std::experimental::execution::blocking_t::always_t + , std::experimental::execution::mapping_t::thread_t +>; + + +} // end impl + + +class parallel_policy +{ + public: + static constexpr std::experimental::execution::bulk_guarantee_t::parallel_t execution_requirement{}; + + template + >> + impl::basic_execution_policy on(const Executor& ex) const + { + return impl::make_basic_execution_policy(ex); + } +}; + + +constexpr parallel_policy par{}; + + +namespace impl +{ + + +std::experimental::static_thread_pool system_thread_pool{std::max(1u,std::thread::hardware_concurrency())}; + + +} // end impl + diff --git a/examples/reduce/query_or.hpp b/examples/reduce/query_or.hpp new file mode 100644 index 0000000..2887450 --- /dev/null +++ b/examples/reduce/query_or.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +template + >> +auto query_or(const T& query_me, const Property& prop, Default&&) +{ + return std::experimental::execution::query(query_me, prop); +} + + +template + >> +Default&& query_or(const T&, const Property&, Default&& result) +{ + return std::forward(result); +} diff --git a/examples/reduce/reduce.hpp b/examples/reduce/reduce.hpp new file mode 100644 index 0000000..4fe30d3 --- /dev/null +++ b/examples/reduce/reduce.hpp @@ -0,0 +1,213 @@ +#pragma once + +#include +#include "par.hpp" +#include "query_or.hpp" +#include "noexcept_function.hpp" +#include "noexcept_iterator.hpp" +#include +#include +#include +#include +#include +#include +#include + + +namespace impl +{ + + +template +struct reduce_state +{ + reduce_state(std::size_t num_partial_sums) + : num_outstanding(num_partial_sums), + sums(num_partial_sums) + {} + + std::atomic num_outstanding; + + // XXX we should just make this a vector of optionals + std::vector sums; +}; + + +template +T reduce(std::random_access_iterator_tag, ExecutionPolicy&& policy, RandomAccessIterator first, RandomAccessIterator last, T init, BinaryOperation binary_op) +{ + std::optional result; + + try + { + namespace execution = std::experimental::execution; + + // enforce requirements + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always); + + // measure the size of the range + size_t n = last - first; + + // choose a number of subranges + size_t num_subranges = std::min(n, query_or(ex, execution::occupancy, 1)); + + // how large should each subrange be? + size_t subrange_size = (n + num_subranges - 1) / num_subranges; + + // deduce the type of each partial sum + using partial_sum_type = std::invoke_result_t::reference, typename std::iterator_traits::reference>; + + // create agents + ex.bulk_execute( + [=,&result,&init](size_t subrange_idx, reduce_state& shared) + { + // find the bounds of the subrange + RandomAccessIterator subrange_begin = first + subrange_idx * subrange_size; + RandomAccessIterator subrange_end = std::min(last, subrange_begin + subrange_size); + + // reduce the subrange to produce a partial sum + shared.sums[subrange_idx] = std::accumulate(subrange_begin + 1, subrange_end, *subrange_begin, binary_op); + + // the final agent reduces partial sums + if(--shared.num_outstanding == 0) + { + result = std::accumulate(shared.sums.begin(), shared.sums.end(), init, binary_op); + } + }, + num_subranges, + [=] + { + return reduce_state(num_subranges); + } + ); + } + catch(...) + { + // sequential fallback + result = std::accumulate(first, last, init, binary_op); + } + + return result.value(); +} + + +template +std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) +{ + // how large should each subrange be? + std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; + + // store an iterator pointing to the beginning of each subrange + // the final element of this vector points is first + n + std::vector result; + result.reserve(num_subranges + 1); + + Size subrange_idx = 0; + for(Size i = 0; i < n; i += subrange_size, ++subrange_idx) + { + // append the beginning of the subrange + result.push_back(first); + + // are we at the last subrange? + std::size_t distance = (subrange_idx == num_subranges - 1) ? (n - i) : subrange_size; + + std::advance(first, distance); + } + + // finally, add the end of the range + result.push_back(first); + + return result; +} + + +template +struct partitions_and_reduce_state +{ + partitions_and_reduce_state(std::vector&& partitions) + : partitions_begin(std::move(partitions)), + state(partitions_begin.size() - 1) + {} + + std::vector partitions_begin; + reduce_state state; +}; + + +template +T reduce(std::forward_iterator_tag, ExecutionPolicy&& policy, ForwardIterator first, ForwardIterator last, T init, BinaryOperation binary_op) +{ + std::optional result; + + try + { + namespace execution = std::experimental::execution; + + // enforce requirements + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always); + + // measure the size of the range + size_t n = std::distance(first, last); + + // choose a number of subranges + size_t num_subranges = std::min(n, query_or(ex, execution::occupancy, 1)); + + // deduce the type of each partial sum + using partial_sum_type = std::invoke_result_t< + BinaryOperation, + typename std::iterator_traits::reference, + typename std::iterator_traits::reference + >; + + // create agents + ex.bulk_execute( + [=,&result,&init](size_t subrange_idx, partitions_and_reduce_state& shared) + { + // find the bounds of the subrange + ForwardIterator subrange_begin = shared.partitions_begin[subrange_idx]; + ForwardIterator subrange_init = subrange_begin++; + ForwardIterator subrange_end = shared.partitions_begin[subrange_idx + 1]; + + // reduce the subrange to produce a partial sum + shared.state.sums[subrange_idx] = std::accumulate(subrange_begin, subrange_end, *subrange_init, binary_op); + + // the final agent reduces partial sums + if(--shared.state.num_outstanding == 0) + { + result = std::accumulate(shared.state.sums.begin(), shared.state.sums.end(), init, binary_op); + } + }, + num_subranges, + [=] + { + // construct shared state for reduction + return partitions_and_reduce_state(partition_into_subranges(first, n, num_subranges)); + } + ); + } + catch(...) + { + // sequential fallback + result = std::accumulate(first, last, init, binary_op); + } + + return result.value(); +} + + +} // end impl + + +template +T reduce(ExecutionPolicy&& policy, ForwardIterator first, ForwardIterator last, T init, BinaryOperation binary_op) +{ + return impl::reduce(typename std::iterator_traits::iterator_category(), std::forward(policy), first, last, init, binary_op); +} + + +template +T reduce(const parallel_policy&, ForwardIterator first, ForwardIterator last, T init, BinaryOperation binary_op) +{ + return ::reduce(par.on(impl::system_thread_pool.executor()), first, last, init, binary_op); +} + diff --git a/examples/reduce/reduce_1.cpp b/examples/reduce/reduce_1.cpp new file mode 100644 index 0000000..12f8886 --- /dev/null +++ b/examples/reduce/reduce_1.cpp @@ -0,0 +1,104 @@ +#include "reduce.hpp" +#include "par.hpp" +#include +#include +#include +#include +#include +#include +#include + +int main() +{ + { + // test with par, vector + + std::vector vec(10); + std::iota(vec.begin(), vec.end(), 1); + + int result = reduce(par, vec.begin(), vec.end(), 13, std::plus<>()); + + assert(std::accumulate(vec.begin(), vec.end(), 13) == result); + } + + { + // test with par, list + + std::list lst(10); + std::iota(lst.begin(), lst.end(), 1); + + int result = reduce(par, lst.begin(), lst.end(), 13, std::plus<>()); + + assert(std::accumulate(lst.begin(), lst.end(), 13) == result); + } + +// // XXX the following tests correctly call std::terminate upon +// // exception in an element access function +// // they are disabled to allow the test program to complete normally +// +// //{ +// // // test with par, vector, throwing function +// +// // std::vector vec(10); +// +// // for_each_n(par, vec.begin(), vec.size(), [](int& x) +// // { +// // x = 42; +// +// // throw 13; +// // }); +// +// // // this line should never be reached +// // assert(false); +// //} +// +// //{ +// // // test with par, list, throwing function +// +// // std::list lst(10); +// +// // for_each_n(par, lst.begin(), lst.size(), [](int& x) +// // { +// // x = 42; +// +// // throw 13; +// // }); +// +// // // this line should never be reached +// // assert(false); +// //} +// +// //{ +// // // test with par, vector, throwing iterator +// +// // std::vector vec(10); +// +// // for_each_n(par, make_throwing_iterator(vec.begin()), vec.size(), [](int& x) +// // { +// // x = 42; +// // }); +// +// // // this line should never be reached +// // assert(false); +// //} +// +// //{ +// // // test with par, list, throwing iterator +// +// // std::list lst(10); +// +// // for_each_n(par, make_throwing_iterator(lst.begin()), lst.size(), [](int& x) +// // { +// // x = 42; +// // }); +// +// // // this line should never be reached +// // assert(false); +// //} + + std::cout << "OK" << std::endl; + + return 0; +} + + From 16f3ae46591a2c4f485f4352d2f64b3d28ffc883 Mon Sep 17 00:00:00 2001 From: Jared Hoberock Date: Tue, 5 Feb 2019 16:05:40 -0600 Subject: [PATCH 08/14] Flesh out exception handling tests in reduce_1.cpp --- examples/reduce/reduce_1.cpp | 125 +++++++++++----------- examples/reduce/throwing_iterator.hpp | 145 ++++++++++++++++++++++++++ 2 files changed, 207 insertions(+), 63 deletions(-) create mode 100644 examples/reduce/throwing_iterator.hpp diff --git a/examples/reduce/reduce_1.cpp b/examples/reduce/reduce_1.cpp index 12f8886..f1d2eaa 100644 --- a/examples/reduce/reduce_1.cpp +++ b/examples/reduce/reduce_1.cpp @@ -1,5 +1,6 @@ #include "reduce.hpp" #include "par.hpp" +#include "throwing_iterator.hpp" #include #include #include @@ -32,69 +33,67 @@ int main() assert(std::accumulate(lst.begin(), lst.end(), 13) == result); } -// // XXX the following tests correctly call std::terminate upon -// // exception in an element access function -// // they are disabled to allow the test program to complete normally -// -// //{ -// // // test with par, vector, throwing function -// -// // std::vector vec(10); -// -// // for_each_n(par, vec.begin(), vec.size(), [](int& x) -// // { -// // x = 42; -// -// // throw 13; -// // }); -// -// // // this line should never be reached -// // assert(false); -// //} -// -// //{ -// // // test with par, list, throwing function -// -// // std::list lst(10); -// -// // for_each_n(par, lst.begin(), lst.size(), [](int& x) -// // { -// // x = 42; -// -// // throw 13; -// // }); -// -// // // this line should never be reached -// // assert(false); -// //} -// -// //{ -// // // test with par, vector, throwing iterator -// -// // std::vector vec(10); -// -// // for_each_n(par, make_throwing_iterator(vec.begin()), vec.size(), [](int& x) -// // { -// // x = 42; -// // }); -// -// // // this line should never be reached -// // assert(false); -// //} -// -// //{ -// // // test with par, list, throwing iterator -// -// // std::list lst(10); -// -// // for_each_n(par, make_throwing_iterator(lst.begin()), lst.size(), [](int& x) -// // { -// // x = 42; -// // }); -// -// // // this line should never be reached -// // assert(false); -// //} + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with par, vector, throwing function + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // reduce(par, vec.begin(), vec.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing function + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // reduce(par, lst.begin(), lst.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, vector, throwing iterator + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // reduce(par, make_throwing_iterator(vec.begin()), make_throwing_iterator(vec.end()), 13, std::plus<>()); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing iterator + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // reduce(par, make_throwing_iterator(lst.begin()), make_throwing_iterator(lst.end()), 13, std::plus<>()); + + // // this line should never be reached + // assert(false); + //} std::cout << "OK" << std::endl; diff --git a/examples/reduce/throwing_iterator.hpp b/examples/reduce/throwing_iterator.hpp new file mode 100644 index 0000000..b991a47 --- /dev/null +++ b/examples/reduce/throwing_iterator.hpp @@ -0,0 +1,145 @@ +#pragma once + +#include +#include +#include + + +template +class throwing_iterator +{ + public: + template + >> + explicit throwing_iterator(OtherIterator&& iterator) + : iterator_(std::forward(iterator)) + {} + + // CopyConstructible requirements + throwing_iterator(const throwing_iterator& other) + : iterator_(other.iterator_) + {} + + // LegacyIterator requirements + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using reference = typename std::iterator_traits::reference; + using pointer = typename std::iterator_traits::pointer; + using iterator_category = typename std::iterator_traits::iterator_category; + + throwing_iterator& operator++() + { + ++iterator_; + return *this; + } + + // LegacyInputIterator requirements + bool operator!=(const throwing_iterator& other) const + { + return iterator_ != other.iterator_; + } + + reference operator*() const + { + throw 13; + return *iterator_; + } + + pointer operator->() const + { + // XXX this doesn't seem right because it wouldn't work if Iterator is a raw pointer + return iterator_.operator->(); + } + + throwing_iterator operator++(int) + { + throwing_iterator result(*this); + operator++(); + return result; + } + + // LegacyBidirectionalIterator requirements + throwing_iterator& operator--() + { + --iterator_; + return *this; + } + + throwing_iterator operator--(int) + { + throwing_iterator result(*this); + operator--(); + return result; + } + + // LegacyRandomAccessIterator requirements + throwing_iterator& operator+=(const difference_type& n) + { + iterator_ += n; + return *this; + } + + friend throwing_iterator operator+(const throwing_iterator& i, const difference_type& n) + { + return throwing_iterator{i.iterator_ + n}; + } + + friend throwing_iterator operator+(const difference_type& n, const throwing_iterator& i) + { + return {n + i.iterator_}; + } + + throwing_iterator& operator-=(const difference_type& n) + { + iterator_ -=n; + return *this; + } + + throwing_iterator operator-(const difference_type& n) const + { + return {iterator_ - n}; + } + + difference_type operator-(const throwing_iterator& rhs) const + { + return iterator_ - rhs.iterator_; + } + + reference operator[](const difference_type& n) const + { + throw 13; + return iterator_[n]; + } + + bool operator<(const throwing_iterator& rhs) const + { + return iterator_ < rhs.iterator_; + } + + bool operator>(const throwing_iterator& rhs) const + { + return iterator_ > rhs.iterator_; + } + + bool operator>=(const throwing_iterator& rhs) const + { + return iterator_ >= rhs.iterator_; + } + + bool operator<=(const throwing_iterator& rhs) const + { + return iterator_ <= rhs.iterator_; + } + + private: + mutable Iterator iterator_; +}; + +template +throwing_iterator make_throwing_iterator(const Iterator& iter) +{ + return throwing_iterator(iter); +} + From f391b517903650daa5717e434828c7c68854f087 Mon Sep 17 00:00:00 2001 From: Jared Hoberock Date: Tue, 5 Feb 2019 16:15:27 -0600 Subject: [PATCH 09/14] Test reduce with on(static_thread_pool.executor()) --- examples/reduce/reduce_2.cpp | 116 +++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 examples/reduce/reduce_2.cpp diff --git a/examples/reduce/reduce_2.cpp b/examples/reduce/reduce_2.cpp new file mode 100644 index 0000000..08f2b52 --- /dev/null +++ b/examples/reduce/reduce_2.cpp @@ -0,0 +1,116 @@ +#include "reduce.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +int main() +{ + { + // test with par, vector + + std::vector vec(10); + std::iota(vec.begin(), vec.end(), 1); + + std::experimental::static_thread_pool pool(4); + + int result = reduce(par.on(pool.executor()), vec.begin(), vec.end(), 13, std::plus<>()); + + assert(std::accumulate(vec.begin(), vec.end(), 13) == result); + } + + { + // test with par, list + + std::list lst(10); + std::iota(lst.begin(), lst.end(), 1); + + std::experimental::static_thread_pool pool(4); + + int result = reduce(par.on(pool.executor()), lst.begin(), lst.end(), 13, std::plus<>()); + + assert(std::accumulate(lst.begin(), lst.end(), 13) == result); + } + + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with par, vector, throwing function + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // std::experimental::static_thread_pool pool(4); + + // reduce(par.on(pool.executor()), vec.begin(), vec.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing function + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // std::experimental::static_thread_pool pool(4); + + // reduce(par.on(pool.executor()), lst.begin(), lst.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, vector, throwing iterator + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // std::experimental::static_thread_pool pool(4); + + // reduce(par.on(pool.executor()), make_throwing_iterator(vec.begin()), make_throwing_iterator(vec.end()), 13, std::plus<>()); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing iterator + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // std::experimental::static_thread_pool pool(4); + + // reduce(par.on(pool.executor()), make_throwing_iterator(lst.begin()), make_throwing_iterator(lst.end()), 13, std::plus<>()); + + // // this line should never be reached + // assert(false); + //} + + std::cout << "OK" << std::endl; + + return 0; +} + + From 61391776240f18373ed58b587665770d4e09b7b0 Mon Sep 17 00:00:00 2001 From: Jared Hoberock Date: Tue, 5 Feb 2019 16:30:48 -0600 Subject: [PATCH 10/14] Allow moves from reduce_state --- examples/reduce/reduce.hpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/examples/reduce/reduce.hpp b/examples/reduce/reduce.hpp index 4fe30d3..41a98a9 100644 --- a/examples/reduce/reduce.hpp +++ b/examples/reduce/reduce.hpp @@ -26,9 +26,15 @@ struct reduce_state sums(num_partial_sums) {} + // XXX implement a move ctor to allow std::make_shared>(shared_factory()) to work + reduce_state(reduce_state&& other) + : num_outstanding(other.sums.size()), + sums(std::move(other.sums)) + {} + std::atomic num_outstanding; - // XXX we should just make this a vector of optionals + // XXX we should just make this a vector of optionals to avoid requiring DefaultConstructible T std::vector sums; }; @@ -49,7 +55,7 @@ T reduce(std::random_access_iterator_tag, ExecutionPolicy&& policy, RandomAccess size_t n = last - first; // choose a number of subranges - size_t num_subranges = std::min(n, query_or(ex, execution::occupancy, 1)); + size_t num_subranges = std::min(n, query_or(ex, execution::occupancy, size_t(1))); // how large should each subrange be? size_t subrange_size = (n + num_subranges - 1) / num_subranges; @@ -129,6 +135,11 @@ struct partitions_and_reduce_state state(partitions_begin.size() - 1) {} + partitions_and_reduce_state(partitions_and_reduce_state&& other) + : partitions_begin(std::move(other.partitions_begin)), + state(std::move(other.state)) + {} + std::vector partitions_begin; reduce_state state; }; @@ -150,7 +161,7 @@ T reduce(std::forward_iterator_tag, ExecutionPolicy&& policy, ForwardIterator fi size_t n = std::distance(first, last); // choose a number of subranges - size_t num_subranges = std::min(n, query_or(ex, execution::occupancy, 1)); + size_t num_subranges = std::min(n, query_or(ex, execution::occupancy, size_t(1))); // deduce the type of each partial sum using partial_sum_type = std::invoke_result_t< From 22f1a27ee7ea438d5d37cc7a6d6f90312c563ec7 Mon Sep 17 00:00:00 2001 From: Jared Hoberock Date: Tue, 5 Feb 2019 16:31:00 -0600 Subject: [PATCH 11/14] Test reduce with inline_executor --- examples/reduce/reduce_3.cpp | 164 +++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 examples/reduce/reduce_3.cpp diff --git a/examples/reduce/reduce_3.cpp b/examples/reduce/reduce_3.cpp new file mode 100644 index 0000000..a65e579 --- /dev/null +++ b/examples/reduce/reduce_3.cpp @@ -0,0 +1,164 @@ +#include "reduce.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace execution = std::experimental::execution; + + +class inline_executor +{ + public: + friend bool operator==(const inline_executor&, const inline_executor&) noexcept + { + return true; + } + + friend bool operator!=(const inline_executor&, const inline_executor&) noexcept + { + return false; + } + + inline_executor require(execution::oneway_t) const + { + return *this; + } + + constexpr static execution::bulk_guarantee_t::parallel_t query(execution::bulk_guarantee_t) + { + return execution::bulk_guarantee.parallel; + } + + constexpr static execution::blocking_t::always_t query(execution::blocking_t) + { + return execution::blocking.always; + } + + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) + { + return execution::mapping.thread; + } + + template + void execute(Function f) const noexcept + { + f(); + } +}; + +int main() +{ + { + // test with par, vector + + std::vector vec(10); + std::iota(vec.begin(), vec.end(), 1); + + int result = reduce(par.on(inline_executor()), vec.begin(), vec.end(), 13, std::plus<>()); + + assert(std::accumulate(vec.begin(), vec.end(), 13) == result); + } + + { + // test with par, list + + std::list lst(10); + std::iota(lst.begin(), lst.end(), 1); + + int result = reduce(par.on(inline_executor()), lst.begin(), lst.end(), 13, std::plus<>()); + + assert(std::accumulate(lst.begin(), lst.end(), 13) == result); + } + + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with par, vector, throwing function + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // try + // { + // reduce(par.on(inline_executor()), vec.begin(), vec.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing function + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // try + // { + // reduce(par.on(inline_executor()), lst.begin(), lst.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, vector, throwing iterator + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // try + // { + // reduce(par.on(inline_executor()), make_throwing_iterator(vec.begin()), make_throwing_iterator(vec.end()), 13, std::plus<>()); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing iterator + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // try + // { + // reduce(par.on(inline_executor()), make_throwing_iterator(lst.begin()), make_throwing_iterator(lst.end()), 13, std::plus<>()); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + std::cout << "OK" << std::endl; + + return 0; +} + + From 13b2713eadc4b0fb60378c99137d8fc45d8353ad Mon Sep 17 00:00:00 2001 From: Jared Hoberock Date: Tue, 5 Feb 2019 16:33:23 -0600 Subject: [PATCH 12/14] Test reduce with bulk_inline_executor --- examples/reduce/reduce_4.cpp | 174 +++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 examples/reduce/reduce_4.cpp diff --git a/examples/reduce/reduce_4.cpp b/examples/reduce/reduce_4.cpp new file mode 100644 index 0000000..93c4f44 --- /dev/null +++ b/examples/reduce/reduce_4.cpp @@ -0,0 +1,174 @@ +#include "reduce.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace execution = std::experimental::execution; + + +class bulk_inline_executor +{ + public: + friend bool operator==(const bulk_inline_executor&, const bulk_inline_executor&) noexcept + { + return true; + } + + friend bool operator!=(const bulk_inline_executor&, const bulk_inline_executor&) noexcept + { + return false; + } + + bulk_inline_executor require(execution::oneway_t) const + { + return *this; + } + + bulk_inline_executor require(execution::bulk_t) const + { + return *this; + } + + constexpr static execution::bulk_guarantee_t::parallel_t query(execution::bulk_guarantee_t) + { + return execution::bulk_guarantee.parallel; + } + + constexpr static execution::blocking_t::always_t query(execution::blocking_t) + { + return execution::blocking.always; + } + + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) + { + return execution::mapping.thread; + } + + template + void bulk_execute(Function f, size_t n, Factory shared_factory) const noexcept + { + auto shared = shared_factory(); + + for(size_t i = 0; i < n; ++i) + { + f(i, shared); + } + } +}; + +int main() +{ + { + // test with par, vector + + std::vector vec(10); + std::iota(vec.begin(), vec.end(), 1); + + int result = reduce(par.on(bulk_inline_executor()), vec.begin(), vec.end(), 13, std::plus<>()); + + assert(std::accumulate(vec.begin(), vec.end(), 13) == result); + } + + { + // test with par, list + + std::list lst(10); + std::iota(lst.begin(), lst.end(), 1); + + int result = reduce(par.on(bulk_inline_executor()), lst.begin(), lst.end(), 13, std::plus<>()); + + assert(std::accumulate(lst.begin(), lst.end(), 13) == result); + } + + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with par, vector, throwing function + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // try + // { + // reduce(par.on(bulk_inline_executor()), vec.begin(), vec.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing function + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // try + // { + // reduce(par.on(bulk_inline_executor()), lst.begin(), lst.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, vector, throwing iterator + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // try + // { + // reduce(par.on(bulk_inline_executor()), make_throwing_iterator(vec.begin()), make_throwing_iterator(vec.end()), 13, std::plus<>()); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing iterator + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // try + // { + // reduce(par.on(bulk_inline_executor()), make_throwing_iterator(lst.begin()), make_throwing_iterator(lst.end()), 13, std::plus<>()); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + std::cout << "OK" << std::endl; + + return 0; +} + + From 42272215f3c02e6718aaee2ae1176e1e2685216d Mon Sep 17 00:00:00 2001 From: Jared Hoberock Date: Tue, 5 Feb 2019 16:43:37 -0600 Subject: [PATCH 13/14] Wrap user types in noexcept wrappers in reduce Test reduce with throwing_executor --- examples/reduce/noexcept_iterator.hpp | 8 +- examples/reduce/reduce.hpp | 2 +- examples/reduce/reduce_5.cpp | 170 ++++++++++++++++++++++++++ 3 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 examples/reduce/reduce_5.cpp diff --git a/examples/reduce/noexcept_iterator.hpp b/examples/reduce/noexcept_iterator.hpp index 2785fc5..53ecb40 100644 --- a/examples/reduce/noexcept_iterator.hpp +++ b/examples/reduce/noexcept_iterator.hpp @@ -74,7 +74,7 @@ class noexcept_iterator return iterator_.operator->(); } - noexcept_iterator operator++(int) const noexcept + noexcept_iterator operator++(int) noexcept { noexcept_iterator result(*this); operator++(); @@ -88,7 +88,7 @@ class noexcept_iterator return *this; } - noexcept_iterator operator--(int) const noexcept + noexcept_iterator operator--(int) noexcept { noexcept_iterator result(*this); operator--(); @@ -104,12 +104,12 @@ class noexcept_iterator friend noexcept_iterator operator+(const noexcept_iterator& i, const difference_type& n) noexcept { - return {i.iterator_ + n}; + return noexcept_iterator{i.iterator_ + n}; } friend noexcept_iterator operator+(const difference_type& n, const noexcept_iterator& i) noexcept { - return {n + i.iterator_}; + return noexcept_iterator{n + i.iterator_}; } noexcept_iterator& operator-=(const difference_type& n) noexcept diff --git a/examples/reduce/reduce.hpp b/examples/reduce/reduce.hpp index 41a98a9..a968766 100644 --- a/examples/reduce/reduce.hpp +++ b/examples/reduce/reduce.hpp @@ -212,7 +212,7 @@ T reduce(std::forward_iterator_tag, ExecutionPolicy&& policy, ForwardIterator fi template T reduce(ExecutionPolicy&& policy, ForwardIterator first, ForwardIterator last, T init, BinaryOperation binary_op) { - return impl::reduce(typename std::iterator_traits::iterator_category(), std::forward(policy), first, last, init, binary_op); + return impl::reduce(typename std::iterator_traits::iterator_category(), std::forward(policy), impl::make_noexcept_iterator(first), impl::make_noexcept_iterator(last), init, impl::make_noexcept_function(binary_op)); } diff --git a/examples/reduce/reduce_5.cpp b/examples/reduce/reduce_5.cpp new file mode 100644 index 0000000..d412b40 --- /dev/null +++ b/examples/reduce/reduce_5.cpp @@ -0,0 +1,170 @@ +#include "reduce.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace execution = std::experimental::execution; + + +// creating EAs with this executor always fails via exception +class throwing_executor +{ + public: + friend bool operator==(const throwing_executor&, const throwing_executor&) noexcept + { + return true; + } + + friend bool operator!=(const throwing_executor&, const throwing_executor&) noexcept + { + return false; + } + + throwing_executor require(execution::oneway_t) const + { + return *this; + } + + throwing_executor require(execution::bulk_t) const + { + return *this; + } + + constexpr static execution::bulk_guarantee_t::parallel_t query(execution::bulk_guarantee_t) + { + return execution::bulk_guarantee.parallel; + } + + constexpr static execution::blocking_t::always_t query(execution::blocking_t) + { + return execution::blocking.always; + } + + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) + { + return execution::mapping.thread; + } + + template + void bulk_execute(Function f, size_t n, Factory shared_factory) const + { + throw -1; + } +}; + +int main() +{ + { + // test with par, vector + + std::vector vec(10); + std::iota(vec.begin(), vec.end(), 1); + + int result = reduce(par.on(throwing_executor()), vec.begin(), vec.end(), 13, std::plus<>()); + + assert(std::accumulate(vec.begin(), vec.end(), 13) == result); + } + + { + // test with par, list + + std::list lst(10); + std::iota(lst.begin(), lst.end(), 1); + + int result = reduce(par.on(throwing_executor()), lst.begin(), lst.end(), 13, std::plus<>()); + + assert(std::accumulate(lst.begin(), lst.end(), 13) == result); + } + + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with par, vector, throwing function + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // try + // { + // reduce(par.on(throwing_executor()), vec.begin(), vec.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing function + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // try + // { + // reduce(par.on(throwing_executor()), lst.begin(), lst.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, vector, throwing iterator + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // try + // { + // reduce(par.on(throwing_executor()), make_throwing_iterator(vec.begin()), make_throwing_iterator(vec.end()), 13, std::plus<>()); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing iterator + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // try + // { + // reduce(par.on(throwing_executor()), make_throwing_iterator(lst.begin()), make_throwing_iterator(lst.end()), 13, std::plus<>()); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + std::cout << "OK" << std::endl; + + return 0; +} + + From 2696f47c7c64f6cbb7181115e073501d3f57e112 Mon Sep 17 00:00:00 2001 From: Jared Hoberock Date: Fri, 8 Feb 2019 14:46:17 -0600 Subject: [PATCH 14/14] Add WIP example of a bulk_oneway_executor_adaptor + for_each_n --- examples/for_each/for_each_n_6.cpp | 170 +++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 examples/for_each/for_each_n_6.cpp diff --git a/examples/for_each/for_each_n_6.cpp b/examples/for_each/for_each_n_6.cpp new file mode 100644 index 0000000..4c19748 --- /dev/null +++ b/examples/for_each/for_each_n_6.cpp @@ -0,0 +1,170 @@ +#include +#include +#include +#include +#include +#include +#include "par.hpp" +#include "for_each_n.hpp" + +namespace execution = std::experimental::execution; + +template +class bulk_oneway_executor_adaptor +{ + public: + bulk_oneway_executor_adaptor(const bulk_oneway_executor_adaptor&) = default; + + explicit bulk_oneway_executor_adaptor(const OneWayExecutor& executor) noexcept + : executor_(executor) + {} + + template + void bulk_execute(Function f, size_t n, Factory shared_factory) const + { + executor_.execute([=] + { + auto shared = shared_factory(); + for(size_t i = 0; i != n; ++i) + { + f(i, shared); + } + }); + } + + bool operator==(const bulk_oneway_executor_adaptor& other) const noexcept + { + return executor_ == other.executor_; + } + + bool operator!=(const bulk_oneway_executor_adaptor& other) const noexcept + { + return executor_ != other.executor_; + } + + bulk_oneway_executor_adaptor require(execution::oneway_t) const + { + return *this; + } + + template + >> + auto require(const Property& prop) const + { + auto adapted_executor = execution::require(executor_, prop); + return bulk_oneway_executor_adaptor{adapted_executor}; + } + + // XXX what is the best way to provide both .query() and ::query()? + //template + // >> + //constexpr auto query(const Property& prop) const + //{ + // return execution::query(executor_, prop); + //} + + //template + // >, + // class = decltype(Property::template static_query_v) + // > + //constexpr static auto query(const Property& prop) + //{ + // return Property::template static_query_v; + //} + + // XXX workaround issues with forwarding queries above + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) noexcept + { + return execution::mapping.thread; + } + + // XXX weaken this guarantee to satisfy .on() + //constexpr static execution::bulk_guarantee_t::sequenced_t query(execution::bulk_guarantee_t) noexcept + //{ + // return execution::bulk_guarantee.sequenced; + //} + constexpr static execution::bulk_guarantee_t::parallel_t query(execution::bulk_guarantee_t) noexcept + { + return execution::bulk_guarantee.parallel; + } + + private: + OneWayExecutor executor_; +}; + + +class inline_executor +{ + public: + friend bool operator==(const inline_executor&, const inline_executor&) noexcept + { + return true; + } + + friend bool operator!=(const inline_executor&, const inline_executor&) noexcept + { + return false; + } + + constexpr static execution::blocking_t::always_t query(execution::blocking_t) + { + return execution::blocking.always; + } + + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) + { + return execution::mapping.thread; + } + + template + void execute(Function f) const noexcept + { + f(); + } +}; + + +int main() +{ + { + // test with adapted inline_executor, vector + + std::vector vec(10); + + bulk_oneway_executor_adaptor ex(inline_executor{}); + + for_each_n(par.on(ex), vec.begin(), vec.size(), [](int& x) + { + x += 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } + + { + // test with adapted static_thread_pool.executor(), vector + + std::vector vec(10); + + std::experimental::static_thread_pool pool(4); + bulk_oneway_executor_adaptor ex(pool.executor()); + + for_each_n(par.on(ex), vec.begin(), vec.size(), [](int& x) + { + x += 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } + + std::cout << "OK" << std::endl; + + return 0; +} +