/* Copyright (c) 2005-2022 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include "common/test.h" #include "common/utils_concurrency_limit.h" #include "common/cpu_usertime.h" #include "common/concepts_common.h" #include "common/iterator.h" #include "tbb/parallel_sort.h" #include "tbb/concurrent_vector.h" #include "tbb/global_control.h" #include #include #include #include #include #include #include #include //! \file test_parallel_sort.cpp //! \brief Test for [algorithms.parallel_sort] /** Has tightly controlled interface so that we can verify that parallel_sort uses only the required interface. */ class Minimal { int val; public: void set_val(int i) { val = i; } static bool Less (const Minimal &a, const Minimal &b) { return (a.val < b.val); } static bool AreEqual( const Minimal &a, const Minimal &b) { return a.val == b.val; } }; //! Defines a comparison function object for Minimal class MinimalLessCompare { public: bool operator() (const Minimal &a, const Minimal &b) const { return Minimal::Less(a,b); } }; template bool compare(const Value& lhs, const Value& rhs) { return lhs == rhs; } bool compare(const Minimal& lhs, const Minimal& rhs) { return Minimal::AreEqual(lhs, rhs); } template void validate(Range test_range, Range sorted_range) { using value_type = typename std::iterator_traits::value_type; REQUIRE( std::equal(std::begin(test_range), std::end(test_range), std::begin(sorted_range), [](const value_type& tested, const value_type& reference) { return compare(tested, reference); } ) ); } //! The default initialization routine. /*! This routine assumes that you can assign to the elements from a float. It assumes that iter and sorted_list have already been allocated. It fills them according to the current data set (tracked by a local static variable). Returns true if a valid test has been setup, or false if there is no test to perform. */ template void set(RefType& ref, ValueType new_value) { ref = static_cast(new_value); } template void set(Minimal& minimal_ref, ValueType new_value) { minimal_ref.set_val(static_cast(new_value)); } template void set(std::string& string_ref, KeyType key) { string_ref = std::to_string(static_cast(key)); } template bool fill_ranges(RandomAccessIterator test_range_begin, RandomAccessIterator sorted_range_begin, std::size_t size, const Compare &compare) { static char test_case = 0; const char num_cases = 3; if (test_case < num_cases) { // switch on the current test case, filling the test_list and sorted_list appropriately switch(test_case) { case 0: /* use sin to generate the values */ for (std::size_t i = 0; i < size; i++) { set(test_range_begin[i], sin(float(i))); set(sorted_range_begin[i], sin(float(i))); } break; case 1: /* presorted list */ for (std::size_t i = 0; i < size; i++) { set(test_range_begin[i], i); set(sorted_range_begin[i], i); } break; case 2: /* reverse-sorted list */ for (std::size_t i = 0; i < size; i++) { set(test_range_begin[i], size - i); set(sorted_range_begin[i], size - i); } break; } // pre-sort sorted_range for later validity testing std::sort(sorted_range_begin, sorted_range_begin + size, compare); test_case++; return true; } test_case = 0; return false; } //! The default test routine. /*! Tests all data set sizes from 0 to N, all grainsizes from 0 to G=10, and selects from all possible interfaces to parallel_sort depending on whether a scratch space and compare have been provided. */ template struct parallel_sort_test { static void run() { std::less default_comp; ContainerType range(Size); ContainerType sorted_range(Size); while (fill_ranges(std::begin(range), std::begin(sorted_range), Size, default_comp)) { tbb::parallel_sort(range); validate(range, sorted_range); } } template static void run(Comparator& comp) { ContainerType range(Size); ContainerType sorted_range(Size); while (fill_ranges(std::begin(range), std::begin(sorted_range), Size, comp)) { tbb::parallel_sort(range, comp); validate(range, sorted_range); } } }; template void parallel_sort_test_suite() { Comparator comp; for (auto concurrency_level : utils::concurrency_range()) { tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level); parallel_sort_test::run(comp); parallel_sort_test::run(comp); parallel_sort_test::run(comp); parallel_sort_test::run(comp); parallel_sort_test::run(comp); } } template void parallel_sort_test_suite() { for (auto concurrency_level : utils::concurrency_range()) { tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level); parallel_sort_test::run(); parallel_sort_test::run(); parallel_sort_test::run(); parallel_sort_test::run(); parallel_sort_test::run(); } } #if __TBB_CPP20_CONCEPTS_PRESENT template concept can_call_parallel_sort_with_iterator = requires( RandomAccessIterator it ) { tbb::parallel_sort(it, it); }; template concept can_call_parallel_sort_with_iterator_and_compare = requires( RandomAccessIterator it, const Compare& compare ) { tbb::parallel_sort(it, it, compare); }; template concept can_call_parallel_sort_with_cbs = requires( CBS& cbs ) { tbb::parallel_sort(cbs); }; template concept can_call_parallel_sort_with_cbs_and_compare = requires( CBS& cbs, const Compare& compare ) { tbb::parallel_sort(cbs, compare); }; template using CorrectCompare = test_concepts::compare::Correct; void test_psort_iterator_constraints() { using namespace test_concepts::parallel_sort_value; static_assert(can_call_parallel_sort_with_iterator>); static_assert(can_call_parallel_sort_with_iterator::iterator>); static_assert(!can_call_parallel_sort_with_iterator>); static_assert(!can_call_parallel_sort_with_iterator>); static_assert(!can_call_parallel_sort_with_iterator>); static_assert(!can_call_parallel_sort_with_iterator>); static_assert(!can_call_parallel_sort_with_iterator>); static_assert(!can_call_parallel_sort_with_iterator>); static_assert(can_call_parallel_sort_with_iterator_and_compare, CorrectCompare>); static_assert(can_call_parallel_sort_with_iterator_and_compare::iterator, CorrectCompare>); static_assert(!can_call_parallel_sort_with_iterator_and_compare, CorrectCompare>); static_assert(!can_call_parallel_sort_with_iterator_and_compare, CorrectCompare>); static_assert(!can_call_parallel_sort_with_iterator_and_compare, CorrectCompare>); static_assert(!can_call_parallel_sort_with_iterator_and_compare, CorrectCompare>); static_assert(!can_call_parallel_sort_with_iterator_and_compare, CorrectCompare>); } void test_psort_compare_constraints() { using namespace test_concepts::compare; using CorrectCBS = test_concepts::container_based_sequence::Correct; using CorrectIterator = CorrectCBS::iterator; static_assert(can_call_parallel_sort_with_iterator_and_compare>); static_assert(!can_call_parallel_sort_with_iterator_and_compare>); static_assert(!can_call_parallel_sort_with_iterator_and_compare>); static_assert(!can_call_parallel_sort_with_iterator_and_compare>); static_assert(!can_call_parallel_sort_with_iterator_and_compare>); static_assert(can_call_parallel_sort_with_cbs_and_compare>); static_assert(!can_call_parallel_sort_with_cbs_and_compare>); static_assert(!can_call_parallel_sort_with_cbs_and_compare>); static_assert(!can_call_parallel_sort_with_cbs_and_compare>); static_assert(!can_call_parallel_sort_with_cbs_and_compare>); } void test_psort_cbs_constraints() { using namespace test_concepts::container_based_sequence; using namespace test_concepts::parallel_sort_value; static_assert(can_call_parallel_sort_with_cbs); static_assert(!can_call_parallel_sort_with_cbs); static_assert(!can_call_parallel_sort_with_cbs); static_assert(!can_call_parallel_sort_with_cbs); static_assert(!can_call_parallel_sort_with_cbs); static_assert(can_call_parallel_sort_with_cbs>); static_assert(!can_call_parallel_sort_with_cbs>); static_assert(!can_call_parallel_sort_with_cbs>); static_assert(!can_call_parallel_sort_with_cbs>);\ using CorrectCompare = test_concepts::compare::Correct; using NonMovableCompare = test_concepts::compare::Correct; using NonMoveAssignableCompare = test_concepts::compare::Correct; static_assert(can_call_parallel_sort_with_cbs_and_compare); static_assert(!can_call_parallel_sort_with_cbs_and_compare); static_assert(!can_call_parallel_sort_with_cbs_and_compare); static_assert(!can_call_parallel_sort_with_cbs_and_compare); static_assert(!can_call_parallel_sort_with_cbs_and_compare); static_assert(!can_call_parallel_sort_with_cbs_and_compare, NonMovableCompare>); static_assert(!can_call_parallel_sort_with_cbs_and_compare, NonMoveAssignableCompare>); } #endif // __TBB_CPP20_CONCEPTS_PRESENT template struct minimal_span { minimal_span(T* input_data, std::size_t input_size) : data{input_data} , size{input_size} {} T* begin() const { return data; } T* end() const { return data + size; } private: T* data; std::size_t size; }; //! Minimal array sorting test (less comparator) //! \brief \ref error_guessing TEST_CASE("Minimal array sorting test (less comparator)") { parallel_sort_test_suite, MinimalLessCompare>(); } //! Float array sorting test (default comparator) //! \brief \ref error_guessing TEST_CASE("Float array sorting test (default comparator)") { parallel_sort_test_suite>(); } //! tbb::concurrent_vector sorting test (less comparator) //! \brief \ref error_guessing TEST_CASE("tbb::concurrent_vector sorting test (less comparator)") { parallel_sort_test_suite, std::less>(); } //! tbb::concurrent_vector sorting test (default comparator) //! \brief \ref error_guessing TEST_CASE("tbb::concurrent_vector sorting test (default comparator)") { parallel_sort_test_suite>(); } //! Array of strings sorting test (less comparator) //! \brief \ref error_guessing TEST_CASE("Array of strings sorting test (less comparator)") { parallel_sort_test_suite, std::less>(); } //! Array of strings sorting test (default comparator) //! \brief \ref error_guessing TEST_CASE("Array of strings sorting test (default comparator)") { parallel_sort_test_suite>(); } //! tbb::concurrent_vector sorting test (less comparator) //! \brief \ref error_guessing TEST_CASE("tbb::concurrent_vector sorting test (less comparator)") { parallel_sort_test_suite, MinimalLessCompare>(); } constexpr std::size_t array_size = 10000; template void sort_array_test(const SortFunctor& sort_functor) { int test_array[array_size]; for (std::size_t i = 0; i < array_size; ++i) test_array[i] = rand() % array_size; sort_functor(test_array); for (std::size_t i = 0; i < array_size - 1; ++i) REQUIRE_MESSAGE(test_array[i] <= test_array[i + 1], "Testing data not sorted"); } //! Array sorting test (default comparator) //! \brief \ref error_guessing TEST_CASE("Array sorting test (default comparator)") { for ( auto concurrency_level : utils::concurrency_range() ) { tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level); sort_array_test([](int (&array)[array_size]) { tbb::parallel_sort(array); }); } } //! Test array sorting via rvalue span (default comparator) //! \brief \ref error_guessing TEST_CASE("Test array sorting via rvalue span (default comparator)") { sort_array_test([](int (&array)[array_size]) { tbb::parallel_sort(minimal_span{array, array_size}); }); } //! Test array sorting via const span (default comparator) //! \brief \ref error_guessing TEST_CASE("Test array sorting via const span (default comparator)") { sort_array_test([](int (&array)[array_size]) { const minimal_span span(array, array_size); tbb::parallel_sort(span); }); } //! Test rvalue container with stateful comparator //! \brief \ref error_guessing TEST_CASE("Test rvalue container with stateful comparator") { // Create sorted range std::vector test_vector(array_size); for (std::size_t i = 0; i < array_size; ++i) test_vector[i] = i; std::atomic count{0}; tbb::parallel_sort(std::move(test_vector), [&](std::size_t lhs, std::size_t rhs) { ++count; return lhs < rhs; }); // The comparator should be called at least (size - 1) times to check that the array is sorted REQUIRE_MESSAGE(count >= array_size - 1, "Incorrect comparator calls count"); } //! Testing workers going to sleep //! \brief \ref resource_usage TEST_CASE("That all workers sleep when no work") { int test_array[array_size]; for (std::size_t i = 0; i < array_size; ++i) test_array[i] = rand() % array_size; tbb::parallel_sort(test_array); TestCPUUserTime(utils::get_platform_max_threads()); } #if __TBB_CPP20_CONCEPTS_PRESENT //! \brief \ref error_guessing TEST_CASE("parallel_sort constraints") { test_psort_iterator_constraints(); test_psort_compare_constraints(); test_psort_cbs_constraints(); } #endif // __TBB_CPP20_CONCEPTS_PRESENT