From 2ea0dbf30e20d21629f8698a1290bcc8bcfd41f3 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 2 May 2023 13:44:27 -0700 Subject: [PATCH] Add impl of member function swap() --- .../boost/unordered/concurrent_flat_map.hpp | 7 + include/boost/unordered/detail/foa/core.hpp | 4 +- test/Jamfile.v2 | 1 + test/cfoa/swap_tests.cpp | 263 ++++++++++++++++++ 4 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 test/cfoa/swap_tests.cpp diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index ffc15f9c..df3718dd 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -694,6 +694,13 @@ namespace boost { return table_.erase_if(f); } + void swap(concurrent_flat_map& other) noexcept( + boost::allocator_is_always_equal::type::value || + boost::allocator_propagate_on_container_swap::type::value) + { + return table_.swap(other.table_); + } + void clear() noexcept { table_.clear(); } /// Hash Policy diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 6769d179..0daa0206 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1447,8 +1447,8 @@ public: swap(h(),x.h()); swap(pred(),x.pred()); swap(arrays,x.arrays); - swap(ml,x.ml); - swap(size_,x.size_); + swap_size_impl(ml,x.ml); + swap_size_impl(size_,x.size_); } void clear()noexcept diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index f3f6feb5..209e73c4 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -185,6 +185,7 @@ local CFOA_TESTS = constructor_tests assign_tests clear_tests + swap_tests ; for local test in $(CFOA_TESTS) diff --git a/test/cfoa/swap_tests.cpp b/test/cfoa/swap_tests.cpp new file mode 100644 index 00000000..40180168 --- /dev/null +++ b/test/cfoa/swap_tests.cpp @@ -0,0 +1,263 @@ +// Copyright (C) 2023 Christian Mazakas +// 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 "helpers.hpp" + +#include + +test::seed_t initialize_seed{996130204}; + +using test::default_generator; +using test::limited_range; +using test::sequential; + +template struct pocs_allocator +{ + using propagate_on_container_swap = std::true_type; + + int x_ = -1; + + using value_type = T; + + pocs_allocator() = default; + pocs_allocator(pocs_allocator const&) = default; + pocs_allocator(pocs_allocator&&) = default; + + pocs_allocator(int const x) : x_{x} {} + + pocs_allocator& operator=(pocs_allocator const& rhs) + { + if (this != &rhs) { + x_ = rhs.x_; + } + return *this; + } + + template pocs_allocator(pocs_allocator const& rhs) : x_{rhs.x_} + { + } + + T* allocate(std::size_t n) + { + return static_cast(::operator new(n * sizeof(T))); + } + + void deallocate(T* p, std::size_t) { ::operator delete(p); } + + bool operator==(pocs_allocator const& rhs) const { return x_ == rhs.x_; } + bool operator!=(pocs_allocator const& rhs) const { return x_ != rhs.x_; } + + friend void swap(pocs_allocator& lhs, pocs_allocator& rhs) noexcept + { + std::swap(lhs.x_, rhs.x_); + } +}; + +using hasher = stateful_hash; +using key_equal = stateful_key_equal; +using allocator_type = stateful_allocator >; + +using map_type = boost::unordered::concurrent_flat_map; + +using map_value_type = typename map_type::value_type; + +using pocs_allocator_type = pocs_allocator >; + +using pocs_map_type = boost::unordered::concurrent_flat_map; + +template struct is_nothrow_member_swappable +{ + static bool const value = + noexcept(std::declval().swap(std::declval())); +}; + +BOOST_STATIC_ASSERT(is_nothrow_member_swappable< + boost::unordered::concurrent_flat_map, + std::equal_to, std::allocator > > >::value); + +BOOST_STATIC_ASSERT(is_nothrow_member_swappable::value); + +BOOST_STATIC_ASSERT(!is_nothrow_member_swappable::value); + +namespace { + template + void swap_tests(X*, G gen, test::random_generator rg) + { + using allocator = typename X::allocator_type; + + bool const pocs = + boost::allocator_propagate_on_container_swap::type::value; + + auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); }); + auto vals2 = make_random_values(1024 * 4, [&] { return gen(rg); }); + + auto ref_map1 = + boost::unordered_flat_map(vals1.begin(), vals1.end()); + + auto ref_map2 = + boost::unordered_flat_map(vals2.begin(), vals2.end()); + + { + raii::reset_counts(); + + X x1(vals1.begin(), vals1.end(), vals1.size(), hasher(1), key_equal(2), + allocator(3)); + + X x2(vals2.begin(), vals2.end(), vals2.size(), hasher(2), key_equal(1), + pocs ? allocator(4) : allocator(3)); + + if (pocs) { + BOOST_TEST(x1.get_allocator() != x2.get_allocator()); + } else { + BOOST_TEST(x1.get_allocator() == x2.get_allocator()); + } + + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + thread_runner(vals1, [&x1, &x2](boost::span s) { + (void)s; + + x1.swap(x2); + x2.swap(x1); + }); + + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + + if (pocs) { + if (x1.get_allocator() == allocator(3)) { + BOOST_TEST(x2.get_allocator() == allocator(4)); + } else { + BOOST_TEST(x1.get_allocator() == allocator(4)); + BOOST_TEST(x2.get_allocator() == allocator(3)); + } + } else { + BOOST_TEST(x1.get_allocator() == allocator(3)); + BOOST_TEST(x1.get_allocator() == x2.get_allocator()); + } + + if (x1.size() == ref_map1.size()) { + test_matches_reference(x1, ref_map1); + test_matches_reference(x2, ref_map2); + + BOOST_TEST_EQ(x1.hash_function(), hasher(1)); + BOOST_TEST_EQ(x1.key_eq(), key_equal(2)); + + BOOST_TEST_EQ(x2.hash_function(), hasher(2)); + BOOST_TEST_EQ(x2.key_eq(), key_equal(1)); + } else { + test_matches_reference(x2, ref_map1); + test_matches_reference(x1, ref_map2); + + BOOST_TEST_EQ(x1.hash_function(), hasher(2)); + BOOST_TEST_EQ(x1.key_eq(), key_equal(1)); + + BOOST_TEST_EQ(x2.hash_function(), hasher(1)); + BOOST_TEST_EQ(x2.key_eq(), key_equal(2)); + } + } + check_raii_counts(); + } + + template void insert_and_swap(G gen, test::random_generator rg) + { + auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); }); + auto vals2 = make_random_values(1024 * 4, [&] { return gen(rg); }); + + { + raii::reset_counts(); + + map_type x1(vals1.size(), hasher(1), key_equal(2), allocator_type(3)); + map_type x2(vals2.size(), hasher(2), key_equal(1), allocator_type(3)); + + std::thread t1, t2, t3; + boost::latch l(2); + + std::mutex m; + std::condition_variable cv; + std::atomic_bool done1{false}, done2{false}; + std::atomic num_swaps{0}; + + t1 = std::thread([&x1, &vals1, &l, &done1, &cv] { + l.arrive_and_wait(); + + for (std::size_t idx = 0; idx < vals1.size(); ++idx) { + auto const& val = vals1[idx]; + x1.insert(val); + if (idx % 100 == 0) { + cv.notify_all(); + } + } + + done1 = true; + }); + + t2 = std::thread([&x2, &vals2, &l, &done2] { + l.arrive_and_wait(); + + for (auto const& val : vals2) { + x2.insert(val); + } + + done2 = true; + }); + + t3 = std::thread([&x1, &x2, &m, &cv, &done1, &done2, &num_swaps] { + do { + { + std::unique_lock lk(m); + cv.wait(lk, [] { return true; }); + } + x1.swap(x2); + ++num_swaps; + } while (!done1 && !done2); + }); + + t1.join(); + t2.join(); + t3.join(); + + BOOST_TEST_GT(num_swaps, 0u); + + BOOST_TEST_NOT(x1.empty()); + BOOST_TEST_NOT(x2.empty()); + + if (x1.hash_function() == hasher(1)) { + BOOST_TEST_EQ(x1.key_eq(), key_equal(2)); + + BOOST_TEST_EQ(x2.hash_function(), hasher(2)); + BOOST_TEST_EQ(x2.key_eq(), key_equal(1)); + } else { + BOOST_TEST_EQ(x1.hash_function(), hasher(2)); + BOOST_TEST_EQ(x1.key_eq(), key_equal(1)); + + BOOST_TEST_EQ(x2.hash_function(), hasher(1)); + BOOST_TEST_EQ(x2.key_eq(), key_equal(2)); + } + } + + check_raii_counts(); + } + + map_type* map; + pocs_map_type* pocs_map; + +} // namespace + +// clang-format off +UNORDERED_TEST( + swap_tests, + ((map)(pocs_map)) + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST(insert_and_swap, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) +// clang-format on + +RUN_TESTS()