From 12935eb6952bb4fd30e8af16345ea0d610ee9135 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 17 Mar 2023 14:22:24 -0700 Subject: [PATCH] Add minimal prototype of concurrent_flat_map --- .../boost/unordered/concurrent_flat_map.hpp | 128 ++++++++++++++ test/Jamfile.v2 | 7 + test/cfoa/insert_tests.cpp | 163 ++++++++++++++++++ 3 files changed, 298 insertions(+) create mode 100644 include/boost/unordered/concurrent_flat_map.hpp create mode 100644 test/cfoa/insert_tests.cpp diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp new file mode 100644 index 00000000..8cd515d3 --- /dev/null +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -0,0 +1,128 @@ +/* Fast open-addressing concurrent hash table. + * + * Copyright 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) + * + * See https://www.boost.org/libs/unordered for library home page. + */ + +/* Reference: + * https://github.com/joaquintides/concurrent_hashmap_api#proposed-synopsis + */ + +#ifndef BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP +#define BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP + +#include + +#include +#include +#include + +#include +#include + +namespace boost { + namespace unordered { + namespace detail { + template struct concurrent_map_types + { + using key_type = Key; + using raw_key_type = typename std::remove_const::type; + using raw_mapped_type = typename std::remove_const::type; + + using init_type = std::pair; + using moved_type = std::pair; + using value_type = std::pair; + + using element_type = value_type; + + static value_type& value_from(element_type& x) { return x; } + + template + static raw_key_type const& extract(std::pair const& kv) + { + return kv.first; + } + + static moved_type move(init_type& x) + { + return {std::move(x.first), std::move(x.second)}; + } + + static moved_type move(element_type& x) + { + // TODO: we probably need to launder here + return {std::move(const_cast(x.first)), + std::move(const_cast(x.second))}; + } + + template + static void construct(A& al, init_type* p, Args&&... args) + { + boost::allocator_construct(al, p, std::forward(args)...); + } + + template + static void construct(A& al, value_type* p, Args&&... args) + { + boost::allocator_construct(al, p, std::forward(args)...); + } + + template static void destroy(A& al, init_type* p) noexcept + { + boost::allocator_destroy(al, p); + } + + template static void destroy(A& al, value_type* p) noexcept + { + boost::allocator_destroy(al, p); + } + }; + } // namespace detail + + template , + class Pred = std::equal_to, + class Allocator = std::allocator > > + class concurrent_flat_map + { + private: + using type_policy = detail::concurrent_map_types; + + detail::foa::concurrent_table table_; + + public: + using key_type = Key; + using mapped_type = T; + using value_type = typename type_policy::value_type; + using init_type = typename type_policy::init_type; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using hasher = typename boost::type_identity::type; + using key_equal = typename boost::type_identity::type; + using allocator_type = typename boost::type_identity::type; + using reference = value_type&; + using const_reference = value_type const&; + using pointer = typename boost::allocator_pointer::type; + using const_pointer = + typename boost::allocator_const_pointer::type; + + concurrent_flat_map() : concurrent_flat_map(0) {} + explicit concurrent_flat_map(size_type n, const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()) + : table_(n, hf, eql, a) + { + } + + bool insert(value_type const& obj) + { + return table_.insert(obj); + } + }; + } // namespace unordered +} // namespace boost + +#endif // BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP \ No newline at end of file diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 31b17efd..f94bc6f6 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -157,3 +157,10 @@ run exception/erase_exception_tests.cpp : : : $(CPP11) BOOST_UNORD run exception/rehash_exception_tests.cpp : : : $(CPP11) BOOST_UNORDERED_FOA_TESTS : foa_rehash_exception_tests ; run exception/swap_exception_tests.cpp : : : $(CPP11) BOOST_UNORDERED_FOA_TESTS : foa_swap_exception_tests ; run exception/merge_exception_tests.cpp : : : $(CPP11) BOOST_UNORDERED_FOA_TESTS : foa_merge_exception_tests ; + +rule build_cfoa ( name ) +{ + run cfoa/$(name).cpp : : : $(CPP11) : cfoa_$(name) ; +} + +build_cfoa insert_tests ; diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp new file mode 100644 index 00000000..817b96b1 --- /dev/null +++ b/test/cfoa/insert_tests.cpp @@ -0,0 +1,163 @@ +// 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/generators.hpp" +#include "../helpers/test.hpp" + +#include +#include + +#include +#include +#include + +struct raii +{ + static std::atomic_int default_constructor; + static std::atomic_int copy_constructor; + static std::atomic_int move_constructor; + static std::atomic_int destructor; + + static std::atomic_int copy_assignment; + static std::atomic_int move_assignment; + + int x_ = -1; + + raii() { ++default_constructor; } + explicit raii(int const x) : x_{x} { ++default_constructor; } + raii(raii const& rhs) : x_{rhs.x_} { ++copy_constructor; } + raii(raii&& rhs) noexcept : x_{rhs.x_} + { + rhs.x_ = -1; + ++move_constructor; + } + ~raii() { ++destructor; } + + raii& operator=(raii const& rhs) + { + ++copy_assignment; + if (this != &rhs) { + x_ = rhs.x_; + } + return *this; + } + + raii& operator=(raii&& rhs) noexcept + { + ++move_assignment; + if (this != &rhs) { + x_ = rhs.x_; + rhs.x_ = -1; + } + return *this; + } + + static void reset_counts() + { + default_constructor = 0; + copy_constructor = 0; + move_constructor = 0; + destructor = 0; + copy_assignment = 0; + move_assignment = 0; + } + + friend std::ostream& operator<<(std::ostream& os, raii const& rhs) + { + os << "{ x_: " << rhs.x_ << "}"; + return os; + } + + friend std::ostream& operator<<( + std::ostream& os, std::pair const& rhs) + { + os << "pair<" << rhs.first << ", " << rhs.second << ">"; + return os; + } + + friend bool operator==(raii const& lhs, raii const& rhs) + { + return lhs.x_ == rhs.x_; + } + + friend bool operator!=(raii const& lhs, raii const& rhs) + { + return !(lhs == rhs); + } +}; + +std::atomic_int raii::default_constructor{0}; +std::atomic_int raii::copy_constructor{0}; +std::atomic_int raii::move_constructor{0}; +std::atomic_int raii::destructor{0}; +std::atomic_int raii::copy_assignment{0}; +std::atomic_int raii::move_assignment{0}; + +std::size_t hash_value(raii const& r) noexcept +{ + boost::hash hasher; + return hasher(r.x_); +} + +std::vector > make_random_values( + std::size_t count, test::random_generator rg) +{ + std::vector > v; + v.reserve(count); + for (std::size_t i = 0; i < count; ++i) { + int* p = nullptr; + int a = generate(p, rg); + int b = generate(p, rg); + v.emplace_back(raii{a}, raii{b}); + } + + raii::reset_counts(); + + return v; +} + +namespace { + test::seed_t initialize_seed(78937); + + template void insert(X*, test::random_generator generator) + { + raii::reset_counts(); + + auto values = make_random_values(1024, generator); + BOOST_TEST_GT(values.size(), 0); + + { + X x; + + for (auto const& r : values) { + bool b = x.insert(r); + (void)b; + } + } + + BOOST_TEST_GE(raii::default_constructor, 0); + BOOST_TEST_GE(raii::copy_constructor, 0); + BOOST_TEST_GE(raii::move_constructor, 0); + BOOST_TEST_GT(raii::destructor, 0); + + BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + + raii::move_constructor, + raii::destructor); + + BOOST_TEST_EQ(raii::copy_assignment, 0); + BOOST_TEST_EQ(raii::move_assignment, 0); + } + + boost::unordered::concurrent_flat_map* map; + +} // namespace + +using test::default_generator; +using test::generate_collisions; +using test::limited_range; + +UNORDERED_TEST( + insert, ((map))((default_generator)(generate_collisions)(limited_range))) + +RUN_TESTS()