From 52f143c0331c85c22782fb8738b63a5633b454ab Mon Sep 17 00:00:00 2001 From: Hartmut Kaiser Date: Fri, 2 Jun 2017 12:24:45 -0500 Subject: [PATCH 1/3] Adding parallel::destroy and destroy_n --- docs/CMakeLists.txt | 1 + docs/hpx.idx | 4 + docs/manual/parallel_algorithms.qbk | 10 + hpx/include/parallel_destroy.hpp | 12 + hpx/parallel/algorithms/destroy.hpp | 289 ++++++++++ hpx/parallel/memory.hpp | 3 +- tests/unit/parallel/algorithms/CMakeLists.txt | 2 + tests/unit/parallel/algorithms/destroy.cpp | 148 ++++++ .../parallel/algorithms/destroy_tests.hpp | 357 +++++++++++++ tests/unit/parallel/algorithms/destroyn.cpp | 494 ++++++++++++++++++ tests/unit/parallel/algorithms/test_utils.hpp | 32 +- 11 files changed, 1340 insertions(+), 12 deletions(-) create mode 100644 hpx/include/parallel_destroy.hpp create mode 100644 hpx/parallel/algorithms/destroy.hpp create mode 100644 tests/unit/parallel/algorithms/destroy.cpp create mode 100644 tests/unit/parallel/algorithms/destroy_tests.hpp create mode 100644 tests/unit/parallel/algorithms/destroyn.cpp diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 1e5b2eb4d29f..576a16c5ce4d 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -63,6 +63,7 @@ set(doxygen_dependencies "${PROJECT_SOURCE_DIR}/hpx/parallel/algorithms/all_any_none.hpp" "${PROJECT_SOURCE_DIR}/hpx/parallel/algorithms/copy.hpp" "${PROJECT_SOURCE_DIR}/hpx/parallel/algorithms/count.hpp" + "${PROJECT_SOURCE_DIR}/hpx/parallel/algorithms/destroy.hpp" "${PROJECT_SOURCE_DIR}/hpx/parallel/algorithms/equal.hpp" "${PROJECT_SOURCE_DIR}/hpx/parallel/algorithms/exclusive_scan.hpp" "${PROJECT_SOURCE_DIR}/hpx/parallel/algorithms/fill.hpp" diff --git a/docs/hpx.idx b/docs/hpx.idx index 2e2efe3a4b1a..6d648ae7f17b 100644 --- a/docs/hpx.idx +++ b/docs/hpx.idx @@ -184,6 +184,10 @@ parallel::copy_n "copy_n" "hpx\.parallel\.v1\.copy_n.*" parallel::count "count" "hpx\.parallel\.v1\.count$.*" parallel::count_if "count_if" "hpx\.parallel\.v1\.count_if.*" +# hpx/parallel/algorithms/destroy.hpp +parallel::destroy "destroy" "hpx\.parallel\.v1\.destroy$.*" +parallel::destroy_n "destroy_n" "hpx\.parallel\.v1\.destroy_n.*" + # hpx/parallel/algorithms/equal.hpp parallel::equal "equal" "hpx\.parallel\.v1\.equal_id.*" diff --git a/docs/manual/parallel_algorithms.qbk b/docs/manual/parallel_algorithms.qbk index 98581ce83fde..311f68faa582 100644 --- a/docs/manual/parallel_algorithms.qbk +++ b/docs/manual/parallel_algorithms.qbk @@ -462,6 +462,16 @@ __hpx__ provides implementations of the following parallel algorithms: [table Dynamic Memory Management (In Header: ``) [[Name] [Description] [In Header] [Algorithm page at cppreference.com]] + [[ [algoref destroy] ] + [Destroys a range of objects.] + [``] + [[cpprefmemdocs destroy]] + ] + [[ [algoref destroy_n] ] + [Destroys a range of objects.] + [``] + [[cpprefmemdocs destroy_n]] + ] [[ [algoref uninitialized_copy] ] [Copies a range of objects to an uninitialized area of memory.] [``] diff --git a/hpx/include/parallel_destroy.hpp b/hpx/include/parallel_destroy.hpp new file mode 100644 index 000000000000..4cf58f71fb38 --- /dev/null +++ b/hpx/include/parallel_destroy.hpp @@ -0,0 +1,12 @@ +// Copyright (c) 2007-2017 Hartmut Kaiser +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#if !defined(HPX_PARALLEL_DESTROY_JUN_02_2017_1042AM) +#define HPX_PARALLEL_DESTROY_JUN_02_2017_1042AM + +#include + +#endif + diff --git a/hpx/parallel/algorithms/destroy.hpp b/hpx/parallel/algorithms/destroy.hpp new file mode 100644 index 000000000000..8dc9dabdf212 --- /dev/null +++ b/hpx/parallel/algorithms/destroy.hpp @@ -0,0 +1,289 @@ +// Copyright (c) 2014-2017 Hartmut Kaiser +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +/// \file parallel/algorithms/destroy.hpp + +#if !defined(HPX_PARALLEL_DETAIL_destroy_JUN_01_2017_1049AM) +#define HPX_PARALLEL_DETAIL_destroy_JUN_01_2017_1049AM + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace hpx { namespace parallel { inline namespace v1 +{ + /////////////////////////////////////////////////////////////////////////// + // destroy + namespace detail + { + /// \cond NOINTERNAL + + // provide our own implementation of std::destroy + // as some versions of MSVC horribly fail at compiling it for some types + // T + template + void std_destroy(FwdIter first, FwdIter last) + { + typedef typename std::iterator_traits::value_type + value_type; + + for (/* */; first != last; ++first) + { + std::addressof(*first)->~value_type(); + } + } + + /////////////////////////////////////////////////////////////////////// + template + typename util::detail::algorithm_result::type + parallel_sequential_destroy_n( + ExPolicy && policy, FwdIter first, std::size_t count) + { + if (count == 0) + { + return util::detail::algorithm_result::get( + std::move(first)); + } + + return util::foreach_partitioner::call( + std::forward(policy), first, count, + [](FwdIter first, std::size_t count, std::size_t) + { + typedef typename std::iterator_traits::value_type + value_type; + + return util::loop_n(first, count, + [](FwdIter it) + { + std::addressof(*it)->~value_type(); + }); + }, + util::projection_identity()); + } + + /////////////////////////////////////////////////////////////////////// + template + struct destroy + : public detail::algorithm > + { + destroy() + : destroy::algorithm("destroy") + {} + + template + static hpx::util::unused_type + sequential(ExPolicy, FwdIter first, FwdIter last) + { + std_destroy(first, last); + return hpx::util::unused; + } + + template + static typename util::detail::algorithm_result::type + parallel(ExPolicy && policy, FwdIter first, FwdIter last) + { + return util::detail::algorithm_result::get( + parallel_sequential_destroy_n( + std::forward(policy), first, + std::distance(first, last))); + } + }; + /// \endcond + } + + /// Destroys objects of type typename iterator_traits::value_type + /// in the range [first, last). + /// + /// \note Complexity: Performs exactly \a last - \a first operations. + /// + /// \tparam ExPolicy The type of the execution policy to use (deduced). + /// It describes the manner in which the execution + /// of the algorithm may be parallelized and the manner + /// in which it executes the assignments. + /// \tparam FwdIter The type of the source iterators used (deduced). + /// This iterator type must meet the requirements of an + /// forward iterator. + /// + /// \param policy The execution policy to use for the scheduling of + /// the iterations. + /// \param first Refers to the beginning of the sequence of elements + /// the algorithm will be applied to. + /// \param last Refers to the end of the sequence of elements the + /// algorithm will be applied to. + /// + /// The operations in the parallel \a destroy + /// algorithm invoked with an execution policy object of type \a sequenced_policy + /// execute in sequential order in the calling thread. + /// + /// The operations in the parallel \a destroy + /// algorithm invoked with an execution policy object of type \a parallel_policy + /// or \a parallel_task_policy are permitted to execute in an + /// unordered fashion in unspecified threads, and indeterminately sequenced + /// within each thread. + /// + /// \returns The \a destroy algorithm returns a + /// \a hpx::future, if the execution policy is of type + /// \a sequenced_task_policy or + /// \a parallel_task_policy and returns \a void otherwise. + /// + template ::value && + hpx::traits::is_iterator::value)> + typename util::detail::algorithm_result::type + destroy(ExPolicy && policy, FwdIter first, FwdIter last) + { + static_assert( + (hpx::traits::is_forward_iterator::value), + "Required at least forward iterator."); + + typedef std::integral_constant::value + > is_seq; + + return detail::destroy().call( + std::forward(policy), is_seq(), first, last); + } + + /////////////////////////////////////////////////////////////////////////// + // destroy_n + namespace detail + { + /// \cond NOINTERNAL + + // provide our own implementation of std::destroy + // as some versions of MSVC horribly fail at compiling it for some + // types T + template + FwdIter std_destroy_n(FwdIter first, std::size_t count) + { + typedef typename std::iterator_traits::value_type + value_type; + + for (/* */; count != 0; (void) ++first, --count) + { + std::addressof(*first)->~value_type(); + } + + return first; + } + + template + struct destroy_n + : public detail::algorithm< + destroy_n, FwdIter> + { + destroy_n() + : destroy_n::algorithm("destroy_n") + {} + + template + static FwdIter sequential(ExPolicy, FwdIter first, std::size_t count) + { + return std_destroy_n(first, count); + } + + template + static typename util::detail::algorithm_result< + ExPolicy, FwdIter + >::type + parallel(ExPolicy && policy, FwdIter first, std::size_t count) + { + return parallel_sequential_destroy_n( + std::forward(policy), first, count); + } + }; + /// \endcond + } + + /// Destroys objects of type typename iterator_traits::value_type + /// in the range [first, first + count). + /// + /// \note Complexity: Performs exactly \a count operations, if + /// count > 0, no assignments otherwise. + /// + /// \tparam ExPolicy The type of the execution policy to use (deduced). + /// It describes the manner in which the execution + /// of the algorithm may be parallelized and the manner + /// in which it executes the assignments. + /// \tparam FwdIter The type of the source iterators used (deduced). + /// This iterator type must meet the requirements of an + /// forward iterator. + /// \tparam Size The type of the argument specifying the number of + /// elements to apply this algorithm to. + /// + /// \param policy The execution policy to use for the scheduling of + /// the iterations. + /// \param first Refers to the beginning of the sequence of elements + /// the algorithm will be applied to. + /// \param count Refers to the number of elements starting at + /// \a first the algorithm will be applied to. + /// + /// The operations in the parallel \a destroy_n + /// algorithm invoked with an execution policy object of type + /// \a sequenced_policy execute in sequential order in the + /// calling thread. + /// + /// The operations in the parallel \a destroy_n + /// algorithm invoked with an execution policy object of type + /// \a parallel_policy or + /// \a parallel_task_policy are permitted to execute in an + /// unordered fashion in unspecified threads, and indeterminately sequenced + /// within each thread. + /// + /// \returns The \a destroy_n algorithm returns a + /// \a hpx::future if the execution policy is of type + /// \a sequenced_task_policy or + /// \a parallel_task_policy and + /// returns \a FwdIter otherwise. + /// The \a destroy_n algorithm returns the + /// iterator to the element in the source range, one past + /// the last element constructed. + /// + template ::value && + hpx::traits::is_iterator::value)> + typename util::detail::algorithm_result::type + destroy_n(ExPolicy && policy, FwdIter first, Size count) + { + static_assert( + (hpx::traits::is_forward_iterator::value), + "Requires at least forward iterator."); + + // if count is representing a negative value, we do nothing + if (detail::is_negative(count)) + { + return util::detail::algorithm_result::get( + std::move(first)); + } + + typedef std::integral_constant::value + > is_seq; + + return detail::destroy_n().call( + std::forward(policy), is_seq(), + first, std::size_t(count)); + } +}}} + +#endif diff --git a/hpx/parallel/memory.hpp b/hpx/parallel/memory.hpp index c5441264693a..0e189229dbf5 100644 --- a/hpx/parallel/memory.hpp +++ b/hpx/parallel/memory.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2007-2014 Hartmut Kaiser +// Copyright (c) 2007-2017 Hartmut Kaiser // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,6 +11,7 @@ /// See N4071: 1.3/3 #include +#include #include #include diff --git a/tests/unit/parallel/algorithms/CMakeLists.txt b/tests/unit/parallel/algorithms/CMakeLists.txt index 3f97e86fc2c9..de8630ee9593 100644 --- a/tests/unit/parallel/algorithms/CMakeLists.txt +++ b/tests/unit/parallel/algorithms/CMakeLists.txt @@ -24,6 +24,8 @@ set(tests copyn count countif + destroy + destroyn equal equal_binary exclusive_scan diff --git a/tests/unit/parallel/algorithms/destroy.cpp b/tests/unit/parallel/algorithms/destroy.cpp new file mode 100644 index 000000000000..38c69df82c10 --- /dev/null +++ b/tests/unit/parallel/algorithms/destroy.cpp @@ -0,0 +1,148 @@ +// Copyright (c) 2014-2017 Hartmut Kaiser +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +#include +#include +#include + +#include "destroy_tests.hpp" + +//////////////////////////////////////////////////////////////////////////// +template +void test_destroy() +{ + using namespace hpx::parallel; + test_destroy(execution::seq, IteratorTag()); + test_destroy(execution::par, IteratorTag()); + test_destroy(execution::par_unseq, IteratorTag()); + + test_destroy_async(execution::seq(execution::task), + IteratorTag()); + test_destroy_async(execution::par(execution::task), + IteratorTag()); + +#if defined(HPX_HAVE_GENERIC_EXECUTION_POLICY) + test_destroy(execution_policy(execution::seq), IteratorTag()); + test_destroy(execution_policy(execution::par), IteratorTag()); + test_destroy(execution_policy(execution::par_unseq), IteratorTag()); + + test_destroy( + execution_policy(execution::seq(execution::task)), IteratorTag()); + test_destroy( + execution_policy(execution::par(execution::task)), IteratorTag()); +#endif +} + +void destroy_test() +{ + test_destroy(); + test_destroy(); +} + +/////////////////////////////////////////////////////////////////////////////// +template +void test_destroy_exception() +{ + using namespace hpx::parallel; + + // If the execution policy object is of type vector_execution_policy, + // std::terminate shall be called. therefore we do not test exceptions + // with a vector execution policy + test_destroy_exception(execution::seq, IteratorTag()); + test_destroy_exception(execution::par, IteratorTag()); + + test_destroy_exception_async(execution::seq(execution::task), IteratorTag()); + test_destroy_exception_async(execution::par(execution::task), IteratorTag()); + +#if defined(HPX_HAVE_GENERIC_EXECUTION_POLICY) + test_destroy_exception(execution_policy(execution::seq), IteratorTag()); + test_destroy_exception(execution_policy(execution::par), IteratorTag()); + + test_destroy_exception( + execution_policy(execution::seq(execution::task)), IteratorTag()); + test_destroy_exception( + execution_policy(execution::par(execution::task)), IteratorTag()); +#endif +} + +void destroy_exception_test() +{ + test_destroy_exception(); + test_destroy_exception(); +} + +////////////////////////////////////////////////////////////////////////////// +template +void test_destroy_bad_alloc() +{ + using namespace hpx::parallel; + + // If the execution policy object is of type vector_execution_policy, + // std::terminate shall be called. therefore we do not test exceptions + // with a vector execution policy + test_destroy_bad_alloc(execution::seq, IteratorTag()); + test_destroy_bad_alloc(execution::par, IteratorTag()); + + test_destroy_bad_alloc_async(execution::seq(execution::task), IteratorTag()); + test_destroy_bad_alloc_async(execution::par(execution::task), IteratorTag()); + +#if defined(HPX_HAVE_GENERIC_EXECUTION_POLICY) + test_destroy_bad_alloc(execution_policy(execution::seq), IteratorTag()); + test_destroy_bad_alloc(execution_policy(execution::par), IteratorTag()); + + test_destroy_bad_alloc( + execution_policy(execution::seq(execution::task)), IteratorTag()); + test_destroy_bad_alloc( + execution_policy(execution::par(execution::task)), IteratorTag()); +#endif +} + +void destroy_bad_alloc_test() +{ + test_destroy_bad_alloc(); + test_destroy_bad_alloc(); +} + +int hpx_main(boost::program_options::variables_map& vm) +{ + unsigned int seed = (unsigned int)std::time(nullptr); + if (vm.count("seed")) + seed = vm["seed"].as(); + + std::cout << "using seed: " << seed << std::endl; + std::srand(seed); + + destroy_test(); + destroy_exception_test(); + destroy_bad_alloc_test(); + return hpx::finalize(); +} + +int main(int argc, char* argv[]) +{ + // add command line option which controls the random number generator seed + using namespace boost::program_options; + options_description desc_commandline( + "Usage: " HPX_APPLICATION_STRING " [options]"); + + desc_commandline.add_options() + ("seed,s", value(), + "the random number generator seed to use for this run") + ; + + // By default this test should run on all available cores + std::vector const cfg = { + "hpx.os_threads=all" + }; + + // Initialize and run HPX + HPX_TEST_EQ_MSG(hpx::init(desc_commandline, argc, argv, cfg), 0, + "HPX main exited with non-zero status"); + + return hpx::util::report_errors(); +} diff --git a/tests/unit/parallel/algorithms/destroy_tests.hpp b/tests/unit/parallel/algorithms/destroy_tests.hpp new file mode 100644 index 000000000000..4b28cdadfb2b --- /dev/null +++ b/tests/unit/parallel/algorithms/destroy_tests.hpp @@ -0,0 +1,357 @@ +// Copyright (c) 2014-2017 Hartmut Kaiser +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#if !defined(HPX_PARALLEL_TEST_UNINIT_COPY_MAY28_15_1344) +#define HPX_PARALLEL_TEST_UNINIT_COPY_MAY28_15_1344 + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "test_utils.hpp" + +struct destructable +{ + destructable() + : value_(0) + {} + + ~destructable() + { + std::memset(&value_, 0xcd, sizeof(value_)); + } + + std::uint32_t value_; +}; + +std::size_t const data_size = 10007; + +//////////////////////////////////////////////////////////////////////////// +template +void test_destroy(ExPolicy && policy, IteratorTag) +{ + static_assert( + hpx::parallel::execution::is_execution_policy::value, + "hpx::parallel::execution::is_execution_policy::value"); + + typedef destructable* base_iterator; + typedef test::test_iterator iterator; + + destructable* p = (destructable*)std::malloc( + data_size * sizeof(destructable)); + + // value-initialize data in array + std::for_each( + p, p + data_size, + [](destructable& d) + { + ::new (static_cast(std::addressof(d))) destructable; + }); + + hpx::parallel::destroy( + std::forward(policy), + iterator(p), iterator(p + data_size)); + + std::size_t count = 0; + std::for_each(p, p + data_size, + [&count](destructable v1) + { + HPX_TEST_EQ(v1.value_, (std::uint32_t)0xcdcdcdcd); + ++count; + }); + HPX_TEST_EQ(count, data_size); + + std::free(p); +} + +template +void test_destroy_async(ExPolicy && policy, IteratorTag) +{ + typedef destructable* base_iterator; + typedef test::test_iterator iterator; + + destructable* p = (destructable*)std::malloc( + data_size * sizeof(destructable)); + + // value-initialize data in array + std::for_each( + p, p + data_size, + [](destructable& d) + { + ::new (static_cast(std::addressof(d))) destructable; + }); + + auto f = + hpx::parallel::destroy( + std::forward(policy), + iterator(p), iterator(p + data_size)); + f.wait(); + + std::size_t count = 0; + std::for_each(p, p + data_size, + [&count](destructable v1) + { + HPX_TEST_EQ(v1.value_, (std::uint32_t)0xcdcdcdcd); + ++count; + }); + HPX_TEST_EQ(count, data_size); + + std::free(p); +} + +/////////////////////////////////////////////////////////////////////////////// +template +void test_destroy_exception(ExPolicy policy, IteratorTag) +{ + static_assert( + hpx::parallel::execution::is_execution_policy::value, + "hpx::parallel::execution::is_execution_policy::value"); + + typedef test::count_instances_v data_type; + typedef data_type* base_iterator; + typedef test::decorated_iterator + decorated_iterator; + + data_type* p = (data_type*)std::malloc(data_size * sizeof(data_type)); + + data_type::instance_count.store(0); + data_type::max_instance_count.store(0); + + // value-initialize data in array + std::for_each( + p, p + data_size, + [](data_type& d) + { + ::new (static_cast(std::addressof(d))) data_type; + }); + + HPX_TEST_EQ(data_type::instance_count.load(), data_size); + + boost::atomic throw_after(std::rand() % data_size); //-V104 + std::int64_t throw_after_ = throw_after.load(); + + bool caught_exception = false; + try { + hpx::parallel::destroy(policy, + decorated_iterator(p, + [&throw_after]() + { + if (throw_after-- == 0) + throw std::runtime_error("test"); + }), + decorated_iterator(p + data_size)); + HPX_TEST(false); + } + catch (hpx::exception_list const& e) { + caught_exception = true; + test::test_num_exceptions::call(policy, e); + } + catch (...) { + HPX_TEST(false); + } + + HPX_TEST(caught_exception); + HPX_TEST_LTE(data_type::instance_count.load(), + std::size_t(data_size-throw_after_)); + + std::free(p); +} + +template +void test_destroy_exception_async( + ExPolicy policy, IteratorTag) +{ + typedef test::count_instances_v data_type; + typedef data_type* base_iterator; + typedef test::decorated_iterator + decorated_iterator; + + data_type* p = (data_type*)std::malloc(data_size * sizeof(data_type)); + + data_type::instance_count.store(0); + data_type::max_instance_count.store(0); + + // value-initialize data in array + std::for_each( + p, p + data_size, + [](data_type& d) + { + ::new (static_cast(std::addressof(d))) data_type; + }); + + HPX_TEST_EQ(data_type::instance_count.load(), data_size); + + boost::atomic throw_after(std::rand() % data_size); //-V104 + std::int64_t throw_after_ = throw_after.load(); + + bool caught_exception = false; + bool returned_from_algorithm = false; + try { + auto f = + hpx::parallel::destroy(policy, + decorated_iterator( + p, + [&throw_after]() + { + if (throw_after-- == 0) + throw std::runtime_error("test"); + }), + decorated_iterator(p + data_size)); + + returned_from_algorithm = true; + f.get(); + + HPX_TEST(false); + } + catch (hpx::exception_list const& e) { + caught_exception = true; + test::test_num_exceptions::call(policy, e); + } + catch (...) { + HPX_TEST(false); + } + + HPX_TEST(caught_exception); + HPX_TEST(returned_from_algorithm); + HPX_TEST_LTE(data_type::instance_count.load(), + std::size_t(data_size-throw_after_)); + + std::free(p); +} + +////////////////////////////////////////////////////////////////////////////// +template +void test_destroy_bad_alloc(ExPolicy policy, IteratorTag) +{ + static_assert( + hpx::parallel::execution::is_execution_policy::value, + "hpx::parallel::execution::is_execution_policy::value"); + + typedef test::count_instances_v data_type; + typedef data_type* base_iterator; + typedef test::decorated_iterator + decorated_iterator; + + data_type* p = (data_type*)std::malloc(data_size * sizeof(data_type)); + + data_type::instance_count.store(0); + data_type::max_instance_count.store(0); + + // value-initialize data in array + std::for_each( + p, p + data_size, + [](data_type& d) + { + ::new (static_cast(std::addressof(d))) data_type; + }); + + HPX_TEST_EQ(data_type::instance_count.load(), data_size); + + boost::atomic throw_after(std::rand() % data_size); //-V104 + std::int64_t throw_after_ = throw_after.load(); + + bool caught_bad_alloc = false; + try { + hpx::parallel::destroy(policy, + decorated_iterator( + p, + [&throw_after]() + { + if (throw_after-- == 0) + throw std::bad_alloc(); + }), + decorated_iterator(p + data_size)); + + HPX_TEST(false); + } + catch (std::bad_alloc const&) { + caught_bad_alloc = true; + } + catch (...) { + HPX_TEST(false); + } + + HPX_TEST(caught_bad_alloc); + HPX_TEST_LTE(data_type::instance_count.load(), + std::size_t(data_size-throw_after_)); + + std::free(p); +} + +template +void test_destroy_bad_alloc_async( + ExPolicy policy, IteratorTag) +{ + typedef test::count_instances_v data_type; + typedef data_type* base_iterator; + typedef test::decorated_iterator + decorated_iterator; + + data_type* p = (data_type*)std::malloc(data_size * sizeof(data_type)); + + data_type::instance_count.store(0); + data_type::max_instance_count.store(0); + + // value-initialize data in array + std::for_each( + p, p + data_size, + [](data_type& d) + { + ::new (static_cast(std::addressof(d))) data_type; + }); + + HPX_TEST_EQ(data_type::instance_count.load(), data_size); + + boost::atomic throw_after(std::rand() % data_size); //-V104 + std::int64_t throw_after_ = throw_after.load(); + + bool caught_bad_alloc = false; + bool returned_from_algorithm = false; + try { + auto f = + hpx::parallel::destroy(policy, + decorated_iterator( + p, + [&throw_after]() + { + if (throw_after-- == 0) + throw std::bad_alloc(); + }), + decorated_iterator(p + data_size)); + + returned_from_algorithm = true; + f.get(); + + HPX_TEST(false); + } + catch(std::bad_alloc const&) { + caught_bad_alloc = true; + } + catch(...) { + HPX_TEST(false); + } + + HPX_TEST(caught_bad_alloc); + HPX_TEST(returned_from_algorithm); + HPX_TEST_LTE(data_type::instance_count.load(), + std::size_t(data_size-throw_after_)); + + std::free(p); +} + +#endif diff --git a/tests/unit/parallel/algorithms/destroyn.cpp b/tests/unit/parallel/algorithms/destroyn.cpp new file mode 100644 index 000000000000..b4a759edfd9c --- /dev/null +++ b/tests/unit/parallel/algorithms/destroyn.cpp @@ -0,0 +1,494 @@ +// Copyright (c) 2014-2017 Hartmut Kaiser +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_utils.hpp" + +struct destructable +{ + destructable() + : value_(0) + {} + + ~destructable() + { + std::memset(&value_, 0xcd, sizeof(value_)); + } + + std::uint32_t value_; +}; + +std::size_t const data_size = 10007; + +//////////////////////////////////////////////////////////////////////////// +template +void test_destroy_n(ExPolicy policy, IteratorTag) +{ + static_assert( + hpx::parallel::execution::is_execution_policy::value, + "hpx::parallel::execution::is_execution_policy::value"); + + typedef destructable* base_iterator; + typedef test::test_iterator iterator; + + destructable* p = (destructable*)std::malloc( + data_size * sizeof(destructable)); + + // value-initialize data in array + std::for_each( + p, p + data_size, + [](destructable& d) + { + ::new (static_cast(std::addressof(d))) destructable; + }); + + hpx::parallel::destroy_n( + std::forward(policy), + iterator(p), data_size); + + std::size_t count = 0; + std::for_each(p, p + data_size, + [&count](destructable v1) + { + HPX_TEST_EQ(v1.value_, (std::uint32_t)0xcdcdcdcd); + ++count; + }); + HPX_TEST_EQ(count, data_size); + + std::free(p); +} + +template +void test_destroy_n_async(ExPolicy policy, IteratorTag) +{ + typedef destructable* base_iterator; + typedef test::test_iterator iterator; + + destructable* p = + (destructable*)std::malloc(data_size * sizeof(destructable)); + + // value-initialize data in array + std::for_each( + p, p + data_size, + [](destructable& d) + { + ::new (static_cast(std::addressof(d))) destructable; + }); + + auto f = + hpx::parallel::destroy_n( + std::forward(policy), + iterator(p), data_size); + f.wait(); + + std::size_t count = 0; + std::for_each(p, p + data_size, + [&count](destructable v1) + { + HPX_TEST_EQ(v1.value_, (std::uint32_t)0xcdcdcdcd); + ++count; + }); + HPX_TEST_EQ(count, data_size); + + std::free(p); +} + +template +void test_destroy_n() +{ + using namespace hpx::parallel; + + test_destroy_n(execution::seq, IteratorTag()); + test_destroy_n(execution::par, IteratorTag()); + test_destroy_n(execution::par_unseq, IteratorTag()); + + test_destroy_n_async(execution::seq(execution::task), IteratorTag()); + test_destroy_n_async(execution::par(execution::task), IteratorTag()); + +#if defined(HPX_HAVE_GENERIC_EXECUTION_POLICY) + test_destroy_n(execution_policy(execution::seq), IteratorTag()); + test_destroy_n(execution_policy(execution::par), IteratorTag()); + test_destroy_n(execution_policy(execution::par_unseq), IteratorTag()); + + test_destroy_n( + execution_policy(execution::seq(execution::task)), IteratorTag()); + test_destroy_n( + execution_policy(execution::par(execution::task)), IteratorTag()); +#endif +} + +void destroy_n_test() +{ + test_destroy_n(); + test_destroy_n(); +} + +/////////////////////////////////////////////////////////////////////////////// +template +void test_destroy_n_exception(ExPolicy policy, IteratorTag) +{ + static_assert( + hpx::parallel::execution::is_execution_policy::value, + "hpx::parallel::execution::is_execution_policy::value"); + + typedef test::count_instances_v data_type; + typedef data_type* base_iterator; + typedef test::decorated_iterator + decorated_iterator; + + data_type* p = (data_type*)std::malloc(data_size * sizeof(data_type)); + + data_type::instance_count.store(0); + data_type::max_instance_count.store(0); + + // value-initialize data in array + std::for_each( + p, p + data_size, + [](data_type& d) + { + ::new (static_cast(std::addressof(d))) data_type; + }); + + HPX_TEST_EQ(data_type::instance_count.load(), data_size); + + boost::atomic throw_after(std::rand() % data_size); //-V104 + std::size_t throw_after_ = throw_after.load(); + + bool caught_exception = false; + try { + hpx::parallel::destroy_n(policy, + decorated_iterator( + p, + [&throw_after]() + { + if (throw_after-- == 0) + throw std::runtime_error("test"); + }), + data_size); + HPX_TEST(false); + } + catch(hpx::exception_list const& e) { + caught_exception = true; + test::test_num_exceptions::call(policy, e); + } + catch(...) { + HPX_TEST(false); + } + + HPX_TEST(caught_exception); + HPX_TEST_LTE(data_type::instance_count.load(), + std::size_t(data_size-throw_after_)); + + std::free(p); +} + +template +void test_destroy_n_exception_async( + ExPolicy policy, IteratorTag) +{ + typedef test::count_instances_v data_type; + typedef data_type* base_iterator; + typedef test::decorated_iterator + decorated_iterator; + + data_type* p = (data_type*)std::malloc(data_size * sizeof(data_type)); + + data_type::instance_count.store(0); + data_type::max_instance_count.store(0); + + // value-initialize data in array + std::for_each( + p, p + data_size, + [](data_type& d) + { + ::new (static_cast(std::addressof(d))) data_type; + }); + + HPX_TEST_EQ(data_type::instance_count.load(), data_size); + + boost::atomic throw_after(std::rand() % data_size); //-V104 + std::size_t throw_after_ = throw_after.load(); + + bool caught_exception = false; + bool returned_from_algorithm = false; + try { + auto f = + hpx::parallel::destroy_n(policy, + decorated_iterator( + p, + [&throw_after]() + { + if (throw_after-- == 0) + throw std::runtime_error("test"); + }), + data_size); + + returned_from_algorithm = true; + f.get(); + + HPX_TEST(false); + } + catch(hpx::exception_list const& e) { + caught_exception = true; + test::test_num_exceptions::call(policy, e); + } + catch(...) { + HPX_TEST(false); + } + + HPX_TEST(caught_exception); + HPX_TEST(returned_from_algorithm); + HPX_TEST_LTE(data_type::instance_count.load(), + std::size_t(data_size-throw_after_)); + + std::free(p); +} + +template +void test_destroy_n_exception() +{ + using namespace hpx::parallel; + + // If the execution policy object is of type vector_execution_policy, + // std::terminate shall be called. therefore we do not test exceptions + // with a vector execution policy + test_destroy_n_exception(execution::seq, IteratorTag()); + test_destroy_n_exception(execution::par, IteratorTag()); + + test_destroy_n_exception_async(execution::seq(execution::task), IteratorTag()); + test_destroy_n_exception_async(execution::par(execution::task), IteratorTag()); + +#if defined(HPX_HAVE_GENERIC_EXECUTION_POLICY) + test_destroy_n_exception(execution_policy(execution::seq), IteratorTag()); + test_destroy_n_exception(execution_policy(execution::par), IteratorTag()); + + test_destroy_n_exception(execution_policy(execution::seq(execution::task)), + IteratorTag()); + test_destroy_n_exception(execution_policy(execution::par(execution::task)), + IteratorTag()); +#endif +} + +void destroy_n_exception_test() +{ + test_destroy_n_exception(); + test_destroy_n_exception(); +} + +//////////////////////////////////////////////////////////////////////////////// +template< typename ExPolicy, typename IteratorTag> +void test_destroy_n_bad_alloc(ExPolicy policy, IteratorTag) +{ + static_assert( + hpx::parallel::execution::is_execution_policy::value, + "hpx::parallel::execution::is_execution_policy::value"); + + typedef test::count_instances_v data_type; + typedef data_type* base_iterator; + typedef test::decorated_iterator + decorated_iterator; + + data_type* p = (data_type*)std::malloc(data_size * sizeof(data_type)); + + data_type::instance_count.store(0); + data_type::max_instance_count.store(0); + + // value-initialize data in array + std::for_each( + p, p + data_size, + [](data_type& d) + { + ::new (static_cast(std::addressof(d))) data_type; + }); + + HPX_TEST_EQ(data_type::instance_count.load(), data_size); + + boost::atomic throw_after(std::rand() % data_size); //-V104 + std::size_t throw_after_ = throw_after.load(); + + bool caught_bad_alloc = false; + try { + hpx::parallel::destroy_n(policy, + decorated_iterator( + p, + [&throw_after]() + { + if (throw_after-- == 0) + throw std::bad_alloc(); + }), + data_size); + + HPX_TEST(false); + } + catch(std::bad_alloc const&) { + caught_bad_alloc = true; + } + catch(...) { + HPX_TEST(false); + } + + HPX_TEST(caught_bad_alloc); + HPX_TEST_LTE(data_type::instance_count.load(), + std::size_t(data_size-throw_after_)); + + std::free(p); +} + +template +void test_destroy_n_bad_alloc_async( + ExPolicy policy, IteratorTag) +{ + typedef test::count_instances_v data_type; + typedef data_type* base_iterator; + typedef test::decorated_iterator + decorated_iterator; + + data_type* p = (data_type*)std::malloc(data_size * sizeof(data_type)); + + data_type::instance_count.store(0); + data_type::max_instance_count.store(0); + + // value-initialize data in array + std::for_each( + p, p + data_size, + [](data_type& d) + { + ::new (static_cast(std::addressof(d))) data_type; + }); + + HPX_TEST_EQ(data_type::instance_count.load(), data_size); + + boost::atomic throw_after(std::rand() % data_size); //-V104 + std::size_t throw_after_ = throw_after.load(); + + bool caught_bad_alloc = false; + bool returned_from_algorithm = false; + try { + auto f = + hpx::parallel::destroy_n(policy, + decorated_iterator( + p, + [&throw_after]() + { + if (throw_after-- == 0) + throw std::bad_alloc(); + }), + data_size); + + returned_from_algorithm = true; + f.get(); + + HPX_TEST(false); + } + catch(std::bad_alloc const&) { + caught_bad_alloc = true; + } + catch(...) { + HPX_TEST(false); + } + + HPX_TEST(caught_bad_alloc); + HPX_TEST(returned_from_algorithm); + HPX_TEST_LTE(data_type::instance_count.load(), + std::size_t(data_size-throw_after_)); + + std::free(p); +} + +template +void test_destroy_n_bad_alloc() +{ + using namespace hpx::parallel; + + // If the execution policy object is of type vector_execution_policy, + // std::terminate shall be called. therefore we do not test exceptions + // with a vector execution policy + test_destroy_n_bad_alloc(execution::seq, IteratorTag()); + test_destroy_n_bad_alloc(execution::par, IteratorTag()); + + test_destroy_n_bad_alloc_async( + execution::seq(execution::task), + IteratorTag()); + test_destroy_n_bad_alloc_async( + execution::par(execution::task), + IteratorTag()); + +#if defined(HPX_HAVE_GENERIC_EXECUTION_POLICY) + test_destroy_n_bad_alloc( + execution_policy(execution::seq), + IteratorTag()); + test_destroy_n_bad_alloc( + execution_policy(execution::par), + IteratorTag()); + + test_destroy_n_bad_alloc( + execution_policy(execution::seq(execution::task)), + IteratorTag()); + test_destroy_n_bad_alloc( + execution_policy(execution::par(execution::task)), + IteratorTag()); +#endif +} + +void destroy_n_bad_alloc_test() +{ + test_destroy_n_bad_alloc(); + test_destroy_n_bad_alloc(); +} + +int hpx_main(boost::program_options::variables_map& vm) +{ + unsigned int seed = (unsigned int)std::time(nullptr); + if (vm.count("seed")) + seed = vm["seed"].as(); + + std::cout << "using seed: " << seed << std::endl; + std::srand(seed); + + destroy_n_test(); + destroy_n_exception_test(); + destroy_n_bad_alloc_test(); + return hpx::finalize(); +} + +int main(int argc, char* argv[]) +{ + // add command line option which controls the random number generator seed + using namespace boost::program_options; + options_description desc_commandline( + "Usage: " HPX_APPLICATION_STRING " [options]"); + + desc_commandline.add_options() + ("seed,s", value(), + "the random number generator seed to use for this run") + ; + + // By default this test should run on all available cores + std::vector const cfg = { + "hpx.os_threads=all" + }; + + // Initialize and run HPX + HPX_TEST_EQ_MSG(hpx::init(desc_commandline, argc, argv, cfg), 0, + "HPX main exited with non-zero status"); + + return hpx::util::report_errors(); +} diff --git a/tests/unit/parallel/algorithms/test_utils.hpp b/tests/unit/parallel/algorithms/test_utils.hpp index c0c007c475f9..283913e92715 100644 --- a/tests/unit/parallel/algorithms/test_utils.hpp +++ b/tests/unit/parallel/algorithms/test_utils.hpp @@ -76,50 +76,60 @@ namespace test }; /////////////////////////////////////////////////////////////////////////// - struct count_instances + template + struct count_instances_v { - count_instances() - : value_(std::size_t(-1)) + count_instances_v() { ++instance_count; + ++max_instance_count; } - count_instances(int value) + count_instances_v(T value) : value_(value) { ++instance_count; + ++max_instance_count; } - count_instances(count_instances const& rhs) + + count_instances_v(count_instances_v const& rhs) : value_(rhs.value_) { ++instance_count; } - count_instances(count_instances && rhs) + count_instances_v(count_instances_v && rhs) : value_(rhs.value_) { ++instance_count; } - count_instances& operator=(count_instances const& rhs) + count_instances_v& operator=(count_instances_v const& rhs) { value_ = rhs.value_; return *this; } - count_instances& operator=(count_instances && rhs) + count_instances_v& operator=(count_instances_v && rhs) { value_ = rhs.value_; return *this; } - ~count_instances() + ~count_instances_v() { --instance_count; } - std::size_t value_; + T value_; static boost::atomic instance_count; + static boost::atomic max_instance_count; }; - boost::atomic count_instances::instance_count(0); + template + boost::atomic count_instances_v::instance_count(0); + + template + boost::atomic count_instances_v::max_instance_count(0); + + using count_instances = count_instances_v; /////////////////////////////////////////////////////////////////////////// template From 2dee0b10777c9052ad5780950a09d73c9be046d4 Mon Sep 17 00:00:00 2001 From: Hartmut Kaiser Date: Thu, 8 Jun 2017 12:24:54 -0500 Subject: [PATCH 2/3] Fixing use of is_sequenced_execution_policy --- hpx/parallel/algorithms/destroy.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hpx/parallel/algorithms/destroy.hpp b/hpx/parallel/algorithms/destroy.hpp index 8dc9dabdf212..243a4c99a20d 100644 --- a/hpx/parallel/algorithms/destroy.hpp +++ b/hpx/parallel/algorithms/destroy.hpp @@ -156,7 +156,7 @@ namespace hpx { namespace parallel { inline namespace v1 "Required at least forward iterator."); typedef std::integral_constant::value + execution::is_sequenced_execution_policy::value > is_seq; return detail::destroy().call( @@ -277,7 +277,7 @@ namespace hpx { namespace parallel { inline namespace v1 } typedef std::integral_constant::value + execution::is_sequenced_execution_policy::value > is_seq; return detail::destroy_n().call( From a81886ec428b82f62efa2788a3b30c04255db921 Mon Sep 17 00:00:00 2001 From: Hartmut Kaiser Date: Thu, 8 Jun 2017 16:58:12 -0500 Subject: [PATCH 3/3] Adding missing #includes --- tests/unit/parallel/algorithms/destroy_tests.hpp | 1 + tests/unit/parallel/algorithms/destroyn.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/unit/parallel/algorithms/destroy_tests.hpp b/tests/unit/parallel/algorithms/destroy_tests.hpp index 4b28cdadfb2b..3661809c23a0 100644 --- a/tests/unit/parallel/algorithms/destroy_tests.hpp +++ b/tests/unit/parallel/algorithms/destroy_tests.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/unit/parallel/algorithms/destroyn.cpp b/tests/unit/parallel/algorithms/destroyn.cpp index b4a759edfd9c..1417fd5a3112 100644 --- a/tests/unit/parallel/algorithms/destroyn.cpp +++ b/tests/unit/parallel/algorithms/destroyn.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include