From 8b056b902e53616abb2ad873e264819b8bc97ad7 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 10 Mar 2023 18:39:55 +0100 Subject: [PATCH 001/327] split foa::table in table and core in preparation for foa::concurrent_table --- .../detail/{foa.hpp => foa/core.hpp} | 983 ++++++------------ include/boost/unordered/detail/foa/table.hpp | 512 +++++++++ .../boost/unordered/unordered_flat_map.hpp | 2 +- .../boost/unordered/unordered_flat_set.hpp | 2 +- .../boost/unordered/unordered_node_map.hpp | 2 +- .../boost/unordered/unordered_node_set.hpp | 2 +- 6 files changed, 818 insertions(+), 685 deletions(-) rename include/boost/unordered/detail/{foa.hpp => foa/core.hpp} (78%) create mode 100644 include/boost/unordered/detail/foa/table.hpp diff --git a/include/boost/unordered/detail/foa.hpp b/include/boost/unordered/detail/foa/core.hpp similarity index 78% rename from include/boost/unordered/detail/foa.hpp rename to include/boost/unordered/detail/foa/core.hpp index 30a27272..395e5812 100644 --- a/include/boost/unordered/detail/foa.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1,4 +1,4 @@ -/* Fast open-addressing hash table. +/* Common base for Boost.Unordered open-addressing tables. * * Copyright 2022-2023 Joaquin M Lopez Munoz. * Copyright 2023 Christian Mazakas. @@ -9,8 +9,8 @@ * See https://www.boost.org/libs/unordered for library home page. */ -#ifndef BOOST_UNORDERED_DETAIL_FOA_HPP -#define BOOST_UNORDERED_DETAIL_FOA_HPP +#ifndef BOOST_UNORDERED_DETAIL_FOA_CORE_HPP +#define BOOST_UNORDERED_DETAIL_FOA_CORE_HPP #include #include @@ -25,14 +25,12 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include #include #include @@ -95,10 +93,12 @@ namespace unordered{ namespace detail{ namespace foa{ -static const std::size_t default_bucket_count = 0; +static constexpr std::size_t default_bucket_count=0; -/* foa::table is an open-addressing hash table serving as the foundational core - * of boost::unordered_flat_[map|set]. Its main internal design aspects are: +/* foa::table_core is the common base of foa::table and foa::concurrent_table, + * which in their turn serve as the foundational core of + * boost::unordered_flat_[map|set] and boost::concurrent_flat_map, + * respectively. Its main internal design aspects are: * * - Element slots are logically split into groups of size N=15. The number * of groups is always a power of two, so the number of allocated slots @@ -218,8 +218,9 @@ struct group15 inline void mark_overflow(std::size_t hash) { -#if BOOST_WORKAROUND(BOOST_GCC, >= 50000 && BOOST_GCC < 60000) - overflow() = static_cast( overflow() | static_cast(1<<(hash%8)) ); +#if BOOST_WORKAROUND(BOOST_GCC,>=50000 && BOOST_GCC<60000) + overflow()=static_cast( + overflow()|static_cast(1<<(hash%8))); #else overflow()|=static_cast(1<<(hash%8)); #endif @@ -674,9 +675,9 @@ private: #endif -/* foa::table uses a size policy to obtain the permissible sizes of the group - * array (and, by implication, the element array) and to do the hash->group - * mapping. +/* foa::table_core uses a size policy to obtain the permissible sizes of the + * group array (and, by implication, the element array) and to do the + * hash->group mapping. * * - size_index(n) returns an unspecified "index" number used in other policy * operations. @@ -762,12 +763,12 @@ private: std::size_t pos,step=0; }; -/* Mixing policies: no_mix is the identity function, xmx_mix uses the - * xmx function defined in , and mulx_mix +/* Mixing policies: no_mix is the identity function, and mulx_mix * uses the mulx function from . * - * foa::table mixes hash results with mulx_mix unless the hash is marked as - * avalanching, i.e. of good quality (see ). + * foa::table_core mixes hash results with mulx_mix unless the hash is marked + * as avalanching, i.e. of good quality + * (see ). */ struct no_mix @@ -779,15 +780,6 @@ struct no_mix } }; -struct xmx_mix -{ - template - static inline std::size_t mix(const Hash& h,const T& x) - { - return xmx(h(x)); - } -}; - struct mulx_mix { template @@ -813,151 +805,6 @@ inline unsigned int unchecked_countr_zero(int x) #endif } -template -class table; - -/* table_iterator keeps two pointers: - * - * - A pointer p to the element slot. - * - A pointer pc to the n-th byte of the associated group metadata, where n - * is the position of the element in the group. - * - * A simpler solution would have been to keep a pointer p to the element, a - * pointer pg to the group, and the position n, but that would increase - * sizeof(table_iterator) by 4/8 bytes. In order to make this compact - * representation feasible, it is required that group objects are aligned - * to their size, so that we can recover pg and n as - * - * - n = pc%sizeof(group) - * - pg = pc-n - * - * (for explanatory purposes pg and pc are treated above as if they were memory - * addresses rather than pointers). - * - * p = nullptr is conventionally used to mark end() iterators. - */ - -/* internal conversion from const_iterator to iterator */ -class const_iterator_cast_tag {}; - -template -class table_iterator -{ - using type_policy=TypePolicy; - using table_element_type=typename type_policy::element_type; - using group_type=Group; - static constexpr auto N=group_type::N; - static constexpr auto regular_layout=group_type::regular_layout; - -public: - using difference_type=std::ptrdiff_t; - using value_type=typename type_policy::value_type; - using pointer= - typename std::conditional::type; - using reference= - typename std::conditional::type; - using iterator_category=std::forward_iterator_tag; - using element_type= - typename std::conditional::type; - - table_iterator()=default; - template::type* =nullptr> - table_iterator(const table_iterator& x): - pc{x.pc},p{x.p}{} - table_iterator( - const_iterator_cast_tag, const table_iterator& x): - pc{x.pc},p{x.p}{} - - inline reference operator*()const noexcept{return type_policy::value_from(*p);} - inline pointer operator->()const noexcept - {return std::addressof(type_policy::value_from(*p));} - inline table_iterator& operator++()noexcept{increment();return *this;} - inline table_iterator operator++(int)noexcept - {auto x=*this;increment();return x;} - friend inline bool operator==( - const table_iterator& x,const table_iterator& y) - {return x.p==y.p;} - friend inline bool operator!=( - const table_iterator& x,const table_iterator& y) - {return !(x==y);} - -private: - template friend class table_iterator; - template friend class table; - - table_iterator(Group* pg,std::size_t n,const table_element_type* p_): - pc{reinterpret_cast(const_cast(pg))+n}, - p{const_cast(p_)} - {} - - inline void increment()noexcept - { - BOOST_ASSERT(p!=nullptr); - increment(std::integral_constant{}); - } - - inline void increment(std::true_type /* regular layout */)noexcept - { - for(;;){ - ++p; - if(reinterpret_cast(pc)%sizeof(group_type)==N-1){ - pc+=sizeof(group_type)-(N-1); - break; - } - ++pc; - if(!group_type::is_occupied(pc))continue; - if(BOOST_UNLIKELY(group_type::is_sentinel(pc)))p=nullptr; - return; - } - - for(;;){ - int mask=reinterpret_cast(pc)->match_occupied(); - if(mask!=0){ - auto n=unchecked_countr_zero(mask); - if(BOOST_UNLIKELY(reinterpret_cast(pc)->is_sentinel(n))){ - p=nullptr; - } - else{ - pc+=n; - p+=n; - } - return; - } - pc+=sizeof(group_type); - p+=N; - } - } - - inline void increment(std::false_type /* interleaved */)noexcept - { - std::size_t n0=reinterpret_cast(pc)%sizeof(group_type); - pc-=n0; - - int mask=( - reinterpret_cast(pc)->match_occupied()>>(n0+1))<<(n0+1); - if(!mask){ - do{ - pc+=sizeof(group_type); - p+=N; - } - while((mask=reinterpret_cast(pc)->match_occupied())==0); - } - - auto n=unchecked_countr_zero(mask); - if(BOOST_UNLIKELY(reinterpret_cast(pc)->is_sentinel(n))){ - p=nullptr; - } - else{ - pc+=n; - p-=n0; - p+=n; - } - } - - unsigned char *pc=nullptr; - table_element_type *p=nullptr; -}; - /* table_arrays controls allocation, initialization and deallocation of * paired arrays of groups and element slots. Only one chunk of memory is * allocated to place both arrays: this is not done for efficiency reasons, @@ -1103,8 +950,6 @@ inline void prefetch(const void* p) #endif } -struct try_emplace_args_t{}; - template struct is_std_allocator:std::false_type{}; @@ -1160,7 +1005,7 @@ _STL_RESTORE_DEPRECATED_WARNING * x.f(); // declaration of "foo" in derived::f shadows base type "foo" * * This makes shadowing warnings unavoidable in general when a class template - * derives from user-provided classes, as is the case with table and + * derives from user-provided classes, as is the case with table_core and * empty_value's below. */ @@ -1183,41 +1028,23 @@ _STL_RESTORE_DEPRECATED_WARNING /* We expose the hard-coded max load factor so that tests can use it without * needing to pull it from an instantiated class template such as the table - * class + * class. */ -constexpr static float const mlf = 0.875f; +static constexpr float mlf=0.875f; -template -union uninitialized_storage +template +struct table_locator { - T t_; - uninitialized_storage(){} - ~uninitialized_storage(){} + Group *pg; + unsigned int n; + Element *p; }; -/* foa::table interface departs in a number of ways from that of C++ unordered - * associative containers because it's not for end-user consumption - * (boost::unordered_[flat|node]_[map|set]) wrappers complete it as - * appropriate). - * - * The table supports two main modes of operation: node-based and flat. In the - * node-based case, buckets store pointers to individually heap-allocated - * elements. For flat, buckets directly store elements. - * - * For both tables: - * - * - begin() is not O(1). - * - No bucket API. - * - Load factor is fixed and can't be set by the user. - * - * For the inline table: - * - * - value_type must be moveable. - * - Pointer stability is not kept under rehashing. - * - No extract API. - * - * The TypePolicy template parameter is used to generate instantiations - * suitable for either maps or sets, and introduces non-standard init_type: +struct try_emplace_args_t{}; + +/* table_core. The TypePolicy template parameter is used to generate + * instantiations suitable for either maps or sets, and introduces non-standard + * init_type and element_type: * * - TypePolicy::key_type and TypePolicy::value_type have the obvious * meaning. @@ -1246,18 +1073,15 @@ union uninitialized_storage * decltype(TypePolicy::move(...)). * * - TypePolicy::element_type is the type that table_arrays uses when - * allocating buckets. For flat containers, this is value_type. For node - * containers, this is a strong typedef to value_type*. + * allocating buckets, which allows us to have flat and node container. + * For flat containers, element_type is value_type. For node + * containers, it is a strong typedef to value_type*. * * - TypePolicy::value_from returns a mutable reference to value_type from * a given element_type. This is used when elements of the table themselves * need to be moved, such as during move construction/assignment when * allocators are unequal and there is no propagation. For all other cases, * the element_type itself is moved. - * - * try_emplace, erase and find support heterogenous lookup by default, that is, - * without checking for any ::is_transparent typedefs --the checking is done by - * boost::unordered_[flat|node]_[map|set]. */ template @@ -1267,11 +1091,9 @@ class __declspec(empty_bases) /* activate EBO with multiple inheritance */ #endif -table:empty_value,empty_value,empty_value +table_core:empty_value,empty_value,empty_value { - using hash_base=empty_value; - using pred_base=empty_value; - using allocator_base=empty_value; +protected: using type_policy=TypePolicy; using group_type=group15; static constexpr auto N=group_type::N; @@ -1283,18 +1105,12 @@ table:empty_value,empty_value,empty_value mulx_mix >::type; using alloc_traits=boost::allocator_traits; + using element_type=typename type_policy::element_type; + using arrays_type=table_arrays; -public: using key_type=typename type_policy::key_type; using init_type=typename type_policy::init_type; using value_type=typename type_policy::value_type; - using element_type=typename type_policy::element_type; - -private: - static constexpr bool has_mutable_iterator= - !std::is_same::value; - -public: using hasher=Hash; using key_equal=Pred; using allocator_type=Allocator; @@ -1304,13 +1120,9 @@ public: using const_reference=const value_type&; using size_type=std::size_t; using difference_type=std::ptrdiff_t; - using const_iterator=table_iterator; - using iterator=typename std::conditional< - has_mutable_iterator, - table_iterator, - const_iterator>::type; + using locator=table_locator; - table( + table_core( std::size_t n=0,const Hash& h_=Hash(),const Pred& pred_=Pred(), const Allocator& al_=Allocator()): hash_base{empty_init,h_},pred_base{empty_init,pred_}, @@ -1318,10 +1130,10 @@ public: ml{initial_max_load()} {} - table(const table& x): - table{x,alloc_traits::select_on_container_copy_construction(x.al())}{} + table_core(const table_core& x): + table_core{x,alloc_traits::select_on_container_copy_construction(x.al())}{} - table(table&& x) + table_core(table_core&& x) noexcept( std::is_nothrow_move_constructible::value&& std::is_nothrow_move_constructible::value&& @@ -1336,14 +1148,14 @@ public: x.ml=x.initial_max_load(); } - table(const table& x,const Allocator& al_): - table{std::size_t(std::ceil(float(x.size())/mlf)),x.h(),x.pred(),al_} + table_core(const table_core& x,const Allocator& al_): + table_core{std::size_t(std::ceil(float(x.size())/mlf)),x.h(),x.pred(),al_} { copy_elements_from(x); } - table(table&& x,const Allocator& al_): - table{0,std::move(x.h()),std::move(x.pred()),al_} + table_core(table_core&& x,const Allocator& al_): + table_core{0,std::move(x.h()),std::move(x.pred()),al_} { if(al()==x.al()){ std::swap(size_,x.size_); @@ -1364,7 +1176,7 @@ public: } } - ~table()noexcept + ~table_core()noexcept { for_all_elements([this](element_type* p){ destroy_element(p); @@ -1372,7 +1184,7 @@ public: delete_arrays(arrays); } - table& operator=(const table& x) + table_core& operator=(const table_core& x) { BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred) @@ -1380,18 +1192,21 @@ public: alloc_traits::propagate_on_container_copy_assignment::value; if(this!=std::addressof(x)){ - // if copy construction here winds up throwing, the container is still - // left intact so we perform these operations first + /* If copy construction here winds up throwing, the container is still + * left intact so we perform these operations first. + */ hasher tmp_h=x.h(); key_equal tmp_p=x.pred(); - // already noexcept, clear() before we swap the Hash, Pred just in case - // the clear() impl relies on them at some point in the future - clear(); + /* already noexcept, clear() before we swap the Hash, Pred just in case + * the clear() impl relies on them at some point in the future. + */ + clear(); - // because we've asserted at compile-time that Hash and Pred are nothrow - // swappable, we can safely mutate our source container and maintain - // consistency between the Hash, Pred compatibility + /* Because we've asserted at compile-time that Hash and Pred are nothrow + * swappable, we can safely mutate our source container and maintain + * consistency between the Hash, Pred compatibility. + */ using std::swap; swap(h(),tmp_h); swap(pred(),tmp_p); @@ -1412,7 +1227,7 @@ public: #pragma warning(disable:4127) /* conditional expression is constant */ #endif - table& operator=(table&& x) + table_core& operator=(table_core&& x) noexcept( alloc_traits::propagate_on_container_move_assignment::value|| alloc_traits::is_always_equal::value) @@ -1469,109 +1284,27 @@ public: allocator_type get_allocator()const noexcept{return al();} - iterator begin()noexcept - { - iterator it{arrays.groups,0,arrays.elements}; - if(arrays.elements&&!(arrays.groups[0].match_occupied()&0x1))++it; - return it; - } - - const_iterator begin()const noexcept - {return const_cast(this)->begin();} - iterator end()noexcept{return {};} - const_iterator end()const noexcept{return const_cast(this)->end();} - const_iterator cbegin()const noexcept{return begin();} - const_iterator cend()const noexcept{return end();} - bool empty()const noexcept{return size()==0;} std::size_t size()const noexcept{return size_;} std::size_t max_size()const noexcept{return SIZE_MAX;} - template - BOOST_FORCEINLINE std::pair emplace(Args&&... args) - { - using emplace_type=typename std::conditional< - std::is_constructible::value, - init_type, - value_type - >::type; - - using insert_type=typename std::conditional< - std::is_constructible< - value_type,emplace_type>::value, - emplace_type,element_type - >::type; - - uninitialized_storage s; - auto *p=std::addressof(s.t_); - - type_policy::construct(al(),p,std::forward(args)...); - - destroy_on_exit guard{al(),p}; - return emplace_impl(type_policy::move(*p)); - } - - template - BOOST_FORCEINLINE std::pair try_emplace( - Key&& x,Args&&... args) - { - return emplace_impl( - try_emplace_args_t{},std::forward(x),std::forward(args)...); - } - - BOOST_FORCEINLINE std::pair - insert(const init_type& x){return emplace_impl(x);} - - BOOST_FORCEINLINE std::pair - insert(init_type&& x){return emplace_impl(std::move(x));} - - /* template tilts call ambiguities in favor of init_type */ - - template - BOOST_FORCEINLINE std::pair - insert(const value_type& x){return emplace_impl(x);} - - template - BOOST_FORCEINLINE std::pair - insert(value_type&& x){return emplace_impl(std::move(x));} - - template - BOOST_FORCEINLINE - typename std::enable_if< - !std::is_same::value, - std::pair - >::type - insert(element_type&& x){return emplace_impl(std::move(x));} - - template< - bool dependent_value=false, - typename std::enable_if< - has_mutable_iterator||dependent_value>::type* =nullptr - > - void erase(iterator pos)noexcept{return erase(const_iterator(pos));} + // TODO unify erase? BOOST_FORCEINLINE - void erase(const_iterator pos)noexcept + void erase(group_type* pg,unsigned int pos,element_type* p)noexcept { - destroy_element(pos.p); - recover_slot(pos.pc); + destroy_element(p); + recover_slot(pg,pos); } - template BOOST_FORCEINLINE - auto erase(Key&& x) -> typename std::enable_if< - !std::is_convertible::value&& - !std::is_convertible::value, std::size_t>::type + void erase(unsigned char* pc,element_type* p)noexcept { - auto it=find(x); - if(it!=end()){ - erase(it); - return 1; - } - else return 0; + destroy_element(p); + recover_slot(pc); } - void swap(table& x) + void swap(table_core& x) noexcept( alloc_traits::propagate_on_container_swap::value|| alloc_traits::is_always_equal::value) @@ -1617,43 +1350,9 @@ public: } } - element_type extract(const_iterator pos) - { - BOOST_ASSERT(pos!=end()); - erase_on_exit e{*this,pos}; - (void)e; - return std::move(*pos.p); - } - - // TODO: should we accept different allocator too? - template - void merge(table& x) - { - x.for_all_elements([&,this](group_type* pg,unsigned int n,element_type* p){ - erase_on_exit e{x,{pg,n,p}}; - if(!emplace_impl(type_policy::move(*p)).second)e.rollback(); - }); - } - - template - void merge(table&& x){merge(x);} - hasher hash_function()const{return h();} key_equal key_eq()const{return pred();} - template - BOOST_FORCEINLINE iterator find(const Key& x) - { - auto hash=hash_for(x); - return find_impl(x,position_for(hash),hash); - } - - template - BOOST_FORCEINLINE const_iterator find(const Key& x)const - { - return const_cast(this)->find(x); - } - std::size_t capacity()const noexcept { return arrays.elements?(arrays.groups_size_mask+1)*N-1:0; @@ -1661,8 +1360,8 @@ public: float load_factor()const noexcept { - if (capacity() == 0) { return 0; } - return float(size())/float(capacity()); + if capacity()==0)return 0; + else return float(size())/float(capacity()); } float max_load_factor()const noexcept{return mlf;} @@ -1683,32 +1382,10 @@ public: rehash(std::size_t(std::ceil(float(n)/mlf))); } - template - friend std::size_t erase_if(table& x,Predicate pr) - { - return x.erase_if_impl(pr); - } - -private: - template friend class table; - using arrays_type=table_arrays; - struct clear_on_exit { ~clear_on_exit(){x.clear();} - table& x; - }; - - struct erase_on_exit - { - erase_on_exit(table& x_,const_iterator it_):x{x_},it{it_}{} - ~erase_on_exit(){if(!rollback_)x.erase(it);} - - void rollback(){rollback_=true;} - - table& x; - const_iterator it; - bool rollback_=false; + table_core& x; }; template @@ -1726,16 +1403,6 @@ private: Allocator& al(){return allocator_base::get();} const Allocator& al()const{return allocator_base::get();} - arrays_type new_arrays(std::size_t n) - { - return arrays_type::new_(al(),n); - } - - void delete_arrays(arrays_type& arrays_)noexcept - { - arrays_type::delete_(al(),arrays_); - } - template void construct_element(element_type* p,Args&&... args) { @@ -1751,28 +1418,6 @@ private: std::forward(args)...); } - template - void construct_element_from_try_emplace_args( - element_type* p,std::false_type,Key&& x,Args&&... args) - { - type_policy::construct( - al(),p, - std::piecewise_construct, - std::forward_as_tuple(std::forward(x)), - std::forward_as_tuple(std::forward(args)...)); - } - - /* This overload allows boost::unordered_flat_set to internally use - * try_emplace to implement heterogeneous insert (P2363). - */ - - template - void construct_element_from_try_emplace_args( - element_type* p,std::true_type,Key&& x) - { - type_policy::construct(al(),p,std::forward(x)); - } - void destroy_element(element_type* p)noexcept { type_policy::destroy(al(),p); @@ -1781,115 +1426,10 @@ private: struct destroy_element_on_exit { ~destroy_element_on_exit(){this_->destroy_element(p);} - table *this_; + table_core *this_; element_type *p; }; - void copy_elements_from(const table& x) - { - BOOST_ASSERT(empty()); - BOOST_ASSERT(this!=std::addressof(x)); - if(arrays.groups_size_mask==x.arrays.groups_size_mask){ - fast_copy_elements_from(x); - } - else{ - x.for_all_elements([this](const element_type* p){ - unchecked_insert(*p); - }); - } - } - - void fast_copy_elements_from(const table& x) - { - if(arrays.elements){ - copy_elements_array_from(x); - std::memcpy( - arrays.groups,x.arrays.groups, - (arrays.groups_size_mask+1)*sizeof(group_type)); - size_=x.size(); - } - } - - void copy_elements_array_from(const table& x) - { - copy_elements_array_from( - x, - std::integral_constant< - bool, -#if BOOST_WORKAROUND(BOOST_LIBSTDCXX_VERSION,<50000) - /* std::is_trivially_copy_constructible not provided */ - boost::has_trivial_copy::value -#else - std::is_trivially_copy_constructible::value -#endif - &&( - is_std_allocator::value|| - !alloc_has_construct::value) - >{} - ); - } - - void copy_elements_array_from(const table& x,std::true_type /* -> memcpy */) - { - /* reinterpret_cast: GCC may complain about value_type not being trivially - * copy-assignable when we're relying on trivial copy constructibility. - */ - std::memcpy( - reinterpret_cast(arrays.elements), - reinterpret_cast(x.arrays.elements), - x.capacity()*sizeof(value_type)); - } - - void copy_elements_array_from(const table& x,std::false_type /* -> manual */) - { - std::size_t num_constructed=0; - BOOST_TRY{ - x.for_all_elements([&,this](const element_type* p){ - construct_element(arrays.elements+(p-x.arrays.elements),*p); - ++num_constructed; - }); - } - BOOST_CATCH(...){ - if(num_constructed){ - x.for_all_elements_while([&,this](const element_type* p){ - destroy_element(arrays.elements+(p-x.arrays.elements)); - return --num_constructed!=0; - }); - } - BOOST_RETHROW - } - BOOST_CATCH_END - } - - void recover_slot(unsigned char* pc) - { - /* If this slot potentially caused overflow, we decrease the maximum load so - * that average probe length won't increase unboundedly in repeated - * insert/erase cycles (drift). - */ - ml-=group_type::maybe_caused_overflow(pc); - group_type::reset(pc); - --size_; - } - - void recover_slot(group_type* pg,std::size_t pos) - { - recover_slot(reinterpret_cast(pg)+pos); - } - - std::size_t initial_max_load()const - { - static constexpr std::size_t small_capacity=2*N-1; - - auto capacity_=capacity(); - if(capacity_<=small_capacity){ - return capacity_; /* we allow 100% usage */ - } - else{ - return (std::size_t)(mlf*(float)(capacity_)); - } - } - template static inline auto key_from(const T& x) ->decltype(type_policy::extract(x)) @@ -1943,77 +1483,18 @@ private: #endif } -#if defined(BOOST_MSVC) -/* warning: forcing value to bool 'true' or 'false' in bool(pred()...) */ -#pragma warning(push) -#pragma warning(disable:4800) -#endif - - template - BOOST_FORCEINLINE iterator find_impl( - const Key& x,std::size_t pos0,std::size_t hash)const - { - prober pb(pos0); - do{ - auto pos=pb.get(); - auto pg=arrays.groups+pos; - auto mask=pg->match(hash); - if(mask){ - BOOST_UNORDERED_ASSUME(arrays.elements != nullptr); - auto p=arrays.elements+pos*N; - prefetch_elements(p); - do{ - auto n=unchecked_countr_zero(mask); - if(BOOST_LIKELY(bool(pred()(x,key_from(p[n]))))){ - return {pg,n,p+n}; - } - mask&=mask-1; - }while(mask); - } - if(BOOST_LIKELY(pg->is_not_overflowed(hash))){ - return {}; /* end() */ - } - } - while(BOOST_LIKELY(pb.next(arrays.groups_size_mask))); - return {}; /* end() */ - } - -#if defined(BOOST_MSVC) -#pragma warning(pop) /* C4800 */ -#endif - template - BOOST_FORCEINLINE std::pair emplace_impl(Args&&... args) + locator unchecked_emplace_at( + std::size_t pos0,std::size_t hash,Args&&... args) { - const auto &k=key_from(std::forward(args)...); - auto hash=hash_for(k); - auto pos0=position_for(hash); - auto it=find_impl(k,pos0,hash); - - if(it!=end()){ - return {it,false}; - } - if(BOOST_LIKELY(size_(args)...), - true - }; - } - else{ - return { - unchecked_emplace_with_rehash(hash,std::forward(args)...), - true - }; - } - } - - static std::size_t capacity_for(std::size_t n) - { - return size_policy::size(size_index_for(n))*N-1; + auto res=nosize_unchecked_emplace_at( + arrays,pos0,hash,std::forward(args)...); + ++size_; + return res; } template - BOOST_NOINLINE iterator + BOOST_NOINLINE locator unchecked_emplace_with_rehash(std::size_t hash,Args&&... args) { /* Due to the anti-drift mechanism (see recover_slot), new_arrays_ may be @@ -2026,9 +1507,9 @@ private: * element having caused overflow; P has been measured as ~0.162 under * ideal conditions, yielding F ~ 0.0165 ~ 1/61. */ - auto new_arrays_=new_arrays(std::size_t( - std::ceil(static_cast(size_+size_/61+1)/mlf))); - iterator it; + auto new_arrays_=new_arrays(std::size_t( + std::ceil(static_cast(size_+size_/61+1)/mlf))); + locator it; BOOST_TRY{ /* strong exception guarantee -> try insertion before rehash */ it=nosize_unchecked_emplace_at( @@ -2047,6 +1528,224 @@ private: return it; } + template + std::size_t erase_if_impl(Predicate pr) + { + std::size_t s=size(); + for_all_elements([&,this](group_type* pg,unsigned int n,element_type* p){ + if(pr(type_policy::value_from(*p))) erase(pg,n,p); + }); + return std::size_t(s-size()); + } + + template + void for_all_elements(F f)const + { + for_all_elements(arrays,f); + } + + template + static auto for_all_elements(const arrays_type& arrays_,F f) + ->decltype(f(nullptr),void()) + { + for_all_elements_while(arrays_,[&](element_type* p){f(p);return true;}); + } + + template + static auto for_all_elements(const arrays_type& arrays_,F f) + ->decltype(f(nullptr,0,nullptr),void()) + { + for_all_elements_while( + arrays_,[&](group_type* pg,unsigned int n,element_type* p) + {f(pg,n,p);return true;}); + } + + template + void for_all_elements_while(F f)const + { + for_all_elements_while(arrays,f); + } + + template + static auto for_all_elements_while(const arrays_type& arrays_,F f) + ->decltype(f(nullptr),void()) + { + for_all_elements_while( + arrays_,[&](group_type*,unsigned int,element_type* p){return f(p);}); + } + + template + static auto for_all_elements_while(const arrays_type& arrays_,F f) + ->decltype(f(nullptr,0,nullptr),void()) + { + auto p=arrays_.elements; + if(!p){return;} + for(auto pg=arrays_.groups,last=pg+arrays_.groups_size_mask+1; + pg!=last;++pg,p+=N){ + auto mask=pg->match_really_occupied(); + while(mask){ + auto n=unchecked_countr_zero(mask); + if(!f(pg,n,p+n))return; + mask&=mask-1; + } + } + } + + std::size_t size_; + arrays_type arrays; + std::size_t ml; + +private: + template friend class table_core; + + using hash_base=empty_value; + using pred_base=empty_value; + using allocator_base=empty_value; + + arrays_type new_arrays(std::size_t n) + { + return arrays_type::new_(al(),n); + } + + void delete_arrays(arrays_type& arrays_)noexcept + { + arrays_type::delete_(al(),arrays_); + } + + template + void construct_element_from_try_emplace_args( + element_type* p,std::false_type,Key&& x,Args&&... args) + { + type_policy::construct( + this->al(),p, + std::piecewise_construct, + std::forward_as_tuple(std::forward(x)), + std::forward_as_tuple(std::forward(args)...)); + } + + /* This overload allows boost::unordered_flat_set to internally use + * try_emplace to implement heterogeneous insert (P2363). + */ + + template + void construct_element_from_try_emplace_args( + element_type* p,std::true_type,Key&& x) + { + type_policy::construct(this->al(),p,std::forward(x)); + } + + void copy_elements_from(const table_core& x) + { + BOOST_ASSERT(empty()); + BOOST_ASSERT(this!=std::addressof(x)); + if(arrays.groups_size_mask==x.arrays.groups_size_mask){ + fast_copy_elements_from(x); + } + else{ + x.for_all_elements([this](const element_type* p){ + unchecked_insert(*p); + }); + } + } + + void fast_copy_elements_from(const table_core& x) + { + if(arrays.elements){ + copy_elements_array_from(x); + std::memcpy( + arrays.groups,x.arrays.groups, + (arrays.groups_size_mask+1)*sizeof(group_type)); + size_=x.size(); + } + } + + void copy_elements_array_from(const table_core& x) + { + copy_elements_array_from( + x, + std::integral_constant< + bool, +#if BOOST_WORKAROUND(BOOST_LIBSTDCXX_VERSION,<50000) + /* std::is_trivially_copy_constructible not provided */ + boost::has_trivial_copy::value +#else + std::is_trivially_copy_constructible::value +#endif + &&( + is_std_allocator::value|| + !alloc_has_construct::value) + >{} + ); + } + + void copy_elements_array_from( + const table_core& x,std::true_type /* -> memcpy */) + { + /* reinterpret_cast: GCC may complain about value_type not being trivially + * copy-assignable when we're relying on trivial copy constructibility. + */ + std::memcpy( + reinterpret_cast(arrays.elements), + reinterpret_cast(x.arrays.elements), + x.capacity()*sizeof(value_type)); + } + + void copy_elements_array_from( + const table_core& x,std::false_type /* -> manual */) + { + std::size_t num_constructed=0; + BOOST_TRY{ + x.for_all_elements([&,this](const element_type* p){ + construct_element(arrays.elements+(p-x.arrays.elements),*p); + ++num_constructed; + }); + } + BOOST_CATCH(...){ + if(num_constructed){ + x.for_all_elements_while([&,this](const element_type* p){ + destroy_element(arrays.elements+(p-x.arrays.elements)); + return --num_constructed!=0; + }); + } + BOOST_RETHROW + } + BOOST_CATCH_END + } + + void recover_slot(unsigned char* pc) + { + /* If this slot potentially caused overflow, we decrease the maximum load so + * that average probe length won't increase unboundedly in repeated + * insert/erase cycles (drift). + */ + ml-=group_type::maybe_caused_overflow(pc); + group_type::reset(pc); + --size_; + } + + void recover_slot(group_type* pg,std::size_t pos) + { + recover_slot(reinterpret_cast(pg)+pos); + } + + std::size_t initial_max_load()const + { + static constexpr std::size_t small_capacity=2*N-1; + + auto capacity_=capacity(); + if(capacity_<=small_capacity){ + return capacity_; /* we allow 100% usage */ + } + else{ + return (std::size_t)(mlf*(float)(capacity_)); + } + } + + static std::size_t capacity_for(std::size_t n) + { + return size_policy::size(size_index_for(n))*N-1; + } + BOOST_NOINLINE void unchecked_rehash(std::size_t n) { auto new_arrays_=new_arrays(n); @@ -2151,17 +1850,7 @@ private: } template - iterator unchecked_emplace_at( - std::size_t pos0,std::size_t hash,Args&&... args) - { - auto res=nosize_unchecked_emplace_at( - arrays,pos0,hash,std::forward(args)...); - ++size_; - return res; - } - - template - iterator nosize_unchecked_emplace_at( + locator nosize_unchecked_emplace_at( const arrays_type& arrays_,std::size_t pos0,std::size_t hash, Args&&... args) { @@ -2179,73 +1868,6 @@ private: else pg->mark_overflow(hash); } } - - template - std::size_t erase_if_impl(Predicate pr) - { - std::size_t s=size(); - for_all_elements([&,this](group_type* pg,unsigned int n,element_type* p){ - if(pr(type_policy::value_from(*p))) erase(iterator{pg,n,p}); - }); - return std::size_t(s-size()); - } - - template - void for_all_elements(F f)const - { - for_all_elements(arrays,f); - } - - template - static auto for_all_elements(const arrays_type& arrays_,F f) - ->decltype(f(nullptr),void()) - { - for_all_elements_while(arrays_,[&](element_type* p){f(p);return true;}); - } - - template - static auto for_all_elements(const arrays_type& arrays_,F f) - ->decltype(f(nullptr,0,nullptr),void()) - { - for_all_elements_while( - arrays_,[&](group_type* pg,unsigned int n,element_type* p) - {f(pg,n,p);return true;}); - } - - template - void for_all_elements_while(F f)const - { - for_all_elements_while(arrays,f); - } - - template - static auto for_all_elements_while(const arrays_type& arrays_,F f) - ->decltype(f(nullptr),void()) - { - for_all_elements_while( - arrays_,[&](group_type*,unsigned int,element_type* p){return f(p);}); - } - - template - static auto for_all_elements_while(const arrays_type& arrays_,F f) - ->decltype(f(nullptr,0,nullptr),void()) - { - auto p=arrays_.elements; - if(!p){return;} - for(auto pg=arrays_.groups,last=pg+arrays_.groups_size_mask+1; - pg!=last;++pg,p+=N){ - auto mask=pg->match_really_occupied(); - while(mask){ - auto n=unchecked_countr_zero(mask); - if(!f(pg,n,p+n))return; - mask&=mask-1; - } - } - } - - std::size_t size_; - arrays_type arrays; - std::size_t ml; }; #if BOOST_WORKAROUND(BOOST_MSVC,<=1900) @@ -2266,6 +1888,5 @@ private: } /* namespace boost */ #undef BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED -#undef BOOST_UNORDERED_ASSUME #undef BOOST_UNORDERED_HAS_BUILTIN #endif diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp new file mode 100644 index 00000000..c1b3972c --- /dev/null +++ b/include/boost/unordered/detail/foa/table.hpp @@ -0,0 +1,512 @@ +/* Fast open-addressing hash table. + * + * Copyright 2022-2023 Joaquin M Lopez Munoz. + * 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. + */ + +#ifndef BOOST_UNORDERED_DETAIL_FOA_TABLE_HPP +#define BOOST_UNORDERED_DETAIL_FOA_TABLE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost{ +namespace unordered{ +namespace detail{ +namespace foa{ + +template +class table; + +/* table_iterator keeps two pointers: + * + * - A pointer p to the element slot. + * - A pointer pc to the n-th byte of the associated group metadata, where n + * is the position of the element in the group. + * + * A simpler solution would have been to keep a pointer p to the element, a + * pointer pg to the group, and the position n, but that would increase + * sizeof(table_iterator) by 4/8 bytes. In order to make this compact + * representation feasible, it is required that group objects are aligned + * to their size, so that we can recover pg and n as + * + * - n = pc%sizeof(group) + * - pg = pc-n + * + * (for explanatory purposes pg and pc are treated above as if they were memory + * addresses rather than pointers). + * + * p = nullptr is conventionally used to mark end() iterators. + */ + +/* internal conversion from const_iterator to iterator */ +struct const_iterator_cast_tag{}; + +template +class table_iterator +{ + using type_policy=TypePolicy; + using table_element_type=typename type_policy::element_type; + using group_type=Group; + static constexpr auto N=group_type::N; + static constexpr auto regular_layout=group_type::regular_layout; + +public: + using difference_type=std::ptrdiff_t; + using value_type=typename type_policy::value_type; + using pointer= + typename std::conditional::type; + using reference= + typename std::conditional::type; + using iterator_category=std::forward_iterator_tag; + using element_type= + typename std::conditional::type; + + table_iterator()=default; + template::type* =nullptr> + table_iterator(const table_iterator& x): + pc{x.pc},p{x.p}{} + table_iterator( + const_iterator_cast_tag, const table_iterator& x): + pc{x.pc},p{x.p}{} + + inline reference operator*()const noexcept{return type_policy::value_from(*p);} + inline pointer operator->()const noexcept + {return std::addressof(type_policy::value_from(*p));} + inline table_iterator& operator++()noexcept{increment();return *this;} + inline table_iterator operator++(int)noexcept + {auto x=*this;increment();return x;} + friend inline bool operator==( + const table_iterator& x,const table_iterator& y) + {return x.p==y.p;} + friend inline bool operator!=( + const table_iterator& x,const table_iterator& y) + {return !(x==y);} + +private: + template friend class table_iterator; + template friend class table; + + table_iterator(Group* pg,std::size_t n,const table_element_type* p_): + pc{reinterpret_cast(const_cast(pg))+n}, + p{const_cast(p_)} + {} + + inline void increment()noexcept + { + BOOST_ASSERT(p!=nullptr); + increment(std::integral_constant{}); + } + + inline void increment(std::true_type /* regular layout */)noexcept + { + for(;;){ + ++p; + if(reinterpret_cast(pc)%sizeof(group_type)==N-1){ + pc+=sizeof(group_type)-(N-1); + break; + } + ++pc; + if(!group_type::is_occupied(pc))continue; + if(BOOST_UNLIKELY(group_type::is_sentinel(pc)))p=nullptr; + return; + } + + for(;;){ + int mask=reinterpret_cast(pc)->match_occupied(); + if(mask!=0){ + auto n=unchecked_countr_zero(mask); + if(BOOST_UNLIKELY(reinterpret_cast(pc)->is_sentinel(n))){ + p=nullptr; + } + else{ + pc+=n; + p+=n; + } + return; + } + pc+=sizeof(group_type); + p+=N; + } + } + + inline void increment(std::false_type /* interleaved */)noexcept + { + std::size_t n0=reinterpret_cast(pc)%sizeof(group_type); + pc-=n0; + + int mask=( + reinterpret_cast(pc)->match_occupied()>>(n0+1))<<(n0+1); + if(!mask){ + do{ + pc+=sizeof(group_type); + p+=N; + } + while((mask=reinterpret_cast(pc)->match_occupied())==0); + } + + auto n=unchecked_countr_zero(mask); + if(BOOST_UNLIKELY(reinterpret_cast(pc)->is_sentinel(n))){ + p=nullptr; + } + else{ + pc+=n; + p-=n0; + p+=n; + } + } + + unsigned char *pc=nullptr; + table_element_type *p=nullptr; +}; + +template +union uninitialized_storage +{ + T t_; + uninitialized_storage(){} + ~uninitialized_storage(){} +}; + +/* foa::table interface departs in a number of ways from that of C++ unordered + * associative containers because it's not for end-user consumption + * (boost::unordered_[flat|node]_[map|set]) wrappers complete it as + * appropriate). + * + * The table supports two main modes of operation: flat and node-based. In the + * flat case, buckets directly store elements. For node-based, buckets store + * pointers to individually heap-allocated elements. + * + * For both flat and node-based: + * + * - begin() is not O(1). + * - No bucket API. + * - Load factor is fixed and can't be set by the user. + * + * For flat only: + * + * - value_type must be moveable. + * - Pointer stability is not kept under rehashing. + * - No extract API. + * + * try_emplace, erase and find support heterogenous lookup by default, that is, + * without checking for any ::is_transparent typedefs --the checking is done by + * boost::unordered_[flat|node]_[map|set]. + */ + +template +class table:table_core +{ + using super=table_core; + using typename super::type_policy; + using typename super::group_type; + using super::N; + using typename super::prober; + using typename super::locator; + +public: + using typename super::key_type; + using typename super::init_type; + using typename super::value_type; + using typename super::element_type; + +private: + static constexpr bool has_mutable_iterator= + !std::is_same::value; + +public: + using typename super::hasher; + using typename super::key_equal; + using typename super::allocator_type; + using typename super::pointer; + using typename super::const_pointer; + using typename super::reference; + using typename super::const_reference; + using typename super::size_type; + using typename super::difference_type; + using const_iterator=table_iterator; + using iterator=typename std::conditional< + has_mutable_iterator, + table_iterator, + const_iterator>::type; + + table( + std::size_t n=0,const Hash& h_=Hash(),const Pred& pred_=Pred(), + const Allocator& al_=Allocator()): + super{n,h_,pred_,al_} + {} + + table(const table& x)=default; + table(table&& x)=default; + table(const table& x,const Allocator& al_):super{x,al_}{} + table(table&& x,const Allocator& al_):super{std::move(x),al_}{} + ~table()=default; + + table& operator=(const table& x)=default; + table& operator=(table&& x)=default; + + using super::get_allocator; + + iterator begin()noexcept + { + iterator it{this->arrays.groups,0,this->arrays.elements}; + if(!(this->arrays.groups[0].match_occupied()&0x1))++it; + return it; + } + + const_iterator begin()const noexcept + {return const_cast(this)->begin();} + iterator end()noexcept{return {};} + const_iterator end()const noexcept{return const_cast(this)->end();} + const_iterator cbegin()const noexcept{return begin();} + const_iterator cend()const noexcept{return end();} + + using super::empty; + using super::size; + using super::max_size; + + template + BOOST_FORCEINLINE std::pair emplace(Args&&... args) + { + using emplace_type=typename std::conditional< + std::is_constructible::value, + init_type, + value_type + >::type; + + using insert_type=typename std::conditional< + std::is_constructible< + value_type,emplace_type>::value, + emplace_type,element_type + >::type; + + uninitialized_storage s; + auto *p=std::addressof(s.t_); + + type_policy::construct(this->al(),p,std::forward(args)...); + + destroy_on_exit guard{this->al(),p}; + return emplace_impl(type_policy::move(*p)); + } + + template + BOOST_FORCEINLINE std::pair try_emplace( + Key&& x,Args&&... args) + { + return emplace_impl( + try_emplace_args_t{},std::forward(x),std::forward(args)...); + } + + BOOST_FORCEINLINE std::pair + insert(const init_type& x){return emplace_impl(x);} + + BOOST_FORCEINLINE std::pair + insert(init_type&& x){return emplace_impl(std::move(x));} + + /* template tilts call ambiguities in favor of init_type */ + + template + BOOST_FORCEINLINE std::pair + insert(const value_type& x){return emplace_impl(x);} + + template + BOOST_FORCEINLINE std::pair + insert(value_type&& x){return emplace_impl(std::move(x));} + + template + BOOST_FORCEINLINE + typename std::enable_if< + !std::is_same::value, + std::pair + >::type + insert(element_type&& x){return emplace_impl(std::move(x));} + + template< + bool dependent_value=false, + typename std::enable_if< + has_mutable_iterator||dependent_value>::type* =nullptr + > + void erase(iterator pos)noexcept{return erase(const_iterator(pos));} + + BOOST_FORCEINLINE + void erase(const_iterator pos)noexcept + { + super::erase(pos.pc,pos.p); + } + + template + BOOST_FORCEINLINE + auto erase(Key&& x) -> typename std::enable_if< + !std::is_convertible::value&& + !std::is_convertible::value, std::size_t>::type + { + auto it=find(x); + if(it!=end()){ + erase(it); + return 1; + } + else return 0; + } + + void swap(table& x) + noexcept(std::declval().swap(std::declval())) + { + super::swap(x); + } + + using super::clear; + + element_type extract(const_iterator pos) + { + BOOST_ASSERT(pos!=end()); + erase_on_exit e{*this,pos}; + (void)e; + return std::move(*pos.p); + } + + // TODO: should we accept different allocator too? + template + void merge(table& x) + { + x.for_all_elements([&,this](group_type* pg,unsigned int n,element_type* p){ + erase_on_exit e{x,{pg,n,p}}; + if(!emplace_impl(type_policy::move(*p)).second)e.rollback(); + }); + } + + template + void merge(table&& x){merge(x);} + + using super::hash_function; + using super::key_eq; + + template + BOOST_FORCEINLINE iterator find(const Key& x) + { + auto hash=this->hash_for(x); + return find_impl(x,this->position_for(hash),hash); + } + + template + BOOST_FORCEINLINE const_iterator find(const Key& x)const + { + return const_cast(this)->find(x); + } + + using super::capacity; + using super::load_factor; + using super::max_load; + using super::rehash; + using super::reserve; + + template + friend std::size_t erase_if(table& x,Predicate pr) + { + return x.erase_if_impl(pr); + } + +private: + using super::destroy_on_exit; + + struct erase_on_exit + { + erase_on_exit(table& x_,const_iterator it_):x{x_},it{it_}{} + ~erase_on_exit(){if(!rollback_)x.erase(it);} + + void rollback(){rollback_=true;} + + table& x; + const_iterator it; + bool rollback_=false; + }; + + static inline iterator make_iterator(const locator& l)noexcept + { + return {l.pg,l.n,l.p}; + } + +#if defined(BOOST_MSVC) +/* warning: forcing value to bool 'true' or 'false' in bool(pred()...) */ +#pragma warning(push) +#pragma warning(disable:4800) +#endif + + template + BOOST_FORCEINLINE iterator find_impl( + const Key& x,std::size_t pos0,std::size_t hash)const + { + prober pb(pos0); + do{ + auto pos=pb.get(); + auto pg=this->arrays.groups+pos; + auto mask=pg->match(hash); + if(mask){ + BOOST_UNORDERED_ASSUME(this->arrays.elements!=nullptr); + auto p=this->arrays.elements+pos*N; + this->prefetch_elements(p); + do{ + auto n=unchecked_countr_zero(mask); + if(BOOST_LIKELY(bool(this->pred()(x,this->key_from(p[n]))))){ + return {pg,n,p+n}; + } + mask&=mask-1; + }while(mask); + } + if(BOOST_LIKELY(pg->is_not_overflowed(hash))){ + return {}; /* end() */ + } + } + while(BOOST_LIKELY(pb.next(this->arrays.groups_size_mask))); + return {}; /* end() */ + } + +#if defined(BOOST_MSVC) +#pragma warning(pop) /* C4800 */ +#endif + + template + BOOST_FORCEINLINE std::pair emplace_impl(Args&&... args) + { + const auto &k=this->key_from(std::forward(args)...); + auto hash=this->hash_for(k); + auto pos0=this->position_for(hash); + auto it=find_impl(k,pos0,hash); + + if(it!=end()){ + return {it,false}; + } + if(BOOST_LIKELY(this->size_ml)){ + return { + make_iterator( + this->unchecked_emplace_at(pos0,hash,std::forward(args)...)), + true + }; + } + else{ + return { + make_iterator( + this->unchecked_emplace_with_rehash( + hash,std::forward(args)...)), + true + }; + } + } +}; + +} /* namespace foa */ +} /* namespace detail */ +} /* namespace unordered */ +} /* namespace boost */ + +#endif diff --git a/include/boost/unordered/unordered_flat_map.hpp b/include/boost/unordered/unordered_flat_map.hpp index 7fb736f6..a7b6482b 100644 --- a/include/boost/unordered/unordered_flat_map.hpp +++ b/include/boost/unordered/unordered_flat_map.hpp @@ -10,7 +10,7 @@ #pragma once #endif -#include +#include #include #include diff --git a/include/boost/unordered/unordered_flat_set.hpp b/include/boost/unordered/unordered_flat_set.hpp index 2138a9f3..4562b1e5 100644 --- a/include/boost/unordered/unordered_flat_set.hpp +++ b/include/boost/unordered/unordered_flat_set.hpp @@ -10,7 +10,7 @@ #pragma once #endif -#include +#include #include #include diff --git a/include/boost/unordered/unordered_node_map.hpp b/include/boost/unordered/unordered_node_map.hpp index 450e7809..6c598354 100644 --- a/include/boost/unordered/unordered_node_map.hpp +++ b/include/boost/unordered/unordered_node_map.hpp @@ -10,9 +10,9 @@ #pragma once #endif -#include #include #include +#include #include #include diff --git a/include/boost/unordered/unordered_node_set.hpp b/include/boost/unordered/unordered_node_set.hpp index 30a63502..2e2a9dd4 100644 --- a/include/boost/unordered/unordered_node_set.hpp +++ b/include/boost/unordered/unordered_node_set.hpp @@ -10,9 +10,9 @@ #pragma once #endif -#include #include #include +#include #include #include From 8f39001ff044e01fa0174891fa81ca61b2d7563f Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 10 Mar 2023 18:52:11 +0100 Subject: [PATCH 002/327] fixed trivial syntax error --- include/boost/unordered/detail/foa/core.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 395e5812..43f87595 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1360,7 +1360,7 @@ protected: float load_factor()const noexcept { - if capacity()==0)return 0; + if(capacity()==0)return 0; else return float(size())/float(capacity()); } From ead55a8938dbed889851634fb160c62bf6aff72c Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 10 Mar 2023 19:12:06 +0100 Subject: [PATCH 003/327] s/noexcept(...)/noexcept(noexcept(...)) --- include/boost/unordered/detail/foa/table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index c1b3972c..9fc94d39 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -360,7 +360,7 @@ public: } void swap(table& x) - noexcept(std::declval().swap(std::declval())) + noexcept(noexcept(std::declval().swap(std::declval()))) { super::swap(x); } From dac11351994d7963e946e245030f38991a3a2200 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 10 Mar 2023 19:20:44 +0100 Subject: [PATCH 004/327] fixed usage syntax for destroy_on_exit --- include/boost/unordered/detail/foa/table.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 9fc94d39..07fa0c58 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -296,7 +296,7 @@ public: type_policy::construct(this->al(),p,std::forward(args)...); - destroy_on_exit guard{this->al(),p}; + typename destroy_on_exit guard{this->al(),p}; return emplace_impl(type_policy::move(*p)); } @@ -417,8 +417,6 @@ public: } private: - using super::destroy_on_exit; - struct erase_on_exit { erase_on_exit(table& x_,const_iterator it_):x{x_},it{it_}{} From 18725a682d3d3180c720b0393e5a1b0fb5f08a53 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 10 Mar 2023 19:22:48 +0100 Subject: [PATCH 005/327] fixed previous fix --- include/boost/unordered/detail/foa/table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 07fa0c58..bff1a021 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -296,7 +296,7 @@ public: type_policy::construct(this->al(),p,std::forward(args)...); - typename destroy_on_exit guard{this->al(),p}; + typename super::destroy_on_exit guard{this->al(),p}; return emplace_impl(type_policy::move(*p)); } From ccbc6396770dc173d39c7393f8f830aa1a2d2b2c Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 10 Mar 2023 19:42:26 +0100 Subject: [PATCH 006/327] made table_core protected interface public --- include/boost/unordered/detail/foa/core.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 43f87595..9358e52b 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1093,7 +1093,7 @@ __declspec(empty_bases) /* activate EBO with multiple inheritance */ table_core:empty_value,empty_value,empty_value { -protected: +public: using type_policy=TypePolicy; using group_type=group15; static constexpr auto N=group_type::N; From a5100a9d3553f675c480ba4e174c2e6299f036b2 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 10 Mar 2023 19:55:51 +0100 Subject: [PATCH 007/327] fixed incomplete porting of PR187 --- include/boost/unordered/detail/foa/table.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index bff1a021..2c14631a 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -261,7 +261,8 @@ public: iterator begin()noexcept { iterator it{this->arrays.groups,0,this->arrays.elements}; - if(!(this->arrays.groups[0].match_occupied()&0x1))++it; + if(this->arrays.elements&& + !(this->arrays.groups[0].match_occupied()&0x1))++it; return it; } From 27f551391159095e944fac209e78c897f61dfe9f Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 10 Mar 2023 19:59:44 +0100 Subject: [PATCH 008/327] published max_load_factor --- include/boost/unordered/detail/foa/table.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 2c14631a..c39c6027 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -407,6 +407,7 @@ public: using super::capacity; using super::load_factor; + using super::max_load_factor; using super::max_load; using super::rehash; using super::reserve; From 2ef47f08135baf5a79605d5ac9d5af669b2190a3 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 11 Mar 2023 09:32:01 +0100 Subject: [PATCH 009/327] reformulated using to workaround VS problem --- include/boost/unordered/detail/foa/table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index c39c6027..6fa48515 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -226,7 +226,7 @@ private: !std::is_same::value; public: - using typename super::hasher; + using hasher=typename super::hasher; using typename super::key_equal; using typename super::allocator_type; using typename super::pointer; From b3b840df302a6cc45b3c22aec8f652244468a296 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 11 Mar 2023 09:43:11 +0100 Subject: [PATCH 010/327] extended Wshadow supression to foa::table --- include/boost/unordered/detail/foa/core.hpp | 54 ++++++------------- .../unordered/detail/foa/ignore_wshadow.hpp | 35 ++++++++++++ .../unordered/detail/foa/restore_wshadow.hpp | 11 ++++ include/boost/unordered/detail/foa/table.hpp | 4 ++ 4 files changed, 66 insertions(+), 38 deletions(-) create mode 100644 include/boost/unordered/detail/foa/ignore_wshadow.hpp create mode 100644 include/boost/unordered/detail/foa/restore_wshadow.hpp diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 9358e52b..c4fb08f4 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -991,41 +991,6 @@ _STL_RESTORE_DEPRECATED_WARNING #pragma warning(pop) #endif -#if defined(BOOST_GCC) -/* GCC's -Wshadow triggers at scenarios like this: - * - * struct foo{}; - * template - * struct derived:Base - * { - * void f(){int foo;} - * }; - * - * derivedx; - * x.f(); // declaration of "foo" in derived::f shadows base type "foo" - * - * This makes shadowing warnings unavoidable in general when a class template - * derives from user-provided classes, as is the case with table_core and - * empty_value's below. - */ - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wshadow" -#endif - -#if defined(BOOST_MSVC) -#pragma warning(push) -#pragma warning(disable:4714) /* marked as __forceinline not inlined */ -#endif - -#if BOOST_WORKAROUND(BOOST_MSVC,<=1900) -/* VS2015 marks as unreachable generic catch clauses around non-throwing - * code. - */ -#pragma warning(push) -#pragma warning(disable:4702) -#endif - /* We expose the hard-coded max load factor so that tests can use it without * needing to pull it from an instantiated class template such as the table * class. @@ -1084,6 +1049,21 @@ struct try_emplace_args_t{}; * the element_type itself is moved. */ +#include + +#if defined(BOOST_MSVC) +#pragma warning(push) +#pragma warning(disable:4714) /* marked as __forceinline not inlined */ +#endif + +#if BOOST_WORKAROUND(BOOST_MSVC,<=1900) +/* VS2015 marks as unreachable generic catch clauses around non-throwing + * code. + */ +#pragma warning(push) +#pragma warning(disable:4702) +#endif + template class @@ -1878,9 +1858,7 @@ private: #pragma warning(pop) /* C4714 */ #endif -#if defined(BOOST_GCC) -#pragma GCC diagnostic pop /* ignored "-Wshadow" */ -#endif +#include } /* namespace foa */ } /* namespace detail */ diff --git a/include/boost/unordered/detail/foa/ignore_wshadow.hpp b/include/boost/unordered/detail/foa/ignore_wshadow.hpp new file mode 100644 index 00000000..f84262bc --- /dev/null +++ b/include/boost/unordered/detail/foa/ignore_wshadow.hpp @@ -0,0 +1,35 @@ +/* Copyright 2023 Joaquin M Lopez Munoz. + * 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. + */ + +#include + +#if defined(BOOST_GCC) +#if !defined(BOOST_UNORDERED_DETAIL_RESTORE_WSHADOW) + /* GCC's -Wshadow triggers at scenarios like this: + * + * struct foo{}; + * template + * struct derived:Base + * { + * void f(){int foo;} + * }; + * + * derivedx; + * x.f(); // declaration of "foo" in derived::f shadows base type "foo" + * + * This makes shadowing warnings unavoidable in general when a class template + * derives from user-provided classes, as is the case with foa::table_core + * deriving from empty_value. + */ + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshadow" +#else +#pragma GCC diagnostic pop +#endif +#endif diff --git a/include/boost/unordered/detail/foa/restore_wshadow.hpp b/include/boost/unordered/detail/foa/restore_wshadow.hpp new file mode 100644 index 00000000..89c32c23 --- /dev/null +++ b/include/boost/unordered/detail/foa/restore_wshadow.hpp @@ -0,0 +1,11 @@ +/* Copyright 2023 Joaquin M Lopez Munoz. + * 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. + */ + +#define BOOST_UNORDERED_DETAIL_RESTORE_WSHADOW +#include +#undef BOOST_UNORDERED_DETAIL_RESTORE_WSHADOW diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 6fa48515..fb958076 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -205,6 +205,8 @@ union uninitialized_storage * boost::unordered_[flat|node]_[map|set]. */ +#include + template class table:table_core { @@ -504,6 +506,8 @@ private: } }; +#include + } /* namespace foa */ } /* namespace detail */ } /* namespace unordered */ From b08837b93e015c1dd922b0280141447cb2926e47 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 11 Mar 2023 09:58:38 +0100 Subject: [PATCH 011/327] added missing template keyword --- include/boost/unordered/detail/foa/table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index fb958076..3dd969d4 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -299,7 +299,7 @@ public: type_policy::construct(this->al(),p,std::forward(args)...); - typename super::destroy_on_exit guard{this->al(),p}; + typename super::template destroy_on_exit guard{this->al(),p}; return emplace_impl(type_policy::move(*p)); } From b33b354818e6b1da3bbffde60b51da0c42f6dc5c Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 11 Mar 2023 10:01:12 +0100 Subject: [PATCH 012/327] extended caae8eb9ac9a47dd224b81f22efe8ea34b084d54 to rest of "using typename"s --- include/boost/unordered/detail/foa/table.hpp | 32 ++++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 3dd969d4..508e8deb 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -211,17 +211,17 @@ template class table:table_core { using super=table_core; - using typename super::type_policy; - using typename super::group_type; + using type_policy=typename super::type_policy; + using group_type=typename super::group_type; using super::N; - using typename super::prober; - using typename super::locator; + using prober=typename super::prober; + using locator=typename super::locator; public: - using typename super::key_type; - using typename super::init_type; - using typename super::value_type; - using typename super::element_type; + using key_type=typename super::key_type; + using init_type=typename super::init_type; + using value_type=typename super::value_type; + using element_type=typename super::element_type; private: static constexpr bool has_mutable_iterator= @@ -229,14 +229,14 @@ private: public: using hasher=typename super::hasher; - using typename super::key_equal; - using typename super::allocator_type; - using typename super::pointer; - using typename super::const_pointer; - using typename super::reference; - using typename super::const_reference; - using typename super::size_type; - using typename super::difference_type; + using key_equal=typename super::key_equal; + using allocator_type=typename super::allocator_type; + using pointer=typename super::pointer; + using const_pointer=typename super::const_pointer; + using reference=typename super::reference; + using const_reference=typename super::const_reference; + using size_type=typename super::size_type; + using difference_type=typename super::difference_type; using const_iterator=table_iterator; using iterator=typename std::conditional< has_mutable_iterator, From 3a8bea9e1df16f972218e5cfdad24cf1021a7629 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 11 Mar 2023 18:36:09 +0100 Subject: [PATCH 013/327] parameterized group15 and core --- include/boost/unordered/detail/foa/core.hpp | 103 ++++++++++++------- include/boost/unordered/detail/foa/table.hpp | 33 ++++-- 2 files changed, 91 insertions(+), 45 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index c4fb08f4..f316fab5 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -158,6 +159,7 @@ static constexpr std::size_t default_bucket_count=0; #if defined(BOOST_UNORDERED_SSE2) +template class IntegralWrapper> struct group15 { static constexpr int N=15; @@ -168,7 +170,11 @@ struct group15 alignas(16) unsigned char storage[N+1]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0}; }; - inline void initialize(){m=_mm_setzero_si128();} + inline void initialize() + { + _mm_store_si128( + reinterpret_cast<__m128i*>(m),_mm_setzero_si128()); + } inline void set(std::size_t pos,std::size_t hash) { @@ -200,13 +206,14 @@ struct group15 static inline void reset(unsigned char* pc) { - *pc=available_; + *reinterpret_cast(pc)=available_; } inline int match(std::size_t hash)const { + auto w=_mm_load_si128(reinterpret_cast(m)); return _mm_movemask_epi8( - _mm_cmpeq_epi8(m,_mm_set1_epi32(match_word(hash))))&0x7FFF; + _mm_cmpeq_epi8(w,_mm_set1_epi32(match_word(hash))))&0x7FFF; } inline bool is_not_overflowed(std::size_t hash)const @@ -218,12 +225,7 @@ struct group15 inline void mark_overflow(std::size_t hash) { -#if BOOST_WORKAROUND(BOOST_GCC,>=50000 && BOOST_GCC<60000) - overflow()=static_cast( - overflow()|static_cast(1<<(hash%8))); -#else overflow()|=static_cast(1<<(hash%8)); -#endif } static inline bool maybe_caused_overflow(unsigned char* pc) @@ -235,13 +237,14 @@ struct group15 inline int match_available()const { + auto w=_mm_load_si128(reinterpret_cast(m)); return _mm_movemask_epi8( - _mm_cmpeq_epi8(m,_mm_setzero_si128()))&0x7FFF; + _mm_cmpeq_epi8(w,_mm_setzero_si128()))&0x7FFF; } static inline bool is_occupied(unsigned char* pc)noexcept { - return *pc!=available_; + return *reinterpret_cast(pc)!=available_; } inline int match_occupied()const @@ -255,6 +258,9 @@ struct group15 } private: + using slot_type=IntegralWrapper; + BOOST_STATIC_ASSERT(sizeof(slot_type)==1); + static constexpr unsigned char available_=0, sentinel_=1; @@ -304,31 +310,32 @@ private: return narrow_cast(match_word(hash)); } - inline unsigned char& at(std::size_t pos) + inline slot_type& at(std::size_t pos) { - return reinterpret_cast(&m)[pos]; + return m[pos]; } - inline unsigned char at(std::size_t pos)const + inline const slot_type& at(std::size_t pos)const { - return reinterpret_cast(&m)[pos]; + return m[pos]; } - inline unsigned char& overflow() + inline slot_type& overflow() { return at(N); } - inline unsigned char overflow()const + inline const slot_type& overflow()const { return at(N); } - alignas(16) __m128i m; + alignas(16) slot_type m[16]; }; #elif defined(BOOST_UNORDERED_LITTLE_ENDIAN_NEON) +template class IntegralWrapper> struct group15 { static constexpr int N=15; @@ -339,7 +346,10 @@ struct group15 alignas(16) unsigned char storage[N+1]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0}; }; - inline void initialize(){m=vdupq_n_s8(0);} + inline void initialize() + { + vst1q_u8(reinterpret_cast(m),vdupq_n_s8(0)); + } inline void set(std::size_t pos,std::size_t hash) { @@ -360,7 +370,7 @@ struct group15 static inline bool is_sentinel(unsigned char* pc)noexcept { - return *pc==sentinel_; + return *reinterpret_cast(pc)==sentinel_; } inline void reset(std::size_t pos) @@ -371,13 +381,14 @@ struct group15 static inline void reset(unsigned char* pc) { - *pc=available_; + *reinterpret_cast(pc)=available_; } inline int match(std::size_t hash)const { return simde_mm_movemask_epi8(vceqq_s8( - m,vdupq_n_s8(static_cast(reduced_hash(hash)))))&0x7FFF; + vld1q_u8(reinterpret_cast(m)), + vdupq_n_s8(static_cast(reduced_hash(hash)))))&0x7FFF; } inline bool is_not_overflowed(std::size_t hash)const @@ -401,18 +412,21 @@ struct group15 inline int match_available()const { - return simde_mm_movemask_epi8(vceqq_s8(m,vdupq_n_s8(0)))&0x7FFF; + return simde_mm_movemask_epi8(vceqq_s8( + vld1q_u8(reinterpret_cast(m)), + vdupq_n_s8(0)))&0x7FFF; } static inline bool is_occupied(unsigned char* pc)noexcept { - return *pc!=available_; + return *reinterpret_cast(pc)!=available_; } inline int match_occupied()const { - return simde_mm_movemask_epi8( - vcgtq_u8(vreinterpretq_u8_s8(m),vdupq_n_u8(0)))&0x7FFF; + return simde_mm_movemask_epi8(vcgtq_u8( + vreinterpretq_u8_s8(vld1q_u8(reinterpret_cast(m))), + vdupq_n_u8(0)))&0x7FFF; } inline int match_really_occupied()const /* excluding sentinel */ @@ -421,6 +435,9 @@ struct group15 } private: + using slot_type=IntegralWrapper; + BOOST_STATIC_ASSERT(sizeof(slot_type)==1); + static constexpr unsigned char available_=0, sentinel_=1; @@ -473,31 +490,32 @@ private: #endif } - inline unsigned char& at(std::size_t pos) + inline slot_type& at(std::size_t pos) { - return reinterpret_cast(&m)[pos]; + return m[pos]; } - inline unsigned char at(std::size_t pos)const + inline const slot_type& at(std::size_t pos)const { - return reinterpret_cast(&m)[pos]; + return m[pos]; } - inline unsigned char& overflow() + inline slot_type& overflow() { return at(N); } - inline unsigned char overflow()const + inline const slot_type& overflow()const { return at(N); } - alignas(16) int8x16_t m; + alignas(16) slot_type m[16]; }; #else /* non-SIMD */ +template class IntegralWrapper> struct group15 { static constexpr int N=15; @@ -590,6 +608,9 @@ struct group15 } private: + using word_type=IntegralWrapper; + BOOST_STATIC_ASSERT(sizeof(slot_type)==1); + static constexpr unsigned char available_=0, sentinel_=1; @@ -624,7 +645,7 @@ private: set_impl(m[1],pos,n>>4); } - static inline void set_impl(boost::uint64_t& x,std::size_t pos,std::size_t n) + static inline void set_impl(word_type& x,std::size_t pos,std::size_t n) { static constexpr boost::uint64_t mask[]= { @@ -670,7 +691,7 @@ private: return y&0x7FFF; } - alignas(16) boost::uint64_t m[2]; + alignas(16) word_type m[2]; }; #endif @@ -1064,7 +1085,10 @@ struct try_emplace_args_t{}; #pragma warning(disable:4702) #endif -template +template< + typename TypePolicy,typename Group,typename Hash,typename Pred, + typename Allocator +> class #if defined(_MSC_VER)&&_MSC_FULL_VER>=190023918 @@ -1075,7 +1099,7 @@ table_core:empty_value,empty_value,empty_value { public: using type_policy=TypePolicy; - using group_type=group15; + using group_type=Group; static constexpr auto N=group_type::N; using size_policy=pow2_size_policy; using prober=pow2_quadratic_prober; @@ -1103,8 +1127,8 @@ public: using locator=table_locator; table_core( - std::size_t n=0,const Hash& h_=Hash(),const Pred& pred_=Pred(), - const Allocator& al_=Allocator()): + std::size_t n=default_bucket_count,const Hash& h_=Hash(), + const Pred& pred_=Pred(),const Allocator& al_=Allocator()): hash_base{empty_init,h_},pred_base{empty_init,pred_}, allocator_base{empty_init,al_},size_{0},arrays(new_arrays(n)), ml{initial_max_load()} @@ -1576,7 +1600,8 @@ public: std::size_t ml; private: - template friend class table_core; + template + friend class table_core; using hash_base=empty_value; using pred_base=empty_value; diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 508e8deb..3bc7a6d7 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -26,6 +27,24 @@ namespace unordered{ namespace detail{ namespace foa{ +template +struct plain_integral +{ + operator Integral()const{return n;} + void operator=(Integral m){n=m;} + + void operator|=(Integral m) + { +#if BOOST_WORKAROUND(BOOST_GCC,>=50000 && BOOST_GCC<60000) + n=static_cast(n|m); +#else + n|=m; +#endif + } + + Integral n; +}; + template class table; @@ -81,7 +100,8 @@ public: const_iterator_cast_tag, const table_iterator& x): pc{x.pc},p{x.p}{} - inline reference operator*()const noexcept{return type_policy::value_from(*p);} + inline reference operator*()const noexcept + {return type_policy::value_from(*p);} inline pointer operator->()const noexcept {return std::addressof(type_policy::value_from(*p));} inline table_iterator& operator++()noexcept{increment();return *this;} @@ -181,7 +201,7 @@ union uninitialized_storage /* foa::table interface departs in a number of ways from that of C++ unordered * associative containers because it's not for end-user consumption - * (boost::unordered_[flat|node]_[map|set]) wrappers complete it as + * (boost::unordered_[flat|node]_[map|set] wrappers complete it as * appropriate). * * The table supports two main modes of operation: flat and node-based. In the @@ -208,9 +228,10 @@ union uninitialized_storage #include template -class table:table_core +class table:table_core,Hash,Pred,Allocator> { - using super=table_core; + using super= + table_core,Hash,Pred,Allocator>; using type_policy=typename super::type_policy; using group_type=typename super::group_type; using super::N; @@ -244,8 +265,8 @@ public: const_iterator>::type; table( - std::size_t n=0,const Hash& h_=Hash(),const Pred& pred_=Pred(), - const Allocator& al_=Allocator()): + std::size_t n=default_bucket_count,const Hash& h_=Hash(), + const Pred& pred_=Pred(),const Allocator& al_=Allocator()): super{n,h_,pred_,al_} {} From 8f17f5f548db2b0fb44e597f2327f6cab9ac8fe4 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 11 Mar 2023 18:42:10 +0100 Subject: [PATCH 014/327] fixed static_assert --- include/boost/unordered/detail/foa/core.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index f316fab5..5096f5e8 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -609,7 +609,7 @@ struct group15 private: using word_type=IntegralWrapper; - BOOST_STATIC_ASSERT(sizeof(slot_type)==1); + BOOST_STATIC_ASSERT(sizeof(word_type)==8); static constexpr unsigned char available_=0, sentinel_=1; From 0590e3bf8cae3273abc8d5357c6cdf3d223b61d8 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 11 Mar 2023 20:17:17 +0100 Subject: [PATCH 015/327] typo --- include/boost/unordered/detail/foa/core.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 5096f5e8..0b94a026 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -419,7 +419,7 @@ struct group15 static inline bool is_occupied(unsigned char* pc)noexcept { - return *reinterpret_cast(pc)!=available_; + return *reinterpret_cast(pc)!=available_; } inline int match_occupied()const From c1b63f68c865edb77ed91a723b3f79b6865d5c05 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 11 Mar 2023 20:19:24 +0100 Subject: [PATCH 016/327] added missing op to plain_integral --- include/boost/unordered/detail/foa/table.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 3bc7a6d7..69d0b219 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -42,6 +42,15 @@ struct plain_integral #endif } + void operator&=(Integral m) + { +#if BOOST_WORKAROUND(BOOST_GCC,>=50000 && BOOST_GCC<60000) + n=static_cast(n&m); +#else + n&=m; +#endif + } + Integral n; }; From 9bbadfdb34e52cba690855fd68e2e1b760297c73 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 11 Mar 2023 22:00:30 +0100 Subject: [PATCH 017/327] fixed sign issues in Neon ops --- include/boost/unordered/detail/foa/core.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 0b94a026..5e6cb368 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -386,9 +386,9 @@ struct group15 inline int match(std::size_t hash)const { - return simde_mm_movemask_epi8(vceqq_s8( + return simde_mm_movemask_epi8(vceqq_u8( vld1q_u8(reinterpret_cast(m)), - vdupq_n_s8(static_cast(reduced_hash(hash)))))&0x7FFF; + vdupq_n_u8(reduced_hash(hash))))&0x7FFF; } inline bool is_not_overflowed(std::size_t hash)const @@ -412,9 +412,9 @@ struct group15 inline int match_available()const { - return simde_mm_movemask_epi8(vceqq_s8( + return simde_mm_movemask_epi8(vceqq_u8( vld1q_u8(reinterpret_cast(m)), - vdupq_n_s8(0)))&0x7FFF; + vdupq_n_u8(0)))&0x7FFF; } static inline bool is_occupied(unsigned char* pc)noexcept @@ -425,7 +425,7 @@ struct group15 inline int match_occupied()const { return simde_mm_movemask_epi8(vcgtq_u8( - vreinterpretq_u8_s8(vld1q_u8(reinterpret_cast(m))), + vld1q_u8(reinterpret_cast(m))), vdupq_n_u8(0)))&0x7FFF; } From e037ee8d82f1caf9574ca96ef79a0d594f17a685 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 11 Mar 2023 22:19:43 +0100 Subject: [PATCH 018/327] fixed syntax error --- include/boost/unordered/detail/foa/core.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 5e6cb368..3cfe1f04 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -425,7 +425,7 @@ struct group15 inline int match_occupied()const { return simde_mm_movemask_epi8(vcgtq_u8( - vld1q_u8(reinterpret_cast(m))), + vld1q_u8(reinterpret_cast(m)), vdupq_n_u8(0)))&0x7FFF; } From 37ad547e18def8906d3822629d818a2e7e0f44ca Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 12 Mar 2023 00:42:36 +0100 Subject: [PATCH 019/327] more fixed sign issues in Neon ops --- include/boost/unordered/detail/foa/core.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 3cfe1f04..26c08da0 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -348,7 +348,7 @@ struct group15 inline void initialize() { - vst1q_u8(reinterpret_cast(m),vdupq_n_s8(0)); + vst1q_u8(reinterpret_cast(m),vdupq_n_u8(0)); } inline void set(std::size_t pos,std::size_t hash) From 4569c1bec081ecd958a5b1ba21a15aff52300f83 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 12 Mar 2023 00:54:52 +0100 Subject: [PATCH 020/327] changed type of N to std::size_t --- include/boost/unordered/detail/foa/core.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 26c08da0..563e81ba 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -162,8 +162,8 @@ static constexpr std::size_t default_bucket_count=0; template class IntegralWrapper> struct group15 { - static constexpr int N=15; - static constexpr bool regular_layout=true; + static constexpr std::size_t N=15; + static constexpr bool regular_layout=true; struct dummy_group_type { @@ -338,8 +338,8 @@ private: template class IntegralWrapper> struct group15 { - static constexpr int N=15; - static constexpr bool regular_layout=true; + static constexpr std::size_t N=15; + static constexpr bool regular_layout=true; struct dummy_group_type { @@ -518,8 +518,8 @@ private: template class IntegralWrapper> struct group15 { - static constexpr int N=15; - static constexpr bool regular_layout=false; + static constexpr std::size_t N=15; + static constexpr bool regular_layout=false; struct dummy_group_type { From 447306bfb98a191cbc8046d7babdf638d8a06d60 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 12 Mar 2023 11:14:40 +0100 Subject: [PATCH 021/327] stylistic --- include/boost/unordered/detail/foa/table.hpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 69d0b219..a155ab5b 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -33,23 +33,13 @@ struct plain_integral operator Integral()const{return n;} void operator=(Integral m){n=m;} - void operator|=(Integral m) - { #if BOOST_WORKAROUND(BOOST_GCC,>=50000 && BOOST_GCC<60000) - n=static_cast(n|m); + void operator|=(Integral m){n=static_cast(n|m);} + void operator&=(Integral m){n=static_cast(n&m);} #else - n|=m; + void operator|=(Integral m){n|=m;} + void operator&=(Integral m){n&=m;} #endif - } - - void operator&=(Integral m) - { -#if BOOST_WORKAROUND(BOOST_GCC,>=50000 && BOOST_GCC<60000) - n=static_cast(n&m); -#else - n&=m; -#endif - } Integral n; }; From 840ea1ce5ca5f3daba7bb16f91524fa9950d14bb Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 12 Mar 2023 15:52:27 +0100 Subject: [PATCH 022/327] refactored some more for future concurrent_table --- include/boost/unordered/detail/foa/core.hpp | 59 +++++++++++++++----- include/boost/unordered/detail/foa/table.hpp | 10 +--- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 563e81ba..175f7493 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -152,6 +152,8 @@ static constexpr std::size_t default_bucket_count=0; * "logical" 128-bit word, and so forth. With this layout, match can be * implemented with 4 ANDs, 3 shifts, 2 XORs, 1 OR and 1 NOT. * + * TODO: Explain IntegralWrapper. + * * group15 has no user-defined ctor so that it's a trivial type and can be * initialized via memset etc. Where needed, group15::initialize sets the * metadata to all zeros. @@ -242,6 +244,12 @@ struct group15 _mm_cmpeq_epi8(w,_mm_setzero_si128()))&0x7FFF; } + inline bool is_occupied(std::size_t pos)const + { + BOOST_ASSERT(pos(pc)!=available_; @@ -417,6 +425,12 @@ struct group15 vdupq_n_u8(0)))&0x7FFF; } + inline bool is_occupied(std::size_t pos)const + { + BOOST_ASSERT(pos(pc)!=available_; @@ -594,6 +608,13 @@ struct group15 return y&0x7FFF; } + inline bool is_occupied(std::size_t pos)const + { + BOOST_ASSERT(pos +union uninitialized_storage +{ + T t_; + uninitialized_storage(){} + ~uninitialized_storage(){} +}; + /* table_core. The TypePolicy template parameter is used to generate * instantiations suitable for either maps or sets, and introduces non-standard * init_type and element_type: @@ -1130,8 +1159,8 @@ public: std::size_t n=default_bucket_count,const Hash& h_=Hash(), const Pred& pred_=Pred(),const Allocator& al_=Allocator()): hash_base{empty_init,h_},pred_base{empty_init,pred_}, - allocator_base{empty_init,al_},size_{0},arrays(new_arrays(n)), - ml{initial_max_load()} + allocator_base{empty_init,al_},arrays(new_arrays(n)), + ml{initial_max_load()},available{ml} {} table_core(const table_core& x): @@ -1145,11 +1174,11 @@ public: hash_base{empty_init,std::move(x.h())}, pred_base{empty_init,std::move(x.pred())}, allocator_base{empty_init,std::move(x.al())}, - size_{x.size_},arrays(x.arrays),ml{x.ml} + arrays(x.arrays),ml{x.ml},available{x.available} { - x.size_=0; x.arrays=x.new_arrays(0); x.ml=x.initial_max_load(); + x.available=x.ml; } table_core(const table_core& x,const Allocator& al_): @@ -1162,9 +1191,9 @@ public: table_core{0,std::move(x.h()),std::move(x.pred()),al_} { if(al()==x.al()){ - std::swap(size_,x.size_); std::swap(arrays,x.arrays); std::swap(ml,x.ml); + std::swap(available,x.available); } else{ reserve(x.size()); @@ -1261,9 +1290,9 @@ public: if(pocma||al()==x.al()){ reserve(0); move_assign_if(al(),x.al()); - swap(size_,x.size_); swap(arrays,x.arrays); swap(ml,x.ml); + swap(available,x.available); } else{ /* noshrink: favor memory reuse over tightness */ @@ -1289,7 +1318,7 @@ public: allocator_type get_allocator()const noexcept{return al();} bool empty()const noexcept{return size()==0;} - std::size_t size()const noexcept{return size_;} + std::size_t size()const noexcept{return ml-available;} std::size_t max_size()const noexcept{return SIZE_MAX;} // TODO unify erase? @@ -1329,9 +1358,9 @@ public: swap(h(),x.h()); swap(pred(),x.pred()); - swap(size_,x.size_); swap(arrays,x.arrays); swap(ml,x.ml); + swap(available,x.available); } void clear()noexcept @@ -1349,8 +1378,8 @@ public: pg->initialize(); } arrays.groups[arrays.groups_size_mask].set_sentinel(); - size_=0; ml=initial_max_load(); + available=ml; } } @@ -1493,7 +1522,7 @@ public: { auto res=nosize_unchecked_emplace_at( arrays,pos0,hash,std::forward(args)...); - ++size_; + --available; return res; } @@ -1512,7 +1541,7 @@ public: * ideal conditions, yielding F ~ 0.0165 ~ 1/61. */ auto new_arrays_=new_arrays(std::size_t( - std::ceil(static_cast(size_+size_/61+1)/mlf))); + std::ceil(static_cast(size()+size()/61+1)/mlf))); locator it; BOOST_TRY{ /* strong exception guarantee -> try insertion before rehash */ @@ -1528,7 +1557,7 @@ public: /* new_arrays_ lifetime taken care of by unchecked_rehash */ unchecked_rehash(new_arrays_); - ++size_; + --available; return it; } @@ -1595,9 +1624,9 @@ public: } } - std::size_t size_; arrays_type arrays; std::size_t ml; + std::size_t available; private: template @@ -1660,7 +1689,7 @@ private: std::memcpy( arrays.groups,x.arrays.groups, (arrays.groups_size_mask+1)*sizeof(group_type)); - size_=x.size(); + available=x.available; } } @@ -1725,7 +1754,7 @@ private: */ ml-=group_type::maybe_caused_overflow(pc); group_type::reset(pc); - --size_; + ++available; } void recover_slot(group_type* pg,std::size_t pos) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index a155ab5b..6b9268c1 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -190,14 +190,6 @@ private: table_element_type *p=nullptr; }; -template -union uninitialized_storage -{ - T t_; - uninitialized_storage(){} - ~uninitialized_storage(){} -}; - /* foa::table interface departs in a number of ways from that of C++ unordered * associative containers because it's not for end-user consumption * (boost::unordered_[flat|node]_[map|set] wrappers complete it as @@ -508,7 +500,7 @@ private: if(it!=end()){ return {it,false}; } - if(BOOST_LIKELY(this->size_ml)){ + if(BOOST_LIKELY(this->available)){ return { make_iterator( this->unchecked_emplace_at(pos0,hash,std::forward(args)...)), From 8010f506a6789b516f531cca297e69ae71474032 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 12 Mar 2023 16:35:09 +0100 Subject: [PATCH 023/327] added SizeImpl and fixed some size arithmetic bugs --- include/boost/unordered/detail/foa/core.hpp | 29 ++++++++++++-------- include/boost/unordered/detail/foa/table.hpp | 11 +++++--- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 175f7493..e6e3fd1c 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1115,8 +1115,8 @@ union uninitialized_storage #endif template< - typename TypePolicy,typename Group,typename Hash,typename Pred, - typename Allocator + typename TypePolicy,typename Group,typename SizeImpl, + typename Hash,typename Pred,typename Allocator > class @@ -1160,7 +1160,7 @@ public: const Pred& pred_=Pred(),const Allocator& al_=Allocator()): hash_base{empty_init,h_},pred_base{empty_init,pred_}, allocator_base{empty_init,al_},arrays(new_arrays(n)), - ml{initial_max_load()},available{ml} + ml{initial_max_load()},available{std::size_t(ml)} {} table_core(const table_core& x): @@ -1174,11 +1174,11 @@ public: hash_base{empty_init,std::move(x.h())}, pred_base{empty_init,std::move(x.pred())}, allocator_base{empty_init,std::move(x.al())}, - arrays(x.arrays),ml{x.ml},available{x.available} + arrays(x.arrays),ml{std::size_t(x.ml)},available{std::size_t(x.available)} { x.arrays=x.new_arrays(0); x.ml=x.initial_max_load(); - x.available=x.ml; + x.available=std::size_t(x.ml); } table_core(const table_core& x,const Allocator& al_): @@ -1379,7 +1379,7 @@ public: } arrays.groups[arrays.groups_size_mask].set_sentinel(); ml=initial_max_load(); - available=ml; + available=std::size_t(ml); } } @@ -1625,11 +1625,11 @@ public: } arrays_type arrays; - std::size_t ml; - std::size_t available; + SizeImpl ml; + SizeImpl available; private: - template + template friend class table_core; using hash_base=empty_value; @@ -1689,7 +1689,7 @@ private: std::memcpy( arrays.groups,x.arrays.groups, (arrays.groups_size_mask+1)*sizeof(group_type)); - available=x.available; + available=std::size_t(x.available); } } @@ -1752,9 +1752,10 @@ private: * that average probe length won't increase unboundedly in repeated * insert/erase cycles (drift). */ - ml-=group_type::maybe_caused_overflow(pc); + auto ov=group_type::maybe_caused_overflow(pc); + ml-=ov; group_type::reset(pc); - ++available; + available+=!ov; } void recover_slot(group_type* pg,std::size_t pos) @@ -1820,7 +1821,9 @@ private: } delete_arrays(arrays); arrays=new_arrays_; + auto s=size(); ml=initial_max_load(); + available=ml-s; } void noshrink_reserve(std::size_t n) @@ -1836,7 +1839,9 @@ private: auto new_arrays_=new_arrays(n); delete_arrays(arrays); arrays=new_arrays_; + auto s=size(); ml=initial_max_load(); + available=ml-s; } } } diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 6b9268c1..96006dcc 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -216,13 +216,16 @@ private: * boost::unordered_[flat|node]_[map|set]. */ +template +using table_core_impl= + table_core,std::size_t,Hash,Pred,Allocator>; + #include template -class table:table_core,Hash,Pred,Allocator> +class table:table_core_impl { - using super= - table_core,Hash,Pred,Allocator>; + using super=table_core_impl; using type_policy=typename super::type_policy; using group_type=typename super::group_type; using super::N; @@ -500,7 +503,7 @@ private: if(it!=end()){ return {it,false}; } - if(BOOST_LIKELY(this->available)){ + if(BOOST_LIKELY(this->available!=0)){ return { make_iterator( this->unchecked_emplace_at(pos0,hash,std::forward(args)...)), From 4ed301df974e9d6c2ef741116fbb47ddcc9af9b5 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 13 Mar 2023 17:50:03 +0100 Subject: [PATCH 024/327] refactored insert_type creation in emplace --- include/boost/unordered/detail/foa/core.hpp | 55 ++++++++++++++------ include/boost/unordered/detail/foa/table.hpp | 22 ++------ 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index e6e3fd1c..cf4fe168 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1049,14 +1049,45 @@ struct table_locator struct try_emplace_args_t{}; -template -union uninitialized_storage +template +class alloc_cted_insert_type { - T t_; - uninitialized_storage(){} - ~uninitialized_storage(){} + using emplace_type=typename std::conditional< + std::is_constructible::value, + typename TypePolicy::init_type, + typename TypePolicy::value_type + >::type; + + using insert_type=typename std::conditional< + std::is_constructible::value, + emplace_type,typename TypePolicy::element_type + >::type; + + Allocator al; + alignas(insert_type) unsigned char storage[sizeof(insert_type)]; + +public: + alloc_cted_insert_type(const Allocator& al_,Args&&... args):al{al_} + { + TypePolicy::construct(al,data(),std::forward(args)...); + } + + ~alloc_cted_insert_type() + { + TypePolicy::destroy(al,data()); + } + + insert_type* data(){return reinterpret_cast(&storage);} + insert_type& value(){return *data();} }; +template +alloc_cted_insert_type +alloc_make_insert_type(const Allocator& al,Args&&... args) +{ + return {al,std::forward(args)...}; +} + /* table_core. The TypePolicy template parameter is used to generate * instantiations suitable for either maps or sets, and introduces non-standard * init_type and element_type: @@ -1421,14 +1452,6 @@ public: table_core& x; }; - template - struct destroy_on_exit - { - Allocator &a; - T *p; - ~destroy_on_exit(){type_policy::destroy(a,p);}; - }; - Hash& h(){return hash_base::get();} const Hash& h()const{return hash_base::get();} Pred& pred(){return pred_base::get();} @@ -1752,10 +1775,10 @@ private: * that average probe length won't increase unboundedly in repeated * insert/erase cycles (drift). */ - auto ov=group_type::maybe_caused_overflow(pc); - ml-=ov; + bool ofw=group_type::maybe_caused_overflow(pc); group_type::reset(pc); - available+=!ov; + ml-=ofw; + available+=!ofw; } void recover_slot(group_type* pg,std::size_t pos) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 96006dcc..5012906f 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -297,25 +297,9 @@ public: template BOOST_FORCEINLINE std::pair emplace(Args&&... args) { - using emplace_type=typename std::conditional< - std::is_constructible::value, - init_type, - value_type - >::type; - - using insert_type=typename std::conditional< - std::is_constructible< - value_type,emplace_type>::value, - emplace_type,element_type - >::type; - - uninitialized_storage s; - auto *p=std::addressof(s.t_); - - type_policy::construct(this->al(),p,std::forward(args)...); - - typename super::template destroy_on_exit guard{this->al(),p}; - return emplace_impl(type_policy::move(*p)); + auto x=alloc_make_insert_type( + this->al(),std::forward(args)...); + return emplace_impl(type_policy::move(x.value())); } template From 83040211e8a978ec5a8e377ad968efdf65d9f9c8 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 13 Mar 2023 17:56:57 +0100 Subject: [PATCH 025/327] recovered warninng supression pragmas --- include/boost/unordered/detail/foa/table.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 5012906f..acb870a0 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -222,6 +222,11 @@ using table_core_impl= #include +#if defined(BOOST_MSVC) +#pragma warning(push) +#pragma warning(disable:4714) /* marked as __forceinline not inlined */ +#endif + template class table:table_core_impl { @@ -505,6 +510,10 @@ private: } }; +#if defined(BOOST_MSVC) +#pragma warning(pop) /* C4714 */ +#endif + #include } /* namespace foa */ From 81584c22cf74cb314a186804e29403bfd9223cc7 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 13 Mar 2023 18:33:48 +0100 Subject: [PATCH 026/327] swapped member order in alloc_cted_insert_type to suppress padding warnings --- include/boost/unordered/detail/foa/core.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index cf4fe168..053c8fa1 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1063,8 +1063,8 @@ class alloc_cted_insert_type emplace_type,typename TypePolicy::element_type >::type; - Allocator al; alignas(insert_type) unsigned char storage[sizeof(insert_type)]; + Allocator al; public: alloc_cted_insert_type(const Allocator& al_,Args&&... args):al{al_} From 06535e518ecc6c29cd3f57eefec45e832cd7f050 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 13 Mar 2023 19:22:41 +0100 Subject: [PATCH 027/327] uploaded first draft of foa::concurrent_table --- .../unordered/detail/foa/concurrent_table.hpp | 697 ++++++++++++++++++ .../unordered/detail/foa/rw_spinlock.hpp | 184 +++++ 2 files changed, 881 insertions(+) create mode 100644 include/boost/unordered/detail/foa/concurrent_table.hpp create mode 100644 include/boost/unordered/detail/foa/rw_spinlock.hpp diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp new file mode 100644 index 00000000..94823642 --- /dev/null +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -0,0 +1,697 @@ +/* Fast open-addressing concurrent hash table. + * + * Copyright 2023 Joaquin M Lopez Munoz. + * 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. + */ + +#ifndef BOOST_UNORDERED_DETAIL_FOA_CONCURRENT_TABLE_HPP +#define BOOST_UNORDERED_DETAIL_FOA_CONCURRENT_TABLE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost{ +namespace unordered{ +namespace detail{ +namespace foa{ + +// TODO: use std::hardware_destructive_interference_size when available + +template +struct alignas(64) cacheline_protected:T +{ + using T::T; +}; + +template +class multimutex +{ +public: + constexpr std::size_t size()const noexcept{return N;} + + Mutex& operator[](std::size_t pos)noexcept + { + BOOST_ASSERT(pos mutexes; +}; + +/* std::shared_lock is C++14 */ + +template +class shared_lock +{ +public: + shared_lock(Mutex& m_)noexcept:m{m_}{m.lock_shared();} + ~shared_lock()noexcept{if(owns)m.unlock_shared();} + + void lock(){BOOST_ASSERT(!owns);m.lock_shared();owns=true;} + void unlock(){BOOST_ASSERT(owns);m.unlock_shared();owns=false;} + +private: + Mutex &m; + bool owns=true; +}; + +/* VS in pre-C++17 mode has trouble returning std::lock_guard due to + * its copy constructor being deleted. + */ + +template +class lock_guard +{ +public: + lock_guard(Mutex& m_)noexcept:m{m_}{m.lock();} + ~lock_guard()noexcept{m.unlock();} + +private: + Mutex &m; +}; + +/* copied from boost/multi_index/detail/scoped_bilock.hpp */ + +template +class scoped_bilock +{ +public: + scoped_bilock(Mutex& m1,Mutex& m2)noexcept:mutex_eq{&m1==&m2} + { + bool mutex_lt=std::less{}(&m1,&m2); + + ::new (static_cast(&storage1)) lock_guard_type(mutex_lt?m1:m2); + if(!mutex_eq) + ::new (static_cast(&storage2)) lock_guard_type(mutex_lt?m2:m1); + } + + ~scoped_bilock()noexcept + { + reinterpret_cast(&storage1)->~lock_guard_type(); + if(!mutex_eq) + reinterpret_cast(&storage2)->~lock_guard_type(); + } + +private: + using lock_guard_type=lock_guard; + + bool mutex_eq; + alignas(lock_guard_type) unsigned char storage1[sizeof(lock_guard_type)], + storage2[sizeof(lock_guard_type)]; +}; + +/* TODO: describe atomic_integral and group_access */ + +template +struct atomic_integral +{ + operator Integral()const{return n.load(std::memory_order_acquire);} + void operator=(Integral m){n.store(m,std::memory_order_release);} + void operator|=(Integral m){n.fetch_or(m,std::memory_order_acq_rel);} + void operator&=(Integral m){n.fetch_and(m,std::memory_order_acq_rel);} + + std::atomic n; +}; + +struct group_access +{ + struct dummy_group_access_type + { + boost::uint32_t storage[2]={0,0}; + }; + + using mutex_type=rw_spinlock; + using shared_lock_guard=shared_lock; + using exclusive_lock_guard=lock_guard; + + shared_lock_guard shared_access(){return m;} + exclusive_lock_guard exclusive_access(){return m;} + std::atomic_uint32_t& insert_counter(){return cnt;} + +private: + mutex_type m; + std::atomic_uint32_t cnt; +}; + +template +struct concurrent_group:Group,group_access +{ + struct dummy_group_type + { + typename Group::dummy_group_type group_storage; + group_access::dummy_group_access_type access_storage; + }; +}; + +/* TODO: describe foa::concurrent_table. + */ + +template +using concurrent_table_core_impl=table_core< + TypePolicy,concurrent_group>,std::atomic_size_t, + Hash,Pred,Allocator>; + +#include + +#if defined(BOOST_MSVC) +#pragma warning(push) +#pragma warning(disable:4714) /* marked as __forceinline not inlined */ +#endif + +template +class concurrent_table:concurrent_table_core_impl +{ + using super=concurrent_table_core_impl; + using type_policy=typename super::type_policy; + using group_type=typename super::group_type; + using super::N; + using prober=typename super::prober; + +public: + using key_type=typename super::key_type; + using init_type=typename super::init_type; + using value_type=typename super::value_type; + using element_type=typename super::element_type; + using hasher=typename super::hasher; + using key_equal=typename super::key_equal; + using allocator_type=typename super::allocator_type; + using size_type=typename super::size_type; + + concurrent_table( + std::size_t n=default_bucket_count,const Hash& h_=Hash(), + const Pred& pred_=Pred(),const Allocator& al_=Allocator()): + super{n,h_,pred_,al_} + {} + + concurrent_table(const concurrent_table& x): + concurrent_table(x,x.exclusive_access()){} + concurrent_table(concurrent_table&& x): + concurrent_table(std::move(x),x.exclusive_access()){} + concurrent_table(const concurrent_table& x,const Allocator& al_): + concurrent_table(x,al_,x.exclusive_access()){} + concurrent_table(concurrent_table&& x,const Allocator& al_): + concurrent_table(std::move(x),al_,x.exclusive_access()){} + ~concurrent_table()=default; + + concurrent_table& operator=(const concurrent_table& x) + { + auto lck=exclusive_access(*this,x); + super::operator=(x); + return *this; + } + + concurrent_table& operator=(concurrent_table&& x) + { + auto lck=exclusive_access(*this,x); + super::operator=(std::move(x)); + return *this; + } + + allocator_type get_allocator()const noexcept + { + auto lck=shared_access(); + return super::get_allocator(); + } + + template + BOOST_FORCEINLINE std::size_t visit(const Key& x,F f) + { + auto lck=shared_access(); + auto hash=this->hash_for(x); + return unprotected_visit(x,this->position_for(hash),hash,f); + } + + template + BOOST_FORCEINLINE std::size_t visit(const Key& x,F f)const + { + return const_cast(this)-> + visit(x,[&](const value_type& v){f(v);}); + } + + // TODO: visit_all + + bool empty()const noexcept{return size()==0;} + + std::size_t size()const noexcept + { + auto lck=shared_access(); + std::size_t ml_=this->ml; /* load order matters */ + std::size_t available_=this->available; + return ml_-available_; + } + + using super::max_size; + + template + BOOST_FORCEINLINE bool emplace(Args&&... args) + { + return construct_and_emplace(std::forward(args)...); + } + + BOOST_FORCEINLINE bool + insert(const init_type& x){return emplace_impl(x);} + + BOOST_FORCEINLINE bool + insert(init_type&& x){return emplace_impl(std::move(x));} + + /* template tilts call ambiguities in favor of init_type */ + + template + BOOST_FORCEINLINE bool + insert(const value_type& x){return emplace_impl(x);} + + template + BOOST_FORCEINLINE bool + insert(value_type&& x){return emplace_impl(std::move(x));} + + template + BOOST_FORCEINLINE bool try_emplace(Key&& x,Args&&... args) + { + return emplace_impl( + try_emplace_args_t{},std::forward(x),std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool try_emplace_or_visit(Key&& x,F f,Args&&... args) + { + return emplace_or_visit_impl( + f,try_emplace_args_t{},std::forward(x),std::forward(args)...); + } + + template + BOOST_FORCEINLINE std::size_t erase(Key&& x) + { + return erase_if(std::forward(x),[](const value_type&){return true;}); + } + + template + BOOST_FORCEINLINE bool emplace_or_visit(F f,Args&&... args) + { + return construct_and_emplace_or_visit( + std::forward(f),std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool insert_or_visit(const init_type& x,F f) + {return emplace_or_visit_impl(x,std::forward(f));} + + template + BOOST_FORCEINLINE bool insert_or_visit(init_type&& x,F f) + {return emplace_or_visit_impl(std::move(x),std::forward(f));} + + /* typename=void tilts call ambiguities in favor of init_type */ + + template + BOOST_FORCEINLINE bool insert_or_visit(const value_type& x,F f) + {return emplace_or_visit_impl(x,std::forward(f));} + + template + BOOST_FORCEINLINE bool insert_or_visit(value_type&& x,F f) + {return emplace_or_visit_impl(std::move(x),std::forward(f));} + + template + BOOST_FORCEINLINE std::size_t erase_if(Key&& x,F f) + { + auto lck=shared_access(); + auto hash=this->hash_for(x); + return (std::size_t)unprotected_internal_visit( + x,this->position_for(hash),hash, + [&,this](group_type* pg,unsigned int n,element_type* p) + { + if(f(const_cast(type_policy::value_from(*p)))){ + super::erase(pg,n,p); + } + }); + } + + void swap(concurrent_table& x) + noexcept(noexcept(std::declval().swap(std::declval()))) + { + auto lck=exclusive_access(*this,x); + super::swap(x); + } + + void clear()noexcept + { + auto lck=exclusive_access(); + super::clear(); + } + +#if 0 + // TODO: should we accept different allocator too? + template + void merge(table& x) + { + x.for_all_elements([&,this](group_type* pg,unsigned int n,element_type* p){ + erase_on_exit e{x,{pg,n,p}}; + if(!emplace_impl(type_policy::move(*p)).second)e.rollback(); + }); + } + + template + void merge(table&& x){merge(x);} +#endif + + hasher hash_function()const + { + auto lck=shared_access(); + return super::hash_function(); + } + + key_equal key_eq()const + { + auto lck=shared_access(); + return super::key_eq(); + } + + template + BOOST_FORCEINLINE std::size_t count(Key&& x)const + { + return (std::size_t)contains(std::forward(x)); + } + + template + BOOST_FORCEINLINE bool contains(Key&& x)const + { + return visit(std::forward(x),[](const value_type&){}); + } + + std::size_t capacity()const noexcept + { + auto lck=shared_access(); + return super::capacity(); + } + + float load_factor()const noexcept + { + auto lck=shared_access(); + return super::load_factor(); + } + + using super::max_load_factor; + + std::size_t max_load()const noexcept + { + auto lck=shared_access(); + return super::max_load(); + } + + void rehash(std::size_t n) + { + auto lck=exclusive_access(); + super::rehash(n); + } + + void reserve(std::size_t n) + { + auto lck=exclusive_access(); + super::reserve(n); + } + + // TODO: rewrite + template + friend std::size_t erase_if(concurrent_table& x,Predicate pr) + { + return x.erase_if_impl(pr); + } + +private: + using mutex_type=cacheline_protected; + using multimutex_type=multimutex; // TODO: adapt 128 to the machine + using shared_lock_guard=shared_lock; + using exclusive_lock_guard=lock_guard; + using exclusive_bilock_guard=scoped_bilock; + using group_shared_lock_guard=typename group_type::shared_lock_guard; + using group_exclusive_lock_guard=typename group_type::exclusive_lock_guard; + + concurrent_table(const concurrent_table& x,exclusive_lock_guard): + super{x}{} + concurrent_table(concurrent_table&& x,exclusive_lock_guard): + super{std::move(x)}{} + concurrent_table( + const concurrent_table& x,const Allocator& al_,exclusive_lock_guard): + super{x,al_}{} + concurrent_table( + concurrent_table&& x,const Allocator& al_,exclusive_lock_guard): + super{std::move(x),al_}{} + + template + BOOST_FORCEINLINE std::size_t unprotected_visit( + const Key& x,std::size_t pos0,std::size_t hash,F&& f)const + { + return unprotected_internal_visit( + x,pos0,hash, + [&,this](group_type*,unsigned int,element_type* p) + {f(type_policy::value_from(*p));}); + } + +#if defined(BOOST_MSVC) +/* warning: forcing value to bool 'true' or 'false' in bool(pred()...) */ +#pragma warning(push) +#pragma warning(disable:4800) +#endif + + template + BOOST_FORCEINLINE std::size_t unprotected_internal_visit( + const Key& x,std::size_t pos0,std::size_t hash,F&& f)const + { + prober pb(pos0); + do{ + auto pos=pb.get(); + auto pg=this->arrays.groups+pos; + auto mask=pg->match(hash); + if(mask){ + auto p=this->arrays.elements+pos*N; + this->prefetch_elements(p); + auto lck=shared_access(pos); + do{ + auto n=unchecked_countr_zero(mask); + if(BOOST_LIKELY( + pg->is_occupied(n)&&bool(this->pred()(x,this->key_from(p[n]))))){ + f(pg,n,p+n); + return 1; + } + mask&=mask-1; + }while(mask); + } + if(BOOST_LIKELY(pg->is_not_overflowed(hash))){ + return 0; + } + } + while(BOOST_LIKELY(pb.next(this->arrays.groups_size_mask))); + return 0; + } + +#if defined(BOOST_MSVC) +#pragma warning(pop) /* C4800 */ +#endif + + template + BOOST_FORCEINLINE bool construct_and_emplace(Args&&... args) + { + return construct_and_emplace_or_visit( + [](value_type&){},std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool construct_and_emplace_or_visit(F&& f,Args&&... args) + { + auto lck=shared_access(); + + auto x=alloc_make_insert_type( + this->al(),std::forward(args)...); + int res=unprotected_norehash_emplace_or_visit( + std::forward(f),type_policy::move(x.value())); + if(res>=0)return res!=0; + + lck.unlock(); + + rehash_if_full(); + return noinline_emplace_or_visit( + std::forward(f),type_policy::move(x.value())); + } + + template + BOOST_FORCEINLINE bool emplace_impl(Args&&... args) + { + return emplace_or_visit_impl( + [](value_type&){},std::forward(args)...); + } + + template + BOOST_NOINLINE bool noinline_emplace_or_visit(F&& f,Args&&... args) + { + return emplace_or_visit_impl( + std::forward(f),std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool emplace_or_visit_impl(F&& f,Args&&... args) + { + for(;;){ + { + auto lck=shared_access(); + int res=unprotected_norehash_emplace_or_visit( + std::forward(f),std::forward(args)...); + if(res>=0)return res!=0; + } + rehash_if_full(); + } + } + + struct reserve_available + { + reserve_available(concurrent_table& x_):x{x_} + { + do{ + available=x.available.load(std::memory_order_acquire); + }while( + available&&!x.available.compare_exchange_weak(available,available-1)); + } + + ~reserve_available() + { + if(!commit_&&available){ + x.available.fetch_add(1,std::memory_order_release); + } + } + + bool succeeded()const{return available!=0;} + void commit(){commit_=true;} + + concurrent_table &x; + std::size_t available; + bool commit_=false; + }; + + template + BOOST_FORCEINLINE int + unprotected_norehash_emplace_or_visit(F&& f,Args&&... args) + { + const auto &k=this->key_from(std::forward(args)...); + auto hash=this->hash_for(k); + auto pos0=this->position_for(hash); + + for(;;){ + startover: + boost::uint32_t counter=insert_counter(pos0); + if(unprotected_visit(k,pos0,hash,f))return 0; + +#if 1 + reserve_available ra(*this); + if(BOOST_LIKELY(ra.succeeded())){ +#else + if(BOOST_LIKELY(this->available!=0)){ +#endif + for(prober pb(pos0);;pb.next(this->arrays.groups_size_mask)){ + auto pos=pb.get(); + auto pg=this->arrays.groups+pos; + auto mask=pg->match_available(); + if(BOOST_LIKELY(mask!=0)){ + auto lck=exclusive_access(pos); + do{ + auto n=unchecked_countr_zero(mask); + if(BOOST_LIKELY(!pg->is_occupied(n))){ + pg->set(n,hash); + if(BOOST_UNLIKELY(insert_counter(pos0)++!=counter)){ + /* other thread inserted from pos0, need to start over */ + pg->reset(n); + goto startover; + } + auto p=this->arrays.elements+pos*N+n; + this->construct_element(p,std::forward(args)...); +#if 1 + ra.commit(); +#else + --(this->available); +#endif + f(type_policy::value_from(*p)); + return 1; + } + mask&=mask-1; + }while(mask); + } + pg->mark_overflow(hash); + } + } + else return -1; + } + } + + BOOST_NOINLINE void rehash_if_full() + { + auto lck=exclusive_access(); + // TODO: use same mechanism as unchecked_emplace_with_rehash + if(!this->available)this->super::rehash(super::capacity()+1); + } + + shared_lock_guard shared_access()const + { + // TODO: make this more sophisticated (even distribution) + thread_local auto id=(++thread_counter)%mutexes.size(); + + return mutexes[id]; + } + + exclusive_lock_guard exclusive_access()const + { + return mutexes; + } + + exclusive_bilock_guard exclusive_access( + const concurrent_table& x,const concurrent_table& y) + { + return {x.mutexes,y.mutexes}; + } + + group_shared_lock_guard shared_access(std::size_t pos)const + { + return this->arrays.groups[pos].shared_access(); + } + + group_exclusive_lock_guard exclusive_access(std::size_t pos)const + { + return this->arrays.groups[pos].exclusive_access(); + } + + std::atomic_uint32_t& insert_counter(std::size_t pos)const + { + return this->arrays.groups[pos].insert_counter(); + } + + mutable std::atomic_uint thread_counter=0; + mutable multimutex_type mutexes; +}; + +#if defined(BOOST_MSVC) +#pragma warning(pop) /* C4714 */ +#endif + +#include + +} /* namespace foa */ +} /* namespace detail */ +} /* namespace unordered */ +} /* namespace boost */ + +#endif diff --git a/include/boost/unordered/detail/foa/rw_spinlock.hpp b/include/boost/unordered/detail/foa/rw_spinlock.hpp new file mode 100644 index 00000000..132c68dd --- /dev/null +++ b/include/boost/unordered/detail/foa/rw_spinlock.hpp @@ -0,0 +1,184 @@ +#ifndef BOOST_UNORDERED_DETAIL_FOA_RW_SPINLOCK_HPP_INCLUDED +#define BOOST_UNORDERED_DETAIL_FOA_RW_SPINLOCK_HPP_INCLUDED + +// Copyright 2023 Peter Dimov +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include +#include + +namespace boost{ +namespace unordered{ +namespace detail{ +namespace foa{ + +class rw_spinlock +{ +private: + + // bit 31: locked exclusive + // bit 30: writer pending + // bit 29..: reader lock count + + std::atomic state_ = {}; + +private: + + // number of times to spin before sleeping + static constexpr int spin_count = 24576; + +public: + + bool try_lock_shared() noexcept + { + std::uint32_t st = state_.load( std::memory_order_relaxed ); + + if( st >= 0x3FFF'FFFF ) + { + // either bit 31 set, bit 30 set, or reader count is max + return false; + } + + std::uint32_t newst = st + 1; + return state_.compare_exchange_strong( st, newst, std::memory_order_acquire, std::memory_order_relaxed ); + } + + void lock_shared() noexcept + { + for( ;; ) + { + for( int k = 0; k < spin_count; ++k ) + { + std::uint32_t st = state_.load( std::memory_order_relaxed ); + + if( st < 0x3FFF'FFFF ) + { + std::uint32_t newst = st + 1; + if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return; + } + + boost::detail::sp_thread_pause(); + } + + boost::detail::sp_thread_sleep(); + } + } + + void unlock_shared() noexcept + { + // pre: locked shared, not locked exclusive + + state_.fetch_sub( 1, std::memory_order_release ); + + // if the writer pending bit is set, there's a writer waiting + // let it acquire the lock; it will clear the bit on unlock + } + + bool try_lock() noexcept + { + std::uint32_t st = state_.load( std::memory_order_relaxed ); + + if( st & 0x8000'0000 ) + { + // locked exclusive + return false; + } + + if( st & 0x3FFF'FFFF ) + { + // locked shared + return false; + } + + std::uint32_t newst = 0x8000'0000; + return state_.compare_exchange_strong( st, newst, std::memory_order_acquire, std::memory_order_relaxed ); + } + + void lock() noexcept + { + for( ;; ) + { + for( int k = 0; k < spin_count; ++k ) + { + std::uint32_t st = state_.load( std::memory_order_relaxed ); + + if( st & 0x8000'0000 ) + { + // locked exclusive, spin + } + else if( ( st & 0x3FFF'FFFF ) == 0 ) + { + // not locked exclusive, not locked shared, try to lock + + std::uint32_t newst = 0x8000'0000; + if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return; + } + else if( st & 0x4000'000 ) + { + // writer pending bit already set, nothing to do + } + else + { + // locked shared, set writer pending bit + + std::uint32_t newst = st | 0x4000'0000; + state_.compare_exchange_weak( st, newst, std::memory_order_relaxed, std::memory_order_relaxed ); + } + + boost::detail::sp_thread_pause(); + } + + // clear writer pending bit before going to sleep + + { + std::uint32_t st = state_.load( std::memory_order_relaxed ); + + for( ;; ) + { + if( st & 0x8000'0000 ) + { + // locked exclusive, nothing to do + break; + } + else if( ( st & 0x3FFF'FFFF ) == 0 ) + { + // lock free, try to take it + + std::uint32_t newst = 0x8000'0000; + if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return; + } + else if( ( st & 0x4000'0000 ) == 0 ) + { + // writer pending bit already clear, nothing to do + break; + } + else + { + // clear writer pending bit + + std::uint32_t newst = st & ~0x4000'0000u; + if( state_.compare_exchange_weak( st, newst, std::memory_order_relaxed, std::memory_order_relaxed ) ) break; + } + } + } + + boost::detail::sp_thread_sleep(); + } + } + + void unlock() noexcept + { + // pre: locked exclusive, not locked shared + state_.store( 0, std::memory_order_release ); + } +}; + +} /* namespace foa */ +} /* namespace detail */ +} /* namespace unordered */ +} /* namespace boost */ + +#endif // BOOST_UNORDERED_DETAIL_FOA_RW_SPINLOCK_HPP_INCLUDED From 5e225fe46c594b42d50483e437df2feff9a92ff7 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Tue, 14 Mar 2023 15:14:09 +0100 Subject: [PATCH 028/327] micro-optimized reserve_available --- .../boost/unordered/detail/foa/concurrent_table.hpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 94823642..c6d5b29e 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -564,7 +564,7 @@ private: reserve_available(concurrent_table& x_):x{x_} { do{ - available=x.available.load(std::memory_order_acquire); + available=x.available.load(std::memory_order_relaxed); }while( available&&!x.available.compare_exchange_weak(available,available-1)); } @@ -597,12 +597,8 @@ private: boost::uint32_t counter=insert_counter(pos0); if(unprotected_visit(k,pos0,hash,f))return 0; -#if 1 reserve_available ra(*this); if(BOOST_LIKELY(ra.succeeded())){ -#else - if(BOOST_LIKELY(this->available!=0)){ -#endif for(prober pb(pos0);;pb.next(this->arrays.groups_size_mask)){ auto pos=pb.get(); auto pg=this->arrays.groups+pos; @@ -620,11 +616,7 @@ private: } auto p=this->arrays.elements+pos*N+n; this->construct_element(p,std::forward(args)...); -#if 1 ra.commit(); -#else - --(this->available); -#endif f(type_policy::value_from(*p)); return 1; } From f244ba55de0ad379e10ae98a0e02ca4a0c7f20f5 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Tue, 14 Mar 2023 19:09:13 +0100 Subject: [PATCH 029/327] switched from available to size_ --- .../unordered/detail/foa/concurrent_table.hpp | 40 +++++++++---------- include/boost/unordered/detail/foa/core.hpp | 33 +++++++-------- include/boost/unordered/detail/foa/table.hpp | 2 +- 3 files changed, 33 insertions(+), 42 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index c6d5b29e..e97c573d 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -255,9 +255,9 @@ public: std::size_t size()const noexcept { auto lck=shared_access(); - std::size_t ml_=this->ml; /* load order matters */ - std::size_t available_=this->available; - return ml_-available_; + std::size_t ml_=this->ml; + std::size_t size_=this->size_; + return size_<=ml_?size_:ml_; } using super::max_size; @@ -522,7 +522,7 @@ private: this->al(),std::forward(args)...); int res=unprotected_norehash_emplace_or_visit( std::forward(f),type_policy::move(x.value())); - if(res>=0)return res!=0; + if(BOOST_LIKELY(res>=0))return res!=0; lck.unlock(); @@ -553,34 +553,30 @@ private: auto lck=shared_access(); int res=unprotected_norehash_emplace_or_visit( std::forward(f),std::forward(args)...); - if(res>=0)return res!=0; + if(BOOST_LIKELY(res>=0))return res!=0; } rehash_if_full(); } } - struct reserve_available + struct reserve_size { - reserve_available(concurrent_table& x_):x{x_} + reserve_size(concurrent_table& x_):x{x_} { - do{ - available=x.available.load(std::memory_order_relaxed); - }while( - available&&!x.available.compare_exchange_weak(available,available-1)); + size_=++x.size_; } - ~reserve_available() + ~reserve_size() { - if(!commit_&&available){ - x.available.fetch_add(1,std::memory_order_release); - } + if(!commit_)--x.size_; } - bool succeeded()const{return available!=0;} + bool succeeded()const{return size_<=x.ml;} + void commit(){commit_=true;} concurrent_table &x; - std::size_t available; + std::size_t size_; bool commit_=false; }; @@ -595,10 +591,10 @@ private: for(;;){ startover: boost::uint32_t counter=insert_counter(pos0); - if(unprotected_visit(k,pos0,hash,f))return 0; + if(unprotected_visit(k,pos0,hash,std::forward(f)))return 0; - reserve_available ra(*this); - if(BOOST_LIKELY(ra.succeeded())){ + reserve_size rs(*this); + if(BOOST_LIKELY(rs.succeeded())){ for(prober pb(pos0);;pb.next(this->arrays.groups_size_mask)){ auto pos=pb.get(); auto pg=this->arrays.groups+pos; @@ -616,7 +612,7 @@ private: } auto p=this->arrays.elements+pos*N+n; this->construct_element(p,std::forward(args)...); - ra.commit(); + rs.commit(); f(type_policy::value_from(*p)); return 1; } @@ -634,7 +630,7 @@ private: { auto lck=exclusive_access(); // TODO: use same mechanism as unchecked_emplace_with_rehash - if(!this->available)this->super::rehash(super::capacity()+1); + if(this->size_==this->ml)this->super::rehash(super::capacity()+1); } shared_lock_guard shared_access()const diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 053c8fa1..7108dee6 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1191,7 +1191,7 @@ public: const Pred& pred_=Pred(),const Allocator& al_=Allocator()): hash_base{empty_init,h_},pred_base{empty_init,pred_}, allocator_base{empty_init,al_},arrays(new_arrays(n)), - ml{initial_max_load()},available{std::size_t(ml)} + ml{initial_max_load()},size_{0} {} table_core(const table_core& x): @@ -1205,11 +1205,11 @@ public: hash_base{empty_init,std::move(x.h())}, pred_base{empty_init,std::move(x.pred())}, allocator_base{empty_init,std::move(x.al())}, - arrays(x.arrays),ml{std::size_t(x.ml)},available{std::size_t(x.available)} + arrays(x.arrays),ml{std::size_t(x.ml)},size_{std::size_t(x.size_)} { x.arrays=x.new_arrays(0); x.ml=x.initial_max_load(); - x.available=std::size_t(x.ml); + x.size_=std::size_t(x.size_); } table_core(const table_core& x,const Allocator& al_): @@ -1224,7 +1224,7 @@ public: if(al()==x.al()){ std::swap(arrays,x.arrays); std::swap(ml,x.ml); - std::swap(available,x.available); + std::swap(size_,x.size_); } else{ reserve(x.size()); @@ -1323,7 +1323,7 @@ public: move_assign_if(al(),x.al()); swap(arrays,x.arrays); swap(ml,x.ml); - swap(available,x.available); + swap(size_,x.size_); } else{ /* noshrink: favor memory reuse over tightness */ @@ -1349,7 +1349,7 @@ public: allocator_type get_allocator()const noexcept{return al();} bool empty()const noexcept{return size()==0;} - std::size_t size()const noexcept{return ml-available;} + std::size_t size()const noexcept{return size_;} std::size_t max_size()const noexcept{return SIZE_MAX;} // TODO unify erase? @@ -1391,7 +1391,7 @@ public: swap(pred(),x.pred()); swap(arrays,x.arrays); swap(ml,x.ml); - swap(available,x.available); + swap(size_,x.size_); } void clear()noexcept @@ -1410,7 +1410,7 @@ public: } arrays.groups[arrays.groups_size_mask].set_sentinel(); ml=initial_max_load(); - available=std::size_t(ml); + size_=0; } } @@ -1545,7 +1545,7 @@ public: { auto res=nosize_unchecked_emplace_at( arrays,pos0,hash,std::forward(args)...); - --available; + ++size_; return res; } @@ -1580,7 +1580,7 @@ public: /* new_arrays_ lifetime taken care of by unchecked_rehash */ unchecked_rehash(new_arrays_); - --available; + ++size_; return it; } @@ -1649,7 +1649,7 @@ public: arrays_type arrays; SizeImpl ml; - SizeImpl available; + SizeImpl size_; private: template @@ -1712,7 +1712,7 @@ private: std::memcpy( arrays.groups,x.arrays.groups, (arrays.groups_size_mask+1)*sizeof(group_type)); - available=std::size_t(x.available); + size_=std::size_t(x.size_); } } @@ -1775,10 +1775,9 @@ private: * that average probe length won't increase unboundedly in repeated * insert/erase cycles (drift). */ - bool ofw=group_type::maybe_caused_overflow(pc); + ml-=group_type::maybe_caused_overflow(pc); group_type::reset(pc); - ml-=ofw; - available+=!ofw; + --size_; } void recover_slot(group_type* pg,std::size_t pos) @@ -1844,9 +1843,7 @@ private: } delete_arrays(arrays); arrays=new_arrays_; - auto s=size(); ml=initial_max_load(); - available=ml-s; } void noshrink_reserve(std::size_t n) @@ -1862,9 +1859,7 @@ private: auto new_arrays_=new_arrays(n); delete_arrays(arrays); arrays=new_arrays_; - auto s=size(); ml=initial_max_load(); - available=ml-s; } } } diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index acb870a0..750e2db8 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -492,7 +492,7 @@ private: if(it!=end()){ return {it,false}; } - if(BOOST_LIKELY(this->available!=0)){ + if(BOOST_LIKELY(this->size_ml)){ return { make_iterator( this->unchecked_emplace_at(pos0,hash,std::forward(args)...)), From c35e9fc631ed0fac98457b548d4a1d6ec656376d Mon Sep 17 00:00:00 2001 From: joaquintides Date: Tue, 14 Mar 2023 20:48:17 +0100 Subject: [PATCH 030/327] made slot setting on insertion exception safe --- .../unordered/detail/foa/concurrent_table.hpp | 71 +++++++++++++++++-- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index e97c573d..8d186f38 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -580,6 +580,26 @@ private: bool commit_=false; }; + struct reserve_slot + { + reserve_slot(group_type* pg_,std::size_t pos_,std::size_t hash): + pg{pg_},pos{pos_} + { + pg->set(pos,hash); + } + + ~reserve_slot() + { + if(!commit_)pg->reset(pos); + } + + void commit(){commit_=true;} + + group_type *pg; + std::size_t pos; + bool commit_=false; + }; + template BOOST_FORCEINLINE int unprotected_norehash_emplace_or_visit(F&& f,Args&&... args) @@ -593,8 +613,9 @@ private: boost::uint32_t counter=insert_counter(pos0); if(unprotected_visit(k,pos0,hash,std::forward(f)))return 0; - reserve_size rs(*this); - if(BOOST_LIKELY(rs.succeeded())){ +#if 1 + reserve_size rsize(*this); + if(BOOST_LIKELY(rsize.succeeded())){ for(prober pb(pos0);;pb.next(this->arrays.groups_size_mask)){ auto pos=pb.get(); auto pg=this->arrays.groups+pos; @@ -604,15 +625,15 @@ private: do{ auto n=unchecked_countr_zero(mask); if(BOOST_LIKELY(!pg->is_occupied(n))){ - pg->set(n,hash); + reserve_slot rslot{pg,n,hash}; if(BOOST_UNLIKELY(insert_counter(pos0)++!=counter)){ /* other thread inserted from pos0, need to start over */ - pg->reset(n); goto startover; } auto p=this->arrays.elements+pos*N+n; this->construct_element(p,std::forward(args)...); - rs.commit(); + rslot.commit(); + rsize.commit(); f(type_policy::value_from(*p)); return 1; } @@ -623,6 +644,46 @@ private: } } else return -1; +#else + if(BOOST_LIKELY(++(this->size_)<=this->ml)){ + BOOST_TRY{ + for(prober pb(pos0);;pb.next(this->arrays.groups_size_mask)){ + auto pos=pb.get(); + auto pg=this->arrays.groups+pos; + auto mask=pg->match_available(); + if(BOOST_LIKELY(mask!=0)){ + auto lck=exclusive_access(pos); + do{ + auto n=unchecked_countr_zero(mask); + if(BOOST_LIKELY(!pg->is_occupied(n))){ + pg->set(n,hash); + if(BOOST_UNLIKELY(insert_counter(pos0)++!=counter)){ + /* other thread inserted from pos0, need to start over */ + pg->reset(n); + --(this->size_); + goto startover; + } + auto p=this->arrays.elements+pos*N+n; + this->construct_element(p,std::forward(args)...); + return 1; + } + mask&=mask-1; + }while(mask); + } + pg->mark_overflow(hash); + } + } + BOOST_CATCH(...){ + --(this->size_); + BOOST_RETHROW + } + BOOST_CATCH_END + } + else{ + --(this->size_); + return -1; + } +#endif } } From 8198b9c57c11eed2d450740b177211ee50fc78f0 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Tue, 14 Mar 2023 20:49:09 +0100 Subject: [PATCH 031/327] dropped erroneous call to user-provided f --- include/boost/unordered/detail/foa/concurrent_table.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 8d186f38..9b13c12f 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -634,7 +634,6 @@ private: this->construct_element(p,std::forward(args)...); rslot.commit(); rsize.commit(); - f(type_policy::value_from(*p)); return 1; } mask&=mask-1; From 2070cbe2cc83edbc8113501b2b4935ada4727492 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Wed, 15 Mar 2023 09:00:31 +0100 Subject: [PATCH 032/327] fixed regression in table_core(table_core&&) --- include/boost/unordered/detail/foa/core.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 7108dee6..79e49ae4 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1209,7 +1209,7 @@ public: { x.arrays=x.new_arrays(0); x.ml=x.initial_max_load(); - x.size_=std::size_t(x.size_); + x.size_=0; } table_core(const table_core& x,const Allocator& al_): From 5881dcc2b296e78570a9df394c28cd836626fb0f Mon Sep 17 00:00:00 2001 From: joaquintides Date: Wed, 15 Mar 2023 09:09:09 +0100 Subject: [PATCH 033/327] done reverse unlock in multimutex --- include/boost/unordered/detail/foa/concurrent_table.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 9b13c12f..a48ed984 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -51,8 +51,8 @@ public: return mutexes[pos]; } - void lock()noexcept{for(auto&m:mutexes)m.lock();} - void unlock()noexcept{for(auto&m:mutexes)m.unlock();} + void lock()noexcept{for(auto& m:mutexes)m.lock();} + void unlock()noexcept{for(auto n=N;n--;)mutexes[n].unlock();} private: mutable std::array mutexes; From 0a03ed8170240233195437a35b42baae6b862bb3 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Wed, 15 Mar 2023 14:25:17 +0100 Subject: [PATCH 034/327] weakened atomic_integral ops, fixed some trivial bugs --- .../unordered/detail/foa/concurrent_table.hpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index a48ed984..53d48958 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -127,8 +127,8 @@ struct atomic_integral { operator Integral()const{return n.load(std::memory_order_acquire);} void operator=(Integral m){n.store(m,std::memory_order_release);} - void operator|=(Integral m){n.fetch_or(m,std::memory_order_acq_rel);} - void operator&=(Integral m){n.fetch_and(m,std::memory_order_acq_rel);} + void operator|=(Integral m){n.fetch_or(m,std::memory_order_release);} + void operator&=(Integral m){n.fetch_and(m,std::memory_order_release);} std::atomic n; }; @@ -295,7 +295,8 @@ public: BOOST_FORCEINLINE bool try_emplace_or_visit(Key&& x,F f,Args&&... args) { return emplace_or_visit_impl( - f,try_emplace_args_t{},std::forward(x),std::forward(args)...); + std::forward(f), + try_emplace_args_t{},std::forward(x),std::forward(args)...); } template @@ -313,21 +314,21 @@ public: template BOOST_FORCEINLINE bool insert_or_visit(const init_type& x,F f) - {return emplace_or_visit_impl(x,std::forward(f));} + {return emplace_or_visit_impl(std::forward(f),x);} template BOOST_FORCEINLINE bool insert_or_visit(init_type&& x,F f) - {return emplace_or_visit_impl(std::move(x),std::forward(f));} + {return emplace_or_visit_impl(std::forward(f),std::move(x));} /* typename=void tilts call ambiguities in favor of init_type */ template BOOST_FORCEINLINE bool insert_or_visit(const value_type& x,F f) - {return emplace_or_visit_impl(x,std::forward(f));} + {return emplace_or_visit_impl(std::forward(f),x);} template BOOST_FORCEINLINE bool insert_or_visit(value_type&& x,F f) - {return emplace_or_visit_impl(std::move(x),std::forward(f));} + {return emplace_or_visit_impl(std::forward(f),std::move(x));} template BOOST_FORCEINLINE std::size_t erase_if(Key&& x,F f) @@ -690,7 +691,8 @@ private: { auto lck=exclusive_access(); // TODO: use same mechanism as unchecked_emplace_with_rehash - if(this->size_==this->ml)this->super::rehash(super::capacity()+1); + //if(this->size_>=this->ml/2)super::rehash(super::capacity()+1); + super::rehash(super::capacity()+1); } shared_lock_guard shared_access()const From ec487b5c6c278d0a9630d94df33ae111cb1c8fe5 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Wed, 15 Mar 2023 18:35:08 +0100 Subject: [PATCH 035/327] stylistic --- .../unordered/detail/foa/concurrent_table.hpp | 50 ++----------------- 1 file changed, 4 insertions(+), 46 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 53d48958..fc0b51e2 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -51,8 +51,8 @@ public: return mutexes[pos]; } - void lock()noexcept{for(auto& m:mutexes)m.lock();} - void unlock()noexcept{for(auto n=N;n--;)mutexes[n].unlock();} + void lock()noexcept{for(std::size_t n=0;n0;)mutexes[--n].unlock();} private: mutable std::array mutexes; @@ -614,7 +614,6 @@ private: boost::uint32_t counter=insert_counter(pos0); if(unprotected_visit(k,pos0,hash,std::forward(f)))return 0; -#if 1 reserve_size rsize(*this); if(BOOST_LIKELY(rsize.succeeded())){ for(prober pb(pos0);;pb.next(this->arrays.groups_size_mask)){ @@ -644,55 +643,14 @@ private: } } else return -1; -#else - if(BOOST_LIKELY(++(this->size_)<=this->ml)){ - BOOST_TRY{ - for(prober pb(pos0);;pb.next(this->arrays.groups_size_mask)){ - auto pos=pb.get(); - auto pg=this->arrays.groups+pos; - auto mask=pg->match_available(); - if(BOOST_LIKELY(mask!=0)){ - auto lck=exclusive_access(pos); - do{ - auto n=unchecked_countr_zero(mask); - if(BOOST_LIKELY(!pg->is_occupied(n))){ - pg->set(n,hash); - if(BOOST_UNLIKELY(insert_counter(pos0)++!=counter)){ - /* other thread inserted from pos0, need to start over */ - pg->reset(n); - --(this->size_); - goto startover; - } - auto p=this->arrays.elements+pos*N+n; - this->construct_element(p,std::forward(args)...); - return 1; - } - mask&=mask-1; - }while(mask); - } - pg->mark_overflow(hash); - } - } - BOOST_CATCH(...){ - --(this->size_); - BOOST_RETHROW - } - BOOST_CATCH_END - } - else{ - --(this->size_); - return -1; - } -#endif } } - BOOST_NOINLINE void rehash_if_full() + void rehash_if_full() { auto lck=exclusive_access(); // TODO: use same mechanism as unchecked_emplace_with_rehash - //if(this->size_>=this->ml/2)super::rehash(super::capacity()+1); - super::rehash(super::capacity()+1); + if(this->size_==this->ml)super::rehash(super::capacity()+1); } shared_lock_guard shared_access()const From ff78ef25c05c60291e2fbc9851b1bffcf2d01070 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Wed, 15 Mar 2023 18:58:51 +0100 Subject: [PATCH 036/327] removed unnecessary forwards and captures --- .../unordered/detail/foa/concurrent_table.hpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index fc0b51e2..ad88b8eb 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -295,8 +295,7 @@ public: BOOST_FORCEINLINE bool try_emplace_or_visit(Key&& x,F f,Args&&... args) { return emplace_or_visit_impl( - std::forward(f), - try_emplace_args_t{},std::forward(x),std::forward(args)...); + f,try_emplace_args_t{},std::forward(x),std::forward(args)...); } template @@ -308,27 +307,26 @@ public: template BOOST_FORCEINLINE bool emplace_or_visit(F f,Args&&... args) { - return construct_and_emplace_or_visit( - std::forward(f),std::forward(args)...); + return construct_and_emplace_or_visit(f,std::forward(args)...); } template BOOST_FORCEINLINE bool insert_or_visit(const init_type& x,F f) - {return emplace_or_visit_impl(std::forward(f),x);} + {return emplace_or_visit_impl(f,x);} template BOOST_FORCEINLINE bool insert_or_visit(init_type&& x,F f) - {return emplace_or_visit_impl(std::forward(f),std::move(x));} + {return emplace_or_visit_impl(f,std::move(x));} /* typename=void tilts call ambiguities in favor of init_type */ template BOOST_FORCEINLINE bool insert_or_visit(const value_type& x,F f) - {return emplace_or_visit_impl(std::forward(f),x);} + {return emplace_or_visit_impl(f,x);} template BOOST_FORCEINLINE bool insert_or_visit(value_type&& x,F f) - {return emplace_or_visit_impl(std::forward(f),std::move(x));} + {return emplace_or_visit_impl(f,std::move(x));} template BOOST_FORCEINLINE std::size_t erase_if(Key&& x,F f) @@ -462,7 +460,7 @@ private: { return unprotected_internal_visit( x,pos0,hash, - [&,this](group_type*,unsigned int,element_type* p) + [&](group_type*,unsigned int,element_type* p) {f(type_policy::value_from(*p));}); } From ca59ed8c500a7d8b38bbc6b600383d0462eea0c8 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 16 Mar 2023 10:44:01 +0100 Subject: [PATCH 037/327] avoided usage of match_really_occupied on for_all_elements_while (used by rehash) --- include/boost/unordered/detail/foa/core.hpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 79e49ae4..8de09173 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1634,6 +1634,7 @@ public: static auto for_all_elements_while(const arrays_type& arrays_,F f) ->decltype(f(nullptr,0,nullptr),void()) { +#if 0 auto p=arrays_.elements; if(!p){return;} for(auto pg=arrays_.groups,last=pg+arrays_.groups_size_mask+1; @@ -1645,6 +1646,26 @@ public: mask&=mask-1; } } +#else + auto p=arrays_.elements; + if(!p){return;} + auto pg=arrays_.groups; + for(auto last=pg+arrays_.groups_size_mask; + pg!=last;++pg,p+=N){ + auto mask=pg->match_occupied(); + while(mask){ + auto n=unchecked_countr_zero(mask); + if(!f(pg,n,p+n))return; + mask&=mask-1; + } + } + auto mask=pg->match_really_occupied(); + while(mask){ + auto n=unchecked_countr_zero(mask); + if(!f(pg,n,p+n))return; + mask&=mask-1; + } +#endif } arrays_type arrays; From 3aaefdcc78e320423d7766182f1fa68ce0d920d4 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 17 Mar 2023 16:58:57 +0100 Subject: [PATCH 038/327] completed concurrent_table's API --- .../unordered/detail/foa/concurrent_table.hpp | 229 ++++++++++++++---- include/boost/unordered/detail/foa/core.hpp | 2 +- 2 files changed, 181 insertions(+), 50 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index ad88b8eb..6f5c609f 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -26,15 +26,25 @@ #include #include +#if !defined(BOOST_NO_CXX17_HDR_EXECUTION) +#include +#endif + namespace boost{ namespace unordered{ namespace detail{ namespace foa{ -// TODO: use std::hardware_destructive_interference_size when available - template -struct alignas(64) cacheline_protected:T +struct + +#if defined(__cpp_lib_hardware_interference_size) +alignas(std::hardware_destructive_interference_size) +#else +alignas(64) +#endif + +cacheline_protected:T { using T::T; }; @@ -144,8 +154,8 @@ struct group_access using shared_lock_guard=shared_lock; using exclusive_lock_guard=lock_guard; - shared_lock_guard shared_access(){return m;} - exclusive_lock_guard exclusive_access(){return m;} + shared_lock_guard shared_access(){return shared_lock_guard{m};} + exclusive_lock_guard exclusive_access(){return exclusive_lock_guard{m};} std::atomic_uint32_t& insert_counter(){return cnt;} private: @@ -187,6 +197,18 @@ class concurrent_table:concurrent_table_core_impl + using is_execution_policy=std::is_execution_policy< + typename std::remove_cv< + typename std::remove_reference::type + >::type + >; +#else + template + using is_execution_policy=std::false_type; +#endif + public: using key_type=typename super::key_type; using init_type=typename super::init_type; @@ -248,7 +270,41 @@ public: visit(x,[&](const value_type& v){f(v);}); } - // TODO: visit_all + template std::size_t visit_all(F f) + { + auto lck=shared_access(); + std::size_t res=0; + this->for_all_elements([&](element_type* p){ + f(type_policy::value_from(*p)); + ++res; + }); + return res; + } + + template std::size_t visit_all(F f)const + { + return const_cast(this)-> + visit_all([&](const value_type& v){f(v);}); + } + +#if !defined(BOOST_NO_CXX17_HDR_EXECUTION) + template + void visit_all(ExecutionPolicy&& policy,F f) + { + auto lck=shared_access(); + for_all_elements_exec( + std::forward(policy), + [&](element_type* p){f(type_policy::value_from(*p));}); + } + + template + void visit_all(ExecutionPolicy&& policy,F f)const + { + return const_cast(this)-> + visit_all( + std::forward(policy),[&](const value_type& v){f(v);}); + } +#endif bool empty()const noexcept{return size()==0;} @@ -298,12 +354,6 @@ public: f,try_emplace_args_t{},std::forward(x),std::forward(args)...); } - template - BOOST_FORCEINLINE std::size_t erase(Key&& x) - { - return erase_if(std::forward(x),[](const value_type&){return true;}); - } - template BOOST_FORCEINLINE bool emplace_or_visit(F f,Args&&... args) { @@ -328,8 +378,15 @@ public: BOOST_FORCEINLINE bool insert_or_visit(value_type&& x,F f) {return emplace_or_visit_impl(f,std::move(x));} + template + BOOST_FORCEINLINE std::size_t erase(Key&& x) + { + return erase_if(std::forward(x),[](const value_type&){return true;}); + } + template - BOOST_FORCEINLINE std::size_t erase_if(Key&& x,F f) + BOOST_FORCEINLINE auto erase_if(Key&& x,F f)->typename std::enable_if< + !is_execution_policy::value,std::size_t>::type { auto lck=shared_access(); auto hash=this->hash_for(x); @@ -343,6 +400,30 @@ public: }); } + template + std::size_t erase_if(F&& f) + { + auto lck=shared_access(); + return super::erase_if_impl(std::forward(f)); + } + +#if !defined(BOOST_NO_CXX17_HDR_EXECUTION) + template + auto erase_if(ExecutionPolicy&& policy,F f)->typename std::enable_if< + is_execution_policy::value,void>::type + { + auto lck=shared_access(); + for_all_elements_exec( + std::forward(policy), + [&,this](group_type* pg,unsigned int n,element_type* p) + { + if(f(const_cast(type_policy::value_from(*p)))){ + super::erase(pg,n,p); + } + }); + } +#endif + void swap(concurrent_table& x) noexcept(noexcept(std::declval().swap(std::declval()))) { @@ -356,20 +437,20 @@ public: super::clear(); } -#if 0 // TODO: should we accept different allocator too? template - void merge(table& x) + void merge(concurrent_table& x) { + // TODO: consider grabbing shared access on *this at this level + auto lck=x.shared_access(); // TODO: can deadlock if x1.merge(x2) while x2.merge(x1) x.for_all_elements([&,this](group_type* pg,unsigned int n,element_type* p){ - erase_on_exit e{x,{pg,n,p}}; - if(!emplace_impl(type_policy::move(*p)).second)e.rollback(); + erase_on_exit e{x,pg,n,p}; + if(!emplace_impl(type_policy::move(*p)))e.rollback(); }); } template - void merge(table&& x){merge(x);} -#endif + void merge(concurrent_table&& x){merge(x);} hasher hash_function()const { @@ -427,11 +508,10 @@ public: super::reserve(n); } - // TODO: rewrite template friend std::size_t erase_if(concurrent_table& x,Predicate pr) { - return x.erase_if_impl(pr); + return x.erase_if(pr); } private: @@ -454,6 +534,57 @@ private: concurrent_table&& x,const Allocator& al_,exclusive_lock_guard): super{std::move(x),al_}{} + shared_lock_guard shared_access()const + { + // TODO: make this more sophisticated (even distribution) + thread_local auto id=(++thread_counter)%mutexes.size(); + + return shared_lock_guard{mutexes[id]}; + } + + exclusive_lock_guard exclusive_access()const + { + return exclusive_lock_guard{mutexes}; + } + + exclusive_bilock_guard exclusive_access( + const concurrent_table& x,const concurrent_table& y) + { + return {x.mutexes,y.mutexes}; + } + + group_shared_lock_guard shared_access(std::size_t pos)const + { + return this->arrays.groups[pos].shared_access(); + } + + group_exclusive_lock_guard exclusive_access(std::size_t pos)const + { + return this->arrays.groups[pos].exclusive_access(); + } + + std::atomic_uint32_t& insert_counter(std::size_t pos)const + { + return this->arrays.groups[pos].insert_counter(); + } + + struct erase_on_exit + { + erase_on_exit( + concurrent_table& x_, + group_type* pg_,unsigned int pos_,element_type* p_): + x{x_},pg{pg_},pos{pos_},p{p_}{} + ~erase_on_exit(){if(!rollback_)x.super::erase(pg,pos,p);} + + void rollback(){rollback_=true;} + + concurrent_table &x; + group_type *pg; + unsigned int pos; + element_type *p; + bool rollback_=false; + }; + template BOOST_FORCEINLINE std::size_t unprotected_visit( const Key& x,std::size_t pos0,std::size_t hash,F&& f)const @@ -651,40 +782,40 @@ private: if(this->size_==this->ml)super::rehash(super::capacity()+1); } - shared_lock_guard shared_access()const +#if !defined(BOOST_NO_CXX17_HDR_EXECUTION) + template + auto for_all_elements_exec(ExecutionPolicy&& policy,F f) + ->decltype(f(nullptr),void()) { - // TODO: make this more sophisticated (even distribution) - thread_local auto id=(++thread_counter)%mutexes.size(); - - return mutexes[id]; + for_all_elements_exec( + std::forward(policy), + [&](group_type* pg,unsigned int n,element_type* p){f(p);}); } - exclusive_lock_guard exclusive_access()const + template + auto for_all_elements_exec(ExecutionPolicy&& policy,F f) + ->decltype(f(nullptr,0,nullptr),void()) { - return mutexes; - } - - exclusive_bilock_guard exclusive_access( - const concurrent_table& x,const concurrent_table& y) - { - return {x.mutexes,y.mutexes}; - } - - group_shared_lock_guard shared_access(std::size_t pos)const - { - return this->arrays.groups[pos].shared_access(); - } - - group_exclusive_lock_guard exclusive_access(std::size_t pos)const - { - return this->arrays.groups[pos].exclusive_access(); - } - - std::atomic_uint32_t& insert_counter(std::size_t pos)const - { - return this->arrays.groups[pos].insert_counter(); + auto lck=shared_access(); + auto first=this->arrays.groups, + last=first+this->arrays.groups_size_mask+1; + std::for_each(std::forward(policy),first,last, + [&,this](group_type& g){ + std::size_t pos=&g-first; + auto p=this->arrays.elements+pos*N; + auto lck=exclusive_access(pos); + auto mask=g.match_really_occupied(); + while(mask){ + auto n=unchecked_countr_zero(mask); + f(&g,n,p+n); + mask&=mask-1; + } + } + ); } +#endif + /* TODO: thread_counter should be static */ mutable std::atomic_uint thread_counter=0; mutable multimutex_type mutexes; }; diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 8de09173..ba596288 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1585,7 +1585,7 @@ public: } template - std::size_t erase_if_impl(Predicate pr) + std::size_t erase_if_impl(Predicate&& pr) { std::size_t s=size(); for_all_elements([&,this](group_type* pg,unsigned int n,element_type* p){ From 9a8ba2bdffeb485b3691897c04e8116434e7743a Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 17 Mar 2023 17:04:42 +0100 Subject: [PATCH 039/327] re-enabled match_really_occupied in for_all_elements_while --- include/boost/unordered/detail/foa/core.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index ba596288..5aaee39c 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1634,7 +1634,7 @@ public: static auto for_all_elements_while(const arrays_type& arrays_,F f) ->decltype(f(nullptr,0,nullptr),void()) { -#if 0 +#if 1 auto p=arrays_.elements; if(!p){return;} for(auto pg=arrays_.groups,last=pg+arrays_.groups_size_mask+1; From 8f348aea26c061ed73b319782d1a07d21d16116e Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 17 Mar 2023 18:45:55 +0100 Subject: [PATCH 040/327] temporarily disabled parallel algorithms --- .../boost/unordered/detail/foa/concurrent_table.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 6f5c609f..f01c5deb 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -26,7 +26,7 @@ #include #include -#if !defined(BOOST_NO_CXX17_HDR_EXECUTION) +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) #include #endif @@ -197,7 +197,7 @@ class concurrent_table:concurrent_table_core_impl using is_execution_policy=std::is_execution_policy< typename std::remove_cv< @@ -287,7 +287,7 @@ public: visit_all([&](const value_type& v){f(v);}); } -#if !defined(BOOST_NO_CXX17_HDR_EXECUTION) +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) template void visit_all(ExecutionPolicy&& policy,F f) { @@ -407,7 +407,7 @@ public: return super::erase_if_impl(std::forward(f)); } -#if !defined(BOOST_NO_CXX17_HDR_EXECUTION) +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) template auto erase_if(ExecutionPolicy&& policy,F f)->typename std::enable_if< is_execution_policy::value,void>::type @@ -782,7 +782,7 @@ private: if(this->size_==this->ml)super::rehash(super::capacity()+1); } -#if !defined(BOOST_NO_CXX17_HDR_EXECUTION) +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) template auto for_all_elements_exec(ExecutionPolicy&& policy,F f) ->decltype(f(nullptr),void()) From e16a8737f400a0135f31f096e9d6537bad1d2624 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 17 Mar 2023 19:20:44 +0100 Subject: [PATCH 041/327] shut down GCC Winterference-size --- include/boost/unordered/detail/foa/concurrent_table.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index f01c5deb..2873bf7e 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -39,7 +39,14 @@ template struct #if defined(__cpp_lib_hardware_interference_size) +#if defined(BOOST_GCC) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winterference-size" +#endif alignas(std::hardware_destructive_interference_size) +#if defined(BOOST_GCC) +#pragma GCC diagnostic pop +#endif #else alignas(64) #endif From d683b3ac3e494b2a188a7f556ca4b3141a8b757d Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 17 Mar 2023 19:21:46 +0100 Subject: [PATCH 042/327] disabled use of match_really_occupied in for_all_elements_while --- include/boost/unordered/detail/foa/core.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 5aaee39c..ba596288 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1634,7 +1634,7 @@ public: static auto for_all_elements_while(const arrays_type& arrays_,F f) ->decltype(f(nullptr,0,nullptr),void()) { -#if 1 +#if 0 auto p=arrays_.elements; if(!p){return;} for(auto pg=arrays_.groups,last=pg+arrays_.groups_size_mask+1; From 3d3457165432d7fdf2c564201bd1ed8ac44b5a9f Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 17 Mar 2023 19:56:30 +0100 Subject: [PATCH 043/327] tried yest another variation for for_all_elements_while --- include/boost/unordered/detail/foa/core.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index ba596288..f7fdc899 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1634,12 +1634,12 @@ public: static auto for_all_elements_while(const arrays_type& arrays_,F f) ->decltype(f(nullptr,0,nullptr),void()) { -#if 0 +#if 1 auto p=arrays_.elements; if(!p){return;} for(auto pg=arrays_.groups,last=pg+arrays_.groups_size_mask+1; pg!=last;++pg,p+=N){ - auto mask=pg->match_really_occupied(); + auto mask=pg->match_occupied()&~(int(pg==last-1)<<(N-1)); while(mask){ auto n=unchecked_countr_zero(mask); if(!f(pg,n,p+n))return; From d2b1f095c812a386fd253d6d7c5b4af57625fca5 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 17 Mar 2023 13:51:37 -0700 Subject: [PATCH 044/327] Update atomic_uint nsdmi to eschew potentially deleted copy constructor --- include/boost/unordered/detail/foa/concurrent_table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 2873bf7e..b2350631 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -823,7 +823,7 @@ private: #endif /* TODO: thread_counter should be static */ - mutable std::atomic_uint thread_counter=0; + mutable std::atomic_uint thread_counter{0}; mutable multimutex_type mutexes; }; From 6abb94bb68679b676261e452bc6934c5f4ba8662 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 17 Mar 2023 13:52:12 -0700 Subject: [PATCH 045/327] Silence unused variable warnings in for_all_elements_exec --- include/boost/unordered/detail/foa/concurrent_table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index b2350631..538d74a3 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -796,7 +796,7 @@ private: { for_all_elements_exec( std::forward(policy), - [&](group_type* pg,unsigned int n,element_type* p){f(p);}); + [&](group_type* /* pg */,unsigned int /* n */,element_type* p){f(p);}); } template From b09c5bdcf0f09e632f2d855b827f035ec6a73e5a Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 17 Mar 2023 14:14:24 -0700 Subject: [PATCH 046/327] Add missing header --- include/boost/unordered/detail/foa/concurrent_table.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 538d74a3..2b19eb59 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -28,6 +28,7 @@ #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) #include +#include #endif namespace boost{ From ed076facc4687350246ae7028d3c008f775d40b4 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 17 Mar 2023 14:15:10 -0700 Subject: [PATCH 047/327] Stabilize ABI of cacheline_protected by hardcoding its alignment to 64 --- .../unordered/detail/foa/concurrent_table.hpp | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 2b19eb59..1e5aaa97 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -37,22 +37,7 @@ namespace detail{ namespace foa{ template -struct - -#if defined(__cpp_lib_hardware_interference_size) -#if defined(BOOST_GCC) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Winterference-size" -#endif -alignas(std::hardware_destructive_interference_size) -#if defined(BOOST_GCC) -#pragma GCC diagnostic pop -#endif -#else -alignas(64) -#endif - -cacheline_protected:T +struct alignas(64) cacheline_protected:T { using T::T; }; From 3df435d4d31af84e1c68e907e5d5f89bbd9bc677 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 17 Mar 2023 14:17:10 -0700 Subject: [PATCH 048/327] Temporarily suppress warning about memset'ing non-trivial types by using a constructor loop --- include/boost/unordered/detail/foa/core.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index f7fdc899..59d1084b 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -910,11 +910,14 @@ struct table_arrays reinterpret_cast(p))%sizeof(group_type); arrays.groups=reinterpret_cast(p); + for (std::size_t i=0;i Date: Fri, 17 Mar 2023 14:20:39 -0700 Subject: [PATCH 049/327] Update rw_spinlock impl to mirror github.com/pdimov's impl --- .../unordered/detail/foa/rw_spinlock.hpp | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/include/boost/unordered/detail/foa/rw_spinlock.hpp b/include/boost/unordered/detail/foa/rw_spinlock.hpp index 132c68dd..a429e5e0 100644 --- a/include/boost/unordered/detail/foa/rw_spinlock.hpp +++ b/include/boost/unordered/detail/foa/rw_spinlock.hpp @@ -21,7 +21,11 @@ private: // bit 31: locked exclusive // bit 30: writer pending - // bit 29..: reader lock count + // bit 29..0: reader lock count + + static constexpr std::uint32_t locked_exclusive_mask = 1u << 31; // 0x8000'0000 + static constexpr std::uint32_t writer_pending_mask = 1u << 30; // 0x4000'0000 + static constexpr std::uint32_t reader_lock_count_mask = writer_pending_mask - 1; // 0x3FFF'FFFF std::atomic state_ = {}; @@ -36,7 +40,7 @@ public: { std::uint32_t st = state_.load( std::memory_order_relaxed ); - if( st >= 0x3FFF'FFFF ) + if( st >= reader_lock_count_mask ) { // either bit 31 set, bit 30 set, or reader count is max return false; @@ -54,7 +58,7 @@ public: { std::uint32_t st = state_.load( std::memory_order_relaxed ); - if( st < 0x3FFF'FFFF ) + if( st < reader_lock_count_mask ) { std::uint32_t newst = st + 1; if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return; @@ -81,19 +85,19 @@ public: { std::uint32_t st = state_.load( std::memory_order_relaxed ); - if( st & 0x8000'0000 ) + if( st & locked_exclusive_mask ) { // locked exclusive return false; } - if( st & 0x3FFF'FFFF ) + if( st & reader_lock_count_mask ) { // locked shared return false; } - std::uint32_t newst = 0x8000'0000; + std::uint32_t newst = locked_exclusive_mask; return state_.compare_exchange_strong( st, newst, std::memory_order_acquire, std::memory_order_relaxed ); } @@ -105,18 +109,18 @@ public: { std::uint32_t st = state_.load( std::memory_order_relaxed ); - if( st & 0x8000'0000 ) + if( st & locked_exclusive_mask ) { // locked exclusive, spin } - else if( ( st & 0x3FFF'FFFF ) == 0 ) + else if( ( st & reader_lock_count_mask ) == 0 ) { // not locked exclusive, not locked shared, try to lock - std::uint32_t newst = 0x8000'0000; + std::uint32_t newst = locked_exclusive_mask; if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return; } - else if( st & 0x4000'000 ) + else if( st & writer_pending_mask ) { // writer pending bit already set, nothing to do } @@ -124,7 +128,7 @@ public: { // locked shared, set writer pending bit - std::uint32_t newst = st | 0x4000'0000; + std::uint32_t newst = st | writer_pending_mask; state_.compare_exchange_weak( st, newst, std::memory_order_relaxed, std::memory_order_relaxed ); } @@ -138,19 +142,19 @@ public: for( ;; ) { - if( st & 0x8000'0000 ) + if( st & locked_exclusive_mask ) { // locked exclusive, nothing to do break; } - else if( ( st & 0x3FFF'FFFF ) == 0 ) + else if( ( st & reader_lock_count_mask ) == 0 ) { // lock free, try to take it - std::uint32_t newst = 0x8000'0000; + std::uint32_t newst = locked_exclusive_mask; if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return; } - else if( ( st & 0x4000'0000 ) == 0 ) + else if( ( st & writer_pending_mask ) == 0 ) { // writer pending bit already clear, nothing to do break; @@ -159,7 +163,7 @@ public: { // clear writer pending bit - std::uint32_t newst = st & ~0x4000'0000u; + std::uint32_t newst = st & ~writer_pending_mask; if( state_.compare_exchange_weak( st, newst, std::memory_order_relaxed, std::memory_order_relaxed ) ) break; } } From 2386188d69862bb1197b05ab5da25e2082a84768 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 17 Mar 2023 14:21:48 -0700 Subject: [PATCH 050/327] Relocate macros needed by test suite into detail/fwd.hpp --- include/boost/unordered/detail/fwd.hpp | 86 +++++++++++++++++++ .../boost/unordered/detail/implementation.hpp | 86 ------------------- test/helpers/test.hpp | 1 + 3 files changed, 87 insertions(+), 86 deletions(-) diff --git a/include/boost/unordered/detail/fwd.hpp b/include/boost/unordered/detail/fwd.hpp index acaa8f11..7fcb770e 100644 --- a/include/boost/unordered/detail/fwd.hpp +++ b/include/boost/unordered/detail/fwd.hpp @@ -61,4 +61,90 @@ namespace boost { } } +// BOOST_UNORDERED_EMPLACE_LIMIT = The maximum number of parameters in +// emplace (not including things like hints). Don't set it to a lower value, as +// that might break something. + +#if !defined BOOST_UNORDERED_EMPLACE_LIMIT +#define BOOST_UNORDERED_EMPLACE_LIMIT 10 +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Configuration +// +// Unless documented elsewhere these configuration macros should be considered +// an implementation detail, I'll try not to break them, but you never know. + +// Use Sun C++ workarounds +// I'm not sure which versions of the compiler require these workarounds, so +// I'm just using them of everything older than the current test compilers +// (as of May 2017). + +#if !defined(BOOST_UNORDERED_SUN_WORKAROUNDS1) +#if BOOST_COMP_SUNPRO && BOOST_COMP_SUNPRO < BOOST_VERSION_NUMBER(5, 20, 0) +#define BOOST_UNORDERED_SUN_WORKAROUNDS1 1 +#else +#define BOOST_UNORDERED_SUN_WORKAROUNDS1 0 +#endif +#endif + +// BOOST_UNORDERED_TUPLE_ARGS +// +// Maximum number of std::tuple members to support, or 0 if std::tuple +// isn't avaiable. More are supported when full C++11 is used. + +// Already defined, so do nothing +#if defined(BOOST_UNORDERED_TUPLE_ARGS) + +// Assume if we have C++11 tuple it's properly variadic, +// and just use a max number of 10 arguments. +#elif !defined(BOOST_NO_CXX11_HDR_TUPLE) +#define BOOST_UNORDERED_TUPLE_ARGS 10 + +// Visual C++ has a decent enough tuple for piecewise construction, +// so use that if available, using _VARIADIC_MAX for the maximum +// number of parameters. Note that this comes after the check +// for a full C++11 tuple. +#elif defined(BOOST_MSVC) +#if !BOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT +#define BOOST_UNORDERED_TUPLE_ARGS 0 +#elif defined(_VARIADIC_MAX) +#define BOOST_UNORDERED_TUPLE_ARGS _VARIADIC_MAX +#else +#define BOOST_UNORDERED_TUPLE_ARGS 5 +#endif + +// Assume that we don't have std::tuple +#else +#define BOOST_UNORDERED_TUPLE_ARGS 0 +#endif + +#if BOOST_UNORDERED_TUPLE_ARGS +#include +#endif + +// BOOST_UNORDERED_CXX11_CONSTRUCTION +// +// Use C++11 construction, requires variadic arguments, good construct support +// in allocator_traits and piecewise construction of std::pair +// Otherwise allocators aren't used for construction/destruction + +#if BOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT && \ + !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && BOOST_UNORDERED_TUPLE_ARGS +#if BOOST_COMP_SUNPRO && BOOST_LIB_STD_GNU +// Sun C++ std::pair piecewise construction doesn't seem to be exception safe. +// (At least for Sun C++ 12.5 using libstdc++). +#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0 +#elif BOOST_COMP_GNUC && BOOST_COMP_GNUC < BOOST_VERSION_NUMBER(4, 7, 0) +// Piecewise construction in GCC 4.6 doesn't work for uncopyable types. +#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0 +#elif !defined(BOOST_NO_CXX11_ALLOCATOR) +#define BOOST_UNORDERED_CXX11_CONSTRUCTION 1 +#endif +#endif + +#if !defined(BOOST_UNORDERED_CXX11_CONSTRUCTION) +#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0 +#endif + #endif diff --git a/include/boost/unordered/detail/implementation.hpp b/include/boost/unordered/detail/implementation.hpp index 13351171..68f312a5 100644 --- a/include/boost/unordered/detail/implementation.hpp +++ b/include/boost/unordered/detail/implementation.hpp @@ -60,92 +60,6 @@ #include #endif -//////////////////////////////////////////////////////////////////////////////// -// Configuration -// -// Unless documented elsewhere these configuration macros should be considered -// an implementation detail, I'll try not to break them, but you never know. - -// Use Sun C++ workarounds -// I'm not sure which versions of the compiler require these workarounds, so -// I'm just using them of everything older than the current test compilers -// (as of May 2017). - -#if !defined(BOOST_UNORDERED_SUN_WORKAROUNDS1) -#if BOOST_COMP_SUNPRO && BOOST_COMP_SUNPRO < BOOST_VERSION_NUMBER(5, 20, 0) -#define BOOST_UNORDERED_SUN_WORKAROUNDS1 1 -#else -#define BOOST_UNORDERED_SUN_WORKAROUNDS1 0 -#endif -#endif - -// BOOST_UNORDERED_EMPLACE_LIMIT = The maximum number of parameters in -// emplace (not including things like hints). Don't set it to a lower value, as -// that might break something. - -#if !defined BOOST_UNORDERED_EMPLACE_LIMIT -#define BOOST_UNORDERED_EMPLACE_LIMIT 10 -#endif - -// BOOST_UNORDERED_TUPLE_ARGS -// -// Maximum number of std::tuple members to support, or 0 if std::tuple -// isn't avaiable. More are supported when full C++11 is used. - -// Already defined, so do nothing -#if defined(BOOST_UNORDERED_TUPLE_ARGS) - -// Assume if we have C++11 tuple it's properly variadic, -// and just use a max number of 10 arguments. -#elif !defined(BOOST_NO_CXX11_HDR_TUPLE) -#define BOOST_UNORDERED_TUPLE_ARGS 10 - -// Visual C++ has a decent enough tuple for piecewise construction, -// so use that if available, using _VARIADIC_MAX for the maximum -// number of parameters. Note that this comes after the check -// for a full C++11 tuple. -#elif defined(BOOST_MSVC) -#if !BOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT -#define BOOST_UNORDERED_TUPLE_ARGS 0 -#elif defined(_VARIADIC_MAX) -#define BOOST_UNORDERED_TUPLE_ARGS _VARIADIC_MAX -#else -#define BOOST_UNORDERED_TUPLE_ARGS 5 -#endif - -// Assume that we don't have std::tuple -#else -#define BOOST_UNORDERED_TUPLE_ARGS 0 -#endif - -#if BOOST_UNORDERED_TUPLE_ARGS -#include -#endif - -// BOOST_UNORDERED_CXX11_CONSTRUCTION -// -// Use C++11 construction, requires variadic arguments, good construct support -// in allocator_traits and piecewise construction of std::pair -// Otherwise allocators aren't used for construction/destruction - -#if BOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT && \ - !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && BOOST_UNORDERED_TUPLE_ARGS -#if BOOST_COMP_SUNPRO && BOOST_LIB_STD_GNU -// Sun C++ std::pair piecewise construction doesn't seem to be exception safe. -// (At least for Sun C++ 12.5 using libstdc++). -#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0 -#elif BOOST_COMP_GNUC && BOOST_COMP_GNUC < BOOST_VERSION_NUMBER(4, 7, 0) -// Piecewise construction in GCC 4.6 doesn't work for uncopyable types. -#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0 -#elif !defined(BOOST_NO_CXX11_ALLOCATOR) -#define BOOST_UNORDERED_CXX11_CONSTRUCTION 1 -#endif -#endif - -#if !defined(BOOST_UNORDERED_CXX11_CONSTRUCTION) -#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0 -#endif - #if BOOST_UNORDERED_CXX11_CONSTRUCTION #include #include diff --git a/test/helpers/test.hpp b/test/helpers/test.hpp index dabd7c24..3417178d 100644 --- a/test/helpers/test.hpp +++ b/test/helpers/test.hpp @@ -6,6 +6,7 @@ #if !defined(BOOST_UNORDERED_TEST_TEST_HEADER) #define BOOST_UNORDERED_TEST_TEST_HEADER +#include #include #include #include From 12935eb6952bb4fd30e8af16345ea0d610ee9135 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 17 Mar 2023 14:22:24 -0700 Subject: [PATCH 051/327] 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() From 9e4f89aa4367be1dcd6aff32c040a265991e8d50 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 17 Mar 2023 15:57:40 -0700 Subject: [PATCH 052/327] Continue fleshing out insert_tests --- .../boost/unordered/concurrent_flat_map.hpp | 16 ++++- test/cfoa/insert_tests.cpp | 60 +++++++++++++++---- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 8cd515d3..8a981db9 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -89,7 +89,7 @@ namespace boost { class concurrent_flat_map { private: - using type_policy = detail::concurrent_map_types; + using type_policy = detail::concurrent_map_types; detail::foa::concurrent_table table_; @@ -117,9 +117,19 @@ namespace boost { { } - bool insert(value_type const& obj) + /// Capacity + /// + + size_type size() const noexcept { return table_.size(); } + + /// Modifiers + /// + + bool insert(value_type const& obj) { return table_.insert(obj); } + + template std::size_t visit_all(F f) { - return table_.insert(obj); + return table_.visit_all(std::move(f)); } }; } // namespace unordered diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index 817b96b1..82e081c2 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -5,9 +5,11 @@ #include "../helpers/generators.hpp" #include "../helpers/test.hpp" -#include #include +#include +#include + #include #include #include @@ -111,30 +113,58 @@ std::vector > make_random_values( 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) + struct lvalue_inserter_type { - raii::reset_counts(); - - auto values = make_random_values(1024, generator); - BOOST_TEST_GT(values.size(), 0); - + template + void operator()(std::vector const& values, X& x) { - X x; - for (auto const& r : values) { bool b = x.insert(r); (void)b; } } + } lvalue_inserter; + + struct rvalue_inserter_type + { + template void operator()(std::vector& values, X& x) + { + for (auto& r : values) { + bool b = x.insert(std::move(r)); + (void)b; + } + } + } rvalue_inserter; + + template + void insert(X*, F inserter, test::random_generator generator) + { + auto values = make_random_values(1024, generator); + BOOST_TEST_GT(values.size(), 0); + + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + + raii::reset_counts(); + + { + X x; + + inserter(values, x); + + BOOST_TEST_EQ(x.size(), reference_map.size()); + + using value_type = typename X::value_type; + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + })); + } BOOST_TEST_GE(raii::default_constructor, 0); BOOST_TEST_GE(raii::copy_constructor, 0); @@ -157,7 +187,11 @@ using test::default_generator; using test::generate_collisions; using test::limited_range; +// clang-format off UNORDERED_TEST( - insert, ((map))((default_generator)(generate_collisions)(limited_range))) + insert, ((map)) + ((lvalue_inserter)(rvalue_inserter)) + ((default_generator)(generate_collisions)(limited_range))) +// clang-format on RUN_TESTS() From c5debf11cf65fce5f9ebbef0a05f389173df6fea Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 09:15:28 +0100 Subject: [PATCH 053/327] added explicit disabling/enabling of parallel algorithms --- include/boost/unordered/detail/foa/concurrent_table.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 1e5aaa97..072bf0f0 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -26,6 +26,13 @@ #include #include +#if !defined(BOOST_UNORDERED_DISABLE_PARALLEL_ALGORITHMS) +#if defined(BOOST_UNORDERED_ENABLE_PARALLEL_ALGORITHMS)|| \ + !defined(BOOST_NO_CXX17_HDR_EXECUTION) +#define BOOST_UNORDERED_PARALLEL_ALGORITHMS +#endif +#endif + #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) #include #include From 0d1fdae84c1b77d42c2a5cd46ca1047b6a5eae03 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 09:21:30 +0100 Subject: [PATCH 054/327] implemented safe concurrent_table::load_factor --- .../unordered/detail/foa/concurrent_table.hpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 072bf0f0..066f49cf 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -311,9 +311,7 @@ public: std::size_t size()const noexcept { auto lck=shared_access(); - std::size_t ml_=this->ml; - std::size_t size_=this->size_; - return size_<=ml_?size_:ml_; + return unprotected_size(); } using super::max_size; @@ -485,7 +483,9 @@ public: float load_factor()const noexcept { auto lck=shared_access(); - return super::load_factor(); + if(super::capacity()==0)return 0; + else return float(unprotected_size())/ + float(super::capacity()); } using super::max_load_factor; @@ -568,6 +568,13 @@ private: return this->arrays.groups[pos].insert_counter(); } + std::size_t unprotected_size()const + { + std::size_t ml_=this->ml; + std::size_t size_=this->size_; + return size_<=ml_?size_:ml_; + } + struct erase_on_exit { erase_on_exit( From fb4437219fb9f5b0e8463e35986cdb001ab898de Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 09:33:13 +0100 Subject: [PATCH 055/327] fixed memory ordering in atomic_integral --- include/boost/unordered/detail/foa/concurrent_table.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 066f49cf..cbd7611e 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -137,8 +137,8 @@ struct atomic_integral { operator Integral()const{return n.load(std::memory_order_acquire);} void operator=(Integral m){n.store(m,std::memory_order_release);} - void operator|=(Integral m){n.fetch_or(m,std::memory_order_release);} - void operator&=(Integral m){n.fetch_and(m,std::memory_order_release);} + void operator|=(Integral m){n.fetch_or(m,std::memory_order_acq_rel);} + void operator&=(Integral m){n.fetch_and(m,std::memory_order_acq_rel);} std::atomic n; }; From 25ba8e4dfc6f8cd8ed93255bc713298de6e7e770 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 09:36:12 +0100 Subject: [PATCH 056/327] shut down VS C4324 warning --- include/boost/unordered/detail/foa/concurrent_table.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index cbd7611e..1989da78 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -102,6 +102,11 @@ private: /* copied from boost/multi_index/detail/scoped_bilock.hpp */ +#if defined(BOOST_MSVC) +#pragma warning(push) +#pragma warning(disable:4324) /* padded structure due to alignas */ +#endif + template class scoped_bilock { @@ -130,6 +135,10 @@ private: storage2[sizeof(lock_guard_type)]; }; +#if defined(BOOST_MSVC) +#pragma warning(pop) /* C4324 */ +#endif + /* TODO: describe atomic_integral and group_access */ template From 1d8c0f91f88eead2415511a01e93f8d34bc00f39 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 09:53:17 +0100 Subject: [PATCH 057/327] avoided name hiding --- include/boost/unordered/detail/foa/concurrent_table.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 1989da78..f1519dfc 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -579,9 +579,9 @@ private: std::size_t unprotected_size()const { - std::size_t ml_=this->ml; - std::size_t size_=this->size_; - return size_<=ml_?size_:ml_; + std::size_t m=this->ml; + std::size_t s=this->size_; + return s<=m?s:m; } struct erase_on_exit From a89c62728a46971746193f47976a7215da66c8e4 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 10:13:53 +0100 Subject: [PATCH 058/327] removed unnecessary mutable --- include/boost/unordered/detail/foa/concurrent_table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index f1519dfc..255b89d7 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -65,7 +65,7 @@ public: void unlock()noexcept{for(auto n=N;n>0;)mutexes[--n].unlock();} private: - mutable std::array mutexes; + std::array mutexes; }; /* std::shared_lock is C++14 */ From 454fc7501e17cddf4ec009c44fee852b6d5552c1 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 10:15:43 +0100 Subject: [PATCH 059/327] extended shutting down VS C4324 warning --- .../boost/unordered/detail/foa/concurrent_table.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 255b89d7..465cc6c2 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -43,6 +43,11 @@ namespace unordered{ namespace detail{ namespace foa{ +#if defined(BOOST_MSVC) +#pragma warning(push) +#pragma warning(disable:4324) /* padded structure due to alignas */ +#endif + template struct alignas(64) cacheline_protected:T { @@ -102,11 +107,6 @@ private: /* copied from boost/multi_index/detail/scoped_bilock.hpp */ -#if defined(BOOST_MSVC) -#pragma warning(push) -#pragma warning(disable:4324) /* padded structure due to alignas */ -#endif - template class scoped_bilock { From e64fd45760c475cf30f2863431b9e1973d34d8ee Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 10:25:50 +0100 Subject: [PATCH 060/327] removed inefficient group15::match_really_occupied --- .../unordered/detail/foa/concurrent_table.hpp | 4 +- include/boost/unordered/detail/foa/core.hpp | 46 ++++--------------- 2 files changed, 10 insertions(+), 40 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 465cc6c2..4698ab87 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -805,7 +805,7 @@ private: { for_all_elements_exec( std::forward(policy), - [&](group_type* /* pg */,unsigned int /* n */,element_type* p){f(p);}); + [&](group_type*,unsigned int,element_type* p){f(p);}); } template @@ -820,7 +820,7 @@ private: std::size_t pos=&g-first; auto p=this->arrays.elements+pos*N; auto lck=exclusive_access(pos); - auto mask=g.match_really_occupied(); + auto mask=this->match_really_occupied(&g,last); while(mask){ auto n=unchecked_countr_zero(mask); f(&g,n,p+n); diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 59d1084b..324b24c2 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -260,11 +260,6 @@ struct group15 return (~match_available())&0x7FFF; } - inline int match_really_occupied()const /* excluding sentinel */ - { - return at(N-1)==sentinel_?match_occupied()&0x3FFF:match_occupied(); - } - private: using slot_type=IntegralWrapper; BOOST_STATIC_ASSERT(sizeof(slot_type)==1); @@ -443,11 +438,6 @@ struct group15 vdupq_n_u8(0)))&0x7FFF; } - inline int match_really_occupied()const /* excluding sentinel */ - { - return at(N-1)==sentinel_?match_occupied()&0x3FFF:match_occupied(); - } - private: using slot_type=IntegralWrapper; BOOST_STATIC_ASSERT(sizeof(slot_type)==1); @@ -623,11 +613,6 @@ struct group15 return y&0x7FFF; } - inline int match_really_occupied()const /* excluding sentinel */ - { - return ~(match_impl(0)|match_impl(1))&0x7FFF; - } - private: using word_type=IntegralWrapper; BOOST_STATIC_ASSERT(sizeof(word_type)==8); @@ -1403,7 +1388,7 @@ public: if(p){ for(auto pg=arrays.groups,last=pg+arrays.groups_size_mask+1; pg!=last;++pg,p+=N){ - auto mask=pg->match_really_occupied(); + auto mask=match_really_occupied(pg,last); while(mask){ destroy_element(p+unchecked_countr_zero(mask)); mask&=mask-1; @@ -1520,6 +1505,12 @@ public: return size_policy::position(hash,arrays_.groups_size_index); } + static inline int match_really_occupied(group_type* pg,group_type* last) + { + /* excluding the sentinel */ + return pg->match_occupied()&~(int(pg==last-1)<<(N-1)); + } + static inline void prefetch_elements(const element_type* p) { /* We have experimentally confirmed that ARM architectures get a higher @@ -1637,38 +1628,17 @@ public: static auto for_all_elements_while(const arrays_type& arrays_,F f) ->decltype(f(nullptr,0,nullptr),void()) { -#if 1 auto p=arrays_.elements; if(!p){return;} for(auto pg=arrays_.groups,last=pg+arrays_.groups_size_mask+1; pg!=last;++pg,p+=N){ - auto mask=pg->match_occupied()&~(int(pg==last-1)<<(N-1)); + auto mask=match_really_occupied(pg,last); while(mask){ auto n=unchecked_countr_zero(mask); if(!f(pg,n,p+n))return; mask&=mask-1; } } -#else - auto p=arrays_.elements; - if(!p){return;} - auto pg=arrays_.groups; - for(auto last=pg+arrays_.groups_size_mask; - pg!=last;++pg,p+=N){ - auto mask=pg->match_occupied(); - while(mask){ - auto n=unchecked_countr_zero(mask); - if(!f(pg,n,p+n))return; - mask&=mask-1; - } - } - auto mask=pg->match_really_occupied(); - while(mask){ - auto n=unchecked_countr_zero(mask); - if(!f(pg,n,p+n))return; - mask&=mask-1; - } -#endif } arrays_type arrays; From 2e447692473d0c8ec86848ad2f597ef30a0208d1 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 11:56:14 +0100 Subject: [PATCH 061/327] added preliminary version of non-embedded group_access --- .../unordered/detail/foa/concurrent_table.hpp | 85 ++++++++++++++++++- include/boost/unordered/detail/foa/core.hpp | 11 ++- include/boost/unordered/detail/foa/table.hpp | 3 +- 3 files changed, 92 insertions(+), 7 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 4698ab87..3aba4a54 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -182,13 +182,70 @@ struct concurrent_group:Group,group_access }; }; +template +struct concurrent_table_arrays:table_arrays +{ + using super=table_arrays; + + template + static concurrent_table_arrays new_(Allocator& al,std::size_t n) + { + concurrent_table_arrays arrays={super::new_(al,n),nullptr}; + //if(arrays.elements){ + using access_alloc= + typename boost::allocator_rebind::type; + using access_traits=boost::allocator_traits; + using pointer=typename access_traits::pointer; + using pointer_traits=boost::pointer_traits; + + // TODO: protect with BOOST_TRY + auto aal=access_alloc(al); + arrays.group_accesses=boost::to_address( + access_traits::allocate(aal,arrays.groups_size_mask+1)); + + for(std::size_t n=0;n + static void delete_(Allocator& al,concurrent_table_arrays& arrays)noexcept + { + //if(arrays.elements){ + using access_alloc= + typename boost::allocator_rebind::type; + using access_traits=boost::allocator_traits; + using pointer=typename access_traits::pointer; + using pointer_traits=boost::pointer_traits; + + auto aal=access_alloc(al); + access_traits::deallocate( + aal,pointer_traits::pointer_to(*arrays.group_accesses), + arrays.groups_size_mask+1); + //} + } + + group_access *group_accesses; +}; + /* TODO: describe foa::concurrent_table. */ template using concurrent_table_core_impl=table_core< - TypePolicy,concurrent_group>,std::atomic_size_t, - Hash,Pred,Allocator>; + TypePolicy, + +#if defined(BOOST_UNORDERED_EMBEDDED_GROUP_ACCESS) + concurrent_group>, + table_arrays, +#else + group15, + concurrent_table_arrays, +#endif + + std::atomic_size_t,Hash,Pred,Allocator>; #include @@ -529,8 +586,15 @@ private: using shared_lock_guard=shared_lock; using exclusive_lock_guard=lock_guard; using exclusive_bilock_guard=scoped_bilock; + +#if defined(BOOST_UNORDERED_EMBEDDED_GROUP_ACCESS) using group_shared_lock_guard=typename group_type::shared_lock_guard; using group_exclusive_lock_guard=typename group_type::exclusive_lock_guard; +#else + using group_shared_lock_guard=typename group_access::shared_lock_guard; + using group_exclusive_lock_guard=typename group_access::exclusive_lock_guard; +#endif + concurrent_table(const concurrent_table& x,exclusive_lock_guard): super{x}{} @@ -562,6 +626,7 @@ private: return {x.mutexes,y.mutexes}; } +#if defined(BOOST_UNORDERED_EMBEDDED_GROUP_ACCESS) group_shared_lock_guard shared_access(std::size_t pos)const { return this->arrays.groups[pos].shared_access(); @@ -576,6 +641,22 @@ private: { return this->arrays.groups[pos].insert_counter(); } +#else + group_shared_lock_guard shared_access(std::size_t pos)const + { + return this->arrays.group_accesses[pos].shared_access(); + } + + group_exclusive_lock_guard exclusive_access(std::size_t pos)const + { + return this->arrays.group_accesses[pos].exclusive_access(); + } + + std::atomic_uint32_t& insert_counter(std::size_t pos)const + { + return this->arrays.group_accesses[pos].insert_counter(); + } +#endif std::size_t unprotected_size()const { diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 324b24c2..0b67911d 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1134,8 +1134,8 @@ alloc_make_insert_type(const Allocator& al,Args&&... args) #endif template< - typename TypePolicy,typename Group,typename SizeImpl, - typename Hash,typename Pred,typename Allocator + typename TypePolicy,typename Group,template class Arrays, + typename SizeImpl,typename Hash,typename Pred,typename Allocator > class @@ -1158,7 +1158,7 @@ public: >::type; using alloc_traits=boost::allocator_traits; using element_type=typename type_policy::element_type; - using arrays_type=table_arrays; + using arrays_type=Arrays; using key_type=typename type_policy::key_type; using init_type=typename type_policy::init_type; @@ -1646,7 +1646,10 @@ public: SizeImpl size_; private: - template + template< + typename,typename,template class, + typename,typename,typename,typename + > friend class table_core; using hash_base=empty_value; diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 750e2db8..1cc23154 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -218,7 +218,8 @@ private: template using table_core_impl= - table_core,std::size_t,Hash,Pred,Allocator>; + table_core,table_arrays, + std::size_t,Hash,Pred,Allocator>; #include From d66ceaf7fe8b56f74e6208362808f06c7c191084 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 12:30:03 +0100 Subject: [PATCH 062/327] avoided std::atomic_* aliases --- .../unordered/detail/foa/concurrent_table.hpp | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 3aba4a54..f11b2a91 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -162,14 +162,15 @@ struct group_access using mutex_type=rw_spinlock; using shared_lock_guard=shared_lock; using exclusive_lock_guard=lock_guard; + using insert_counter_type=std::atomic; - shared_lock_guard shared_access(){return shared_lock_guard{m};} - exclusive_lock_guard exclusive_access(){return exclusive_lock_guard{m};} - std::atomic_uint32_t& insert_counter(){return cnt;} + shared_lock_guard shared_access(){return shared_lock_guard{m};} + exclusive_lock_guard exclusive_access(){return exclusive_lock_guard{m};} + insert_counter_type& insert_counter(){return cnt;} private: - mutex_type m; - std::atomic_uint32_t cnt; + mutex_type m; + insert_counter_type cnt; }; template @@ -245,7 +246,7 @@ using concurrent_table_core_impl=table_core< concurrent_table_arrays, #endif - std::atomic_size_t,Hash,Pred,Allocator>; + std::atomic,Hash,Pred,Allocator>; #include @@ -590,9 +591,11 @@ private: #if defined(BOOST_UNORDERED_EMBEDDED_GROUP_ACCESS) using group_shared_lock_guard=typename group_type::shared_lock_guard; using group_exclusive_lock_guard=typename group_type::exclusive_lock_guard; + using group_insert_counter_type=typename group_type::insert_counter_type; #else using group_shared_lock_guard=typename group_access::shared_lock_guard; using group_exclusive_lock_guard=typename group_access::exclusive_lock_guard; + using group_insert_counter_type=typename group_access::insert_counter_type; #endif @@ -637,7 +640,7 @@ private: return this->arrays.groups[pos].exclusive_access(); } - std::atomic_uint32_t& insert_counter(std::size_t pos)const + group_insert_counter_type& insert_counter(std::size_t pos)const { return this->arrays.groups[pos].insert_counter(); } @@ -652,7 +655,7 @@ private: return this->arrays.group_accesses[pos].exclusive_access(); } - std::atomic_uint32_t& insert_counter(std::size_t pos)const + group_insert_counter_type& insert_counter(std::size_t pos)const { return this->arrays.group_accesses[pos].insert_counter(); } @@ -913,8 +916,8 @@ private: #endif /* TODO: thread_counter should be static */ - mutable std::atomic_uint thread_counter{0}; - mutable multimutex_type mutexes; + mutable std::atomic thread_counter{0}; + mutable multimutex_type mutexes; }; #if defined(BOOST_MSVC) From 96562f4c3bfd29e517d5107038953f92f6ced633 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 12:58:17 +0100 Subject: [PATCH 063/327] hardened non-embedded group_access --- .../unordered/detail/foa/concurrent_table.hpp | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index f11b2a91..b3c499f8 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -183,6 +183,19 @@ struct concurrent_group:Group,group_access }; }; +template +group_access* dummy_group_accesses() +{ + /* TODO: describe + */ + + static constexpr group_access::dummy_group_access_type + storage[Size]={typename group_access::dummy_group_access_type(),}; + + return reinterpret_cast( + const_cast(storage)); +} + template struct concurrent_table_arrays:table_arrays { @@ -192,29 +205,38 @@ struct concurrent_table_arrays:table_arrays static concurrent_table_arrays new_(Allocator& al,std::size_t n) { concurrent_table_arrays arrays={super::new_(al,n),nullptr}; - //if(arrays.elements){ + if(!arrays.elements){ + arrays.group_accesses=dummy_group_accesses(); + } + else{ using access_alloc= typename boost::allocator_rebind::type; using access_traits=boost::allocator_traits; using pointer=typename access_traits::pointer; using pointer_traits=boost::pointer_traits; - // TODO: protect with BOOST_TRY - auto aal=access_alloc(al); - arrays.group_accesses=boost::to_address( - access_traits::allocate(aal,arrays.groups_size_mask+1)); + BOOST_TRY{ + auto aal=access_alloc(al); + arrays.group_accesses=boost::to_address( + access_traits::allocate(aal,arrays.groups_size_mask+1)); - for(std::size_t n=0;n static void delete_(Allocator& al,concurrent_table_arrays& arrays)noexcept { - //if(arrays.elements){ + if(arrays.elements){ using access_alloc= typename boost::allocator_rebind::type; using access_traits=boost::allocator_traits; @@ -225,7 +247,7 @@ struct concurrent_table_arrays:table_arrays access_traits::deallocate( aal,pointer_traits::pointer_to(*arrays.group_accesses), arrays.groups_size_mask+1); - //} + } } group_access *group_accesses; From 77eaab4803f4b05530250a3a2689a5fc4c17d0f5 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 13:14:49 +0100 Subject: [PATCH 064/327] provided concurrent_table_arrays ctor to satisfy older compilers --- include/boost/unordered/detail/foa/concurrent_table.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index b3c499f8..ca30728b 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -201,10 +201,13 @@ struct concurrent_table_arrays:table_arrays { using super=table_arrays; + concurrent_table_arrays(const super& arrays,group_access *pga): + super{arrays},group_accesses{pga}{} + template static concurrent_table_arrays new_(Allocator& al,std::size_t n) { - concurrent_table_arrays arrays={super::new_(al,n),nullptr}; + concurrent_table_arrays arrays{super::new_(al,n),nullptr}; if(!arrays.elements){ arrays.group_accesses=dummy_group_accesses(); } From ce722e4d5627b5481f30c95bd92ec15e6ffeb252 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 15:52:08 +0100 Subject: [PATCH 065/327] avoided shadowing --- include/boost/unordered/detail/foa/concurrent_table.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index ca30728b..ab9e3a4a 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -223,8 +223,8 @@ struct concurrent_table_arrays:table_arrays arrays.group_accesses=boost::to_address( access_traits::allocate(aal,arrays.groups_size_mask+1)); - for(std::size_t n=0;n Date: Sat, 18 Mar 2023 16:12:39 +0100 Subject: [PATCH 066/327] fixed UB in dummy_group_accesses --- include/boost/unordered/detail/foa/concurrent_table.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index ab9e3a4a..88d31455 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -189,11 +189,10 @@ group_access* dummy_group_accesses() /* TODO: describe */ - static constexpr group_access::dummy_group_access_type + static group_access::dummy_group_access_type storage[Size]={typename group_access::dummy_group_access_type(),}; - return reinterpret_cast( - const_cast(storage)); + return reinterpret_cast(storage); } template From d8e950386997d9fb6ee2a78a455781508a92a7fd Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 16:16:57 +0100 Subject: [PATCH 067/327] made table_arrays non-aggregate for the benefit of older compilers --- include/boost/unordered/detail/foa/core.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 0b67911d..6d7e8df4 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -867,6 +867,9 @@ struct table_arrays static constexpr auto N=group_type::N; using size_policy=SizePolicy; + table_arrays(std::size_t gsi,std::size_t gsm,group_type *pg,value_type *pe): + groups_size_index{gsi},groups_size_mask{gsm},groups{pg},elements{pe}{} + template static table_arrays new_(Allocator& al,std::size_t n) { From 68563fab721be1df2b6c9cea188366e6e0c7f204 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 17:09:16 +0100 Subject: [PATCH 068/327] removed unused typedef --- include/boost/unordered/detail/foa/concurrent_table.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 88d31455..cd2061c8 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -215,7 +215,6 @@ struct concurrent_table_arrays:table_arrays typename boost::allocator_rebind::type; using access_traits=boost::allocator_traits; using pointer=typename access_traits::pointer; - using pointer_traits=boost::pointer_traits; BOOST_TRY{ auto aal=access_alloc(al); From ba4f321934bc822586d18db9d22319305522e5b4 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 18:01:05 +0100 Subject: [PATCH 069/327] removed unused typedef --- include/boost/unordered/detail/foa/concurrent_table.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index cd2061c8..e6aede9d 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -214,7 +214,6 @@ struct concurrent_table_arrays:table_arrays using access_alloc= typename boost::allocator_rebind::type; using access_traits=boost::allocator_traits; - using pointer=typename access_traits::pointer; BOOST_TRY{ auto aal=access_alloc(al); From 63f51e7c41c70512e1287c6d79935ff1c897afcb Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 18 Mar 2023 19:37:49 +0100 Subject: [PATCH 070/327] fixed concurrent_table_arrays::delete_ --- include/boost/unordered/detail/foa/concurrent_table.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index e6aede9d..c010997f 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -248,6 +248,7 @@ struct concurrent_table_arrays:table_arrays aal,pointer_traits::pointer_to(*arrays.group_accesses), arrays.groups_size_mask+1); } + super::delete_(al,arrays); } group_access *group_accesses; From 41abb96d83464f65a69e03e1541bcd32b49a42bc Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 19 Mar 2023 12:11:42 +0100 Subject: [PATCH 071/327] avoided sign conversion warning --- test/cfoa/insert_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index 82e081c2..1d3a6ac8 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -146,7 +146,7 @@ namespace { void insert(X*, F inserter, test::random_generator generator) { auto values = make_random_values(1024, generator); - BOOST_TEST_GT(values.size(), 0); + BOOST_TEST_GT(values.size(), 0u); auto reference_map = boost::unordered_flat_map(values.begin(), values.end()); From 2fae05ed316a81e9d6b795367106cfaa410b7419 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 19 Mar 2023 13:08:29 +0100 Subject: [PATCH 072/327] made concurrent_table use table_core's automatic capacity growth formula --- .../unordered/detail/foa/concurrent_table.hpp | 4 +-- include/boost/unordered/detail/foa/core.hpp | 35 ++++++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index c010997f..c33f594d 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -621,7 +621,6 @@ private: using group_insert_counter_type=typename group_access::insert_counter_type; #endif - concurrent_table(const concurrent_table& x,exclusive_lock_guard): super{x}{} concurrent_table(concurrent_table&& x,exclusive_lock_guard): @@ -901,8 +900,7 @@ private: void rehash_if_full() { auto lck=exclusive_access(); - // TODO: use same mechanism as unchecked_emplace_with_rehash - if(this->size_==this->ml)super::rehash(super::capacity()+1); + if(this->size_==this->ml)this->unchecked_rehash_for_growth(); } #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 6d7e8df4..feefe33c 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1546,22 +1546,17 @@ public: return res; } + BOOST_NOINLINE void unchecked_rehash_for_growth() + { + auto new_arrays_=new_arrays_for_growth(); + unchecked_rehash(new_arrays_); + } + template BOOST_NOINLINE locator unchecked_emplace_with_rehash(std::size_t hash,Args&&... args) { - /* Due to the anti-drift mechanism (see recover_slot), new_arrays_ may be - * of the same size as the old arrays; in the limit, erasing one element at - * full load and then inserting could bring us back to the same capacity - * after a costly rehash. To avoid this, we jump to the next capacity level - * when the number of erased elements is <= 10% of total elements at full - * load, which is implemented by requesting additional F*size elements, - * with F = P * 10% / (1 - P * 10%), where P is the probability of an - * element having caused overflow; P has been measured as ~0.162 under - * ideal conditions, yielding F ~ 0.0165 ~ 1/61. - */ - auto new_arrays_=new_arrays(std::size_t( - std::ceil(static_cast(size()+size()/61+1)/mlf))); + auto new_arrays_=new_arrays_for_growth(); locator it; BOOST_TRY{ /* strong exception guarantee -> try insertion before rehash */ @@ -1664,6 +1659,22 @@ private: return arrays_type::new_(al(),n); } + arrays_type new_arrays_for_growth() + { + /* Due to the anti-drift mechanism (see recover_slot), the new arrays may + * be of the same size as the old arrays; in the limit, erasing one + * element at full load and then inserting could bring us back to the same + * capacity after a costly rehash. To avoid this, we jump to the next + * capacity level when the number of erased elements is <= 10% of total + * elements at full load, which is implemented by requesting additional + * F*size elements, with F = P * 10% / (1 - P * 10%), where P is the + * probability of an element having caused overflow; P has been measured as + * ~0.162 under ideal conditions, yielding F ~ 0.0165 ~ 1/61. + */ + return new_arrays(std::size_t( + std::ceil(static_cast(size()+size()/61+1)/mlf))); + } + void delete_arrays(arrays_type& arrays_)noexcept { arrays_type::delete_(al(),arrays_); From 4c927cd2f22dcf212a10420eb03acf9b6e09b357 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 20 Mar 2023 16:00:54 -0700 Subject: [PATCH 073/327] Implement myriad insert() overloads, get first threaded test working --- .../boost/unordered/concurrent_flat_map.hpp | 12 ++ test/cfoa/insert_tests.cpp | 167 ++++++++++++++---- 2 files changed, 147 insertions(+), 32 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 8a981db9..0be57eef 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -126,6 +126,18 @@ namespace boost { /// bool insert(value_type const& obj) { return table_.insert(obj); } + bool insert(value_type&& obj) { return table_.insert(obj); } + + bool insert(init_type const& obj) { return table_.insert(obj); } + bool insert(init_type&& obj) { return table_.insert(std::move(obj)); } + + template + void insert(InputIterator begin, InputIterator end) + { + for (auto pos = begin; pos != end; ++pos) { + table_.insert(*pos); + } + } template std::size_t visit_all(F f) { diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index 1d3a6ac8..eb63403b 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -8,12 +8,17 @@ #include #include +#include #include #include +#include #include +#include #include +constexpr std::size_t const num_threads = 16; + struct raii { static std::atomic_int default_constructor; @@ -55,19 +60,19 @@ struct raii return *this; } - static void reset_counts() + friend bool operator==(raii const& lhs, raii const& rhs) { - default_constructor = 0; - copy_constructor = 0; - move_constructor = 0; - destructor = 0; - copy_assignment = 0; - move_assignment = 0; + return lhs.x_ == rhs.x_; + } + + friend bool operator!=(raii const& lhs, raii const& rhs) + { + return !(lhs == rhs); } friend std::ostream& operator<<(std::ostream& os, raii const& rhs) { - os << "{ x_: " << rhs.x_ << "}"; + os << "{ x_: " << rhs.x_ << " }"; return os; } @@ -78,14 +83,14 @@ struct raii return os; } - friend bool operator==(raii const& lhs, raii const& rhs) + static void reset_counts() { - return lhs.x_ == rhs.x_; - } - - friend bool operator!=(raii const& lhs, raii const& rhs) - { - return !(lhs == rhs); + default_constructor = 0; + copy_constructor = 0; + move_constructor = 0; + destructor = 0; + copy_assignment = 0; + move_assignment = 0; } }; @@ -102,16 +107,15 @@ std::size_t hash_value(raii const& r) noexcept return hasher(r.x_); } -std::vector > make_random_values( - std::size_t count, test::random_generator rg) +template +auto make_random_values(std::size_t count, F f) -> std::vector { - std::vector > v; + using vector_type = std::vector; + + vector_type 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}); + v.emplace_back(f()); } return v; } @@ -119,14 +123,104 @@ std::vector > make_random_values( namespace { test::seed_t initialize_seed(78937); + struct value_type_generator_type + { + std::pair operator()(test::random_generator rg) + { + int* p = nullptr; + int a = generate(p, rg); + int b = generate(p, rg); + return std::make_pair(raii{a}, raii{b}); + } + } value_type_generator; + + struct init_type_generator_type + { + std::pair operator()(test::random_generator rg) + { + int* p = nullptr; + int a = generate(p, rg); + int b = generate(p, rg); + return std::make_pair(raii{a}, raii{b}); + } + } init_type_generator; + + template + std::vector > split( + std::vector& vec, std::size_t const nt /* num threads*/) + { + std::vector > subslices; + subslices.reserve(nt); + + boost::span s(vec); + + auto a = vec.size() / nt; + auto b = a; + if (vec.size() % nt != 0) { + ++b; + } + + auto num_a = nt; + auto num_b = std::size_t{0}; + + if (nt * b > vec.size()) { + num_a = nt * b - vec.size(); + num_b = nt - num_a; + } + + auto sub_b = s.subspan(0, num_b * b); + auto sub_a = s.subspan(num_b * b); + + for (std::size_t i = 0; i < num_b; ++i) { + subslices.push_back(sub_b.subspan(i * b, b)); + } + + for (std::size_t i = 0; i < num_a; ++i) { + auto const is_last = i == (num_a - 1); + subslices.push_back( + sub_a.subspan(i * a, is_last ? boost::dynamic_extent : a)); + } + + return subslices; + } + struct lvalue_inserter_type { - template - void operator()(std::vector const& values, X& x) + template void operator()(std::vector& values, X& x) { - for (auto const& r : values) { - bool b = x.insert(r); - (void)b; + std::mutex m; + std::vector threads; + std::condition_variable cv; + + auto ready = false; + + auto subslices = split(values, num_threads); + + BOOST_ASSERT(subslices.size() == num_threads); + + for (std::size_t i = 0; i < num_threads; ++i) { + threads.emplace_back([&x, &m, &ready, &subslices, &cv, i] { + std::unique_lock lk(m); + cv.wait(lk, [&] { return ready; }); + lk.unlock(); + + auto s = subslices[i]; + + for (auto const& r : s) { + bool b = x.insert(r); + (void)b; + } + }); + } + + { + std::unique_lock lk(m); + ready = true; + } + cv.notify_all(); + + for (auto& t : threads) { + t.join(); } } } lvalue_inserter; @@ -142,10 +236,18 @@ namespace { } } rvalue_inserter; - template - void insert(X*, F inserter, test::random_generator generator) + struct iterator_range_inserter_type { - auto values = make_random_values(1024, generator); + template void operator()(std::vector& values, X& x) + { + x.insert(values.begin(), values.end()); + } + } iterator_range_inserter; + + template + void insert(X*, G gen, F inserter, test::random_generator rg) + { + auto values = make_random_values(1024 * 1024, [&] { return gen(rg); }); BOOST_TEST_GT(values.size(), 0u); auto reference_map = @@ -190,8 +292,9 @@ using test::limited_range; // clang-format off UNORDERED_TEST( insert, ((map)) - ((lvalue_inserter)(rvalue_inserter)) - ((default_generator)(generate_collisions)(limited_range))) + ((value_type_generator)(init_type_generator)) + ((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter)) + ((default_generator)(limited_range))) // clang-format on RUN_TESTS() From 7ed7878278b19e500aed2bc680ea7421fa60932a Mon Sep 17 00:00:00 2001 From: joaquintides Date: Tue, 21 Mar 2023 11:50:21 +0100 Subject: [PATCH 074/327] fixed 3df435d4d31af84e1c68e907e5d5f89bbd9bc677 fix --- include/boost/unordered/detail/foa/core.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index feefe33c..1cb19ad0 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -900,6 +900,7 @@ struct table_arrays for (std::size_t i=0;i Date: Tue, 21 Mar 2023 14:47:43 +0100 Subject: [PATCH 075/327] group-synchronized earlier in unprotected_norehash_emplace_or_visit --- .../unordered/detail/foa/concurrent_table.hpp | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index c33f594d..b7d1b453 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -870,25 +870,20 @@ private: for(prober pb(pos0);;pb.next(this->arrays.groups_size_mask)){ auto pos=pb.get(); auto pg=this->arrays.groups+pos; + auto lck=exclusive_access(pos); auto mask=pg->match_available(); if(BOOST_LIKELY(mask!=0)){ - auto lck=exclusive_access(pos); - do{ - auto n=unchecked_countr_zero(mask); - if(BOOST_LIKELY(!pg->is_occupied(n))){ - reserve_slot rslot{pg,n,hash}; - if(BOOST_UNLIKELY(insert_counter(pos0)++!=counter)){ - /* other thread inserted from pos0, need to start over */ - goto startover; - } - auto p=this->arrays.elements+pos*N+n; - this->construct_element(p,std::forward(args)...); - rslot.commit(); - rsize.commit(); - return 1; - } - mask&=mask-1; - }while(mask); + auto n=unchecked_countr_zero(mask); + reserve_slot rslot{pg,n,hash}; + if(BOOST_UNLIKELY(insert_counter(pos0)++!=counter)){ + /* other thread inserted from pos0, need to start over */ + goto startover; + } + auto p=this->arrays.elements+pos*N+n; + this->construct_element(p,std::forward(args)...); + rslot.commit(); + rsize.commit(); + return 1; } pg->mark_overflow(hash); } From a9c2d1daa5df37d64bd84e5039084cfa61c458df Mon Sep 17 00:00:00 2001 From: joaquintides Date: Tue, 21 Mar 2023 19:41:55 +0100 Subject: [PATCH 076/327] abode by TSan on SSE2 --- include/boost/unordered/detail/foa/core.hpp | 30 ++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 1cb19ad0..a84e8592 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -83,6 +83,12 @@ }while(0) #endif +#ifdef __has_feature +#define BOOST_UNORDERED_HAS_FEATURE(x) __has_feature(x) +#else +#define BOOST_UNORDERED_HAS_FEATURE(x) 0 +#endif + #define BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred) \ static_assert(boost::is_nothrow_swappable::value, \ "Template parameter Hash is required to be nothrow Swappable."); \ @@ -213,9 +219,8 @@ struct group15 inline int match(std::size_t hash)const { - auto w=_mm_load_si128(reinterpret_cast(m)); return _mm_movemask_epi8( - _mm_cmpeq_epi8(w,_mm_set1_epi32(match_word(hash))))&0x7FFF; + _mm_cmpeq_epi8(load_si128(),_mm_set1_epi32(match_word(hash))))&0x7FFF; } inline bool is_not_overflowed(std::size_t hash)const @@ -239,9 +244,8 @@ struct group15 inline int match_available()const { - auto w=_mm_load_si128(reinterpret_cast(m)); return _mm_movemask_epi8( - _mm_cmpeq_epi8(w,_mm_setzero_si128()))&0x7FFF; + _mm_cmpeq_epi8(load_si128(),_mm_setzero_si128()))&0x7FFF; } inline bool is_occupied(std::size_t pos)const @@ -267,6 +271,23 @@ private: static constexpr unsigned char available_=0, sentinel_=1; + inline __m128i load_si128()const + { +#if BOOST_UNORDERED_HAS_FEATURE(thread_sanitizer) + /* ThreadSanitizer complains on 1-byte atomic writes combined with + * 16-byte atomic reads. + */ + + return _mm_set_epi8( + (char)m[15],(char)m[14],(char)m[13],(char)m[12], + (char)m[11],(char)m[10],(char)m[ 9],(char)m[ 8], + (char)m[ 7],(char)m[ 6],(char)m[ 5],(char)m[ 4], + (char)m[ 3],(char)m[ 2],(char)m[ 1],(char)m[ 0]); +#else + return _mm_load_si128(reinterpret_cast(m)); +#endif + } + inline static int match_word(std::size_t hash) { static constexpr boost::uint32_t word[]= @@ -1955,5 +1976,6 @@ private: } /* namespace boost */ #undef BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED +#undef BOOST_UNORDERED_HAS_FEATURE #undef BOOST_UNORDERED_HAS_BUILTIN #endif From 0b2829c793a58540cddb3ff710cb81e114529d8b Mon Sep 17 00:00:00 2001 From: joaquintides Date: Tue, 21 Mar 2023 20:03:35 +0100 Subject: [PATCH 077/327] hardened TSan detection --- include/boost/unordered/detail/foa/core.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index a84e8592..5d2ad1ee 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -89,6 +89,11 @@ #define BOOST_UNORDERED_HAS_FEATURE(x) 0 #endif +#if BOOST_UNORDERED_HAS_FEATURE(thread_sanitizer)|| \ + defined(__SANITIZE_THREAD__) +#define BOOST_UNORDERED_THREAD_SANITIZER +#endif + #define BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred) \ static_assert(boost::is_nothrow_swappable::value, \ "Template parameter Hash is required to be nothrow Swappable."); \ @@ -273,7 +278,7 @@ private: inline __m128i load_si128()const { -#if BOOST_UNORDERED_HAS_FEATURE(thread_sanitizer) +#if defined(BOOST_UNORDERED_THREAD_SANITIZER) /* ThreadSanitizer complains on 1-byte atomic writes combined with * 16-byte atomic reads. */ From 9b85c38e374bc0fec5562578dc73559adf111e1b Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 22 Mar 2023 10:46:19 -0700 Subject: [PATCH 078/327] Add rehash, add missing move in insert() --- include/boost/unordered/concurrent_flat_map.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 0be57eef..c21660b8 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -126,7 +126,7 @@ namespace boost { /// bool insert(value_type const& obj) { return table_.insert(obj); } - bool insert(value_type&& obj) { return table_.insert(obj); } + bool insert(value_type&& obj) { return table_.insert(std::move(obj)); } bool insert(init_type const& obj) { return table_.insert(obj); } bool insert(init_type&& obj) { return table_.insert(std::move(obj)); } @@ -143,6 +143,10 @@ namespace boost { { return table_.visit_all(std::move(f)); } + + /// Hash Policy + /// + void rehash(size_type n) { table_.rehash(n); } }; } // namespace unordered } // namespace boost From e9d9f19e76793b34551ea61da8d80aa962951562 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 22 Mar 2023 10:47:01 -0700 Subject: [PATCH 079/327] Flesh out insert_tests --- test/cfoa/insert_tests.cpp | 88 ++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 31 deletions(-) diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index eb63403b..ea2db80e 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -188,37 +188,22 @@ namespace { { template void operator()(std::vector& values, X& x) { - std::mutex m; std::vector threads; - std::condition_variable cv; - - auto ready = false; - auto subslices = split(values, num_threads); - BOOST_ASSERT(subslices.size() == num_threads); - for (std::size_t i = 0; i < num_threads; ++i) { - threads.emplace_back([&x, &m, &ready, &subslices, &cv, i] { - std::unique_lock lk(m); - cv.wait(lk, [&] { return ready; }); - lk.unlock(); + threads.emplace_back([&x, &subslices, i] { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); - auto s = subslices[i]; - - for (auto const& r : s) { - bool b = x.insert(r); - (void)b; + { + auto s = subslices[i]; + for (auto const& r : s) { + bool b = x.insert(r); + (void)b; + } } }); } - - { - std::unique_lock lk(m); - ready = true; - } - cv.notify_all(); - for (auto& t : threads) { t.join(); } @@ -229,9 +214,32 @@ namespace { { template void operator()(std::vector& values, X& x) { - for (auto& r : values) { - bool b = x.insert(std::move(r)); - (void)b; + BOOST_TEST_EQ(raii::copy_constructor, 0); + + std::vector threads; + auto subslices = split(values, num_threads); + + for (std::size_t i = 0; i < num_threads; ++i) { + threads.emplace_back([&x, &subslices, i] { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + { + auto s = subslices[i]; + for (auto& r : s) { + bool b = x.insert(std::move(r)); + (void)b; + } + } + }); + } + for (auto& t : threads) { + t.join(); + } + + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0); } } } rvalue_inserter; @@ -240,14 +248,29 @@ namespace { { template void operator()(std::vector& values, X& x) { - x.insert(values.begin(), values.end()); + std::vector threads; + auto subslices = split(values, num_threads); + + for (std::size_t i = 0; i < num_threads; ++i) { + threads.emplace_back([&x, &subslices, i] { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + { + auto s = subslices[i]; + x.insert(s.begin(), s.end()); + } + }); + } + for (auto& t : threads) { + t.join(); + } } } iterator_range_inserter; template void insert(X*, G gen, F inserter, test::random_generator rg) { - auto values = make_random_values(1024 * 1024, [&] { return gen(rg); }); + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); BOOST_TEST_GT(values.size(), 0u); auto reference_map = @@ -264,7 +287,10 @@ namespace { using value_type = typename X::value_type; BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { - BOOST_TEST(reference_map.contains(kv.first)); + if (BOOST_TEST(reference_map.contains(kv.first)) && + rg == test::sequential) { + BOOST_TEST_EQ(kv.second, reference_map[kv.first]); + } })); } @@ -286,15 +312,15 @@ namespace { } // namespace using test::default_generator; -using test::generate_collisions; using test::limited_range; +using test::sequential; // clang-format off UNORDERED_TEST( insert, ((map)) ((value_type_generator)(init_type_generator)) ((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter)) - ((default_generator)(limited_range))) + ((default_generator)(sequential)(limited_range))) // clang-format on RUN_TESTS() From 3064801a8904ca650b60c5ab656647ec61d80839 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 22 Mar 2023 11:19:13 -0700 Subject: [PATCH 080/327] Attempt to add tsan to GHA --- .github/workflows/ci.yml | 9 +++++++++ test/Jamfile.v2 | 2 ++ 2 files changed, 11 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da3f5b2c..40e6f9a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,9 @@ jobs: - { name: Collect coverage, coverage: yes, compiler: gcc-8, cxxstd: '03,11', os: ubuntu-20.04, install: 'g++-8-multilib', address-model: '32,64', ccache_key: "cov" } + - { name: "cfoa tsan (gcc)", cxxstd: '11,14,17,20,2b', os: ubuntu-22.04, compiler: gcc-12, + targets: 'libs/unordered/test//cfoa_tests', thread-sanitize: yes } + # Linux, clang, libc++ - { compiler: clang-7, cxxstd: '03,11,14,17', os: ubuntu-20.04, stdlib: libc++, install: 'clang-7 libc++-7-dev libc++abi-7-dev' } - { compiler: clang-10, cxxstd: '03,11,14,17,20', os: ubuntu-20.04, stdlib: libc++, install: 'clang-10 libc++-10-dev libc++abi-10-dev' } @@ -65,12 +68,16 @@ jobs: compiler: clang-12, cxxstd: '17,20,2b', os: ubuntu-20.04, stdlib: libc++, install: 'clang-12 libc++-12-dev libc++abi-12-dev', ccache_key: "san2" } - { compiler: clang-13, cxxstd: '03,11,14,17,20,2b', os: ubuntu-22.04, stdlib: libc++, install: 'clang-13 libc++-13-dev libc++abi-13-dev' } - { compiler: clang-14, cxxstd: '03,11,14,17,20,2b', os: ubuntu-22.04, stdlib: libc++, install: 'clang-14 libc++-14-dev libc++abi-14-dev' } + # not using libc++ because of https://github.com/llvm/llvm-project/issues/52771 - { name: "clang-14 w/ sanitizers (03,11,14)", sanitize: yes, compiler: clang-14, cxxstd: '03,11,14', os: ubuntu-22.04, ccache_key: "san1" } - { name: "clang-14 w/ sanitizers (17,20,2b)", sanitize: yes, compiler: clang-14, cxxstd: '17,20,2b', os: ubuntu-22.04, ccache_key: "san2" } + - { name: "cfoa tsan (clang)", cxxstd: '11,14,17,20,2b', os: ubuntu-22.04, compiler: clang-14, + targets: 'libs/unordered/test//cfoa_tests', thread-sanitize: yes } + # OSX, clang - { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-11, } - { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-12, sanitize: yes } @@ -184,6 +191,8 @@ jobs: B2_COMPILER: ${{matrix.compiler}} B2_CXXSTD: ${{matrix.cxxstd}} B2_SANITIZE: ${{matrix.sanitize}} + B2_TSAN: ${{matrx.thread-sanitize}} + B2_TARGETS: ${{matrix.targets}} B2_STDLIB: ${{matrix.stdlib}} # More entries can be added in the same way, see the B2_ARGS assignment in ci/enforce.sh for the possible keys. # B2_DEFINES: ${{matrix.defines}} diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index f94bc6f6..eb971b37 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -164,3 +164,5 @@ rule build_cfoa ( name ) } build_cfoa insert_tests ; + +alias cfoa_tests : cfoa_insert_tests ; From ff356ac0839d4f8080995b03b8b0eae8358bad88 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 22 Mar 2023 11:20:03 -0700 Subject: [PATCH 081/327] Fix typo in GHA ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40e6f9a7..f0cf3805 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -191,7 +191,7 @@ jobs: B2_COMPILER: ${{matrix.compiler}} B2_CXXSTD: ${{matrix.cxxstd}} B2_SANITIZE: ${{matrix.sanitize}} - B2_TSAN: ${{matrx.thread-sanitize}} + B2_TSAN: ${{matrix.thread-sanitize}} B2_TARGETS: ${{matrix.targets}} B2_STDLIB: ${{matrix.stdlib}} # More entries can be added in the same way, see the B2_ARGS assignment in ci/enforce.sh for the possible keys. From 44b918896d8c7c05a1a8e036de9e7ea8298bddc2 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Wed, 22 Mar 2023 19:24:46 +0100 Subject: [PATCH 082/327] tried relaxing atomic operations --- include/boost/unordered/detail/foa/concurrent_table.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index b7d1b453..6b37aebe 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -144,10 +144,10 @@ private: template struct atomic_integral { - operator Integral()const{return n.load(std::memory_order_acquire);} - void operator=(Integral m){n.store(m,std::memory_order_release);} - void operator|=(Integral m){n.fetch_or(m,std::memory_order_acq_rel);} - void operator&=(Integral m){n.fetch_and(m,std::memory_order_acq_rel);} + operator Integral()const{return n.load(std::memory_order_relaxed);} + void operator=(Integral m){n.store(m,std::memory_order_relaxed);} + void operator|=(Integral m){n.fetch_or(m,std::memory_order_relaxed);} + void operator&=(Integral m){n.fetch_and(m,std::memory_order_relaxed);} std::atomic n; }; From 41584e73d9f16d1dc77cb6a8e8bdfed3993f6f78 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Wed, 22 Mar 2023 20:09:53 +0100 Subject: [PATCH 083/327] partially reverted prior commit to nail down problem with Clang x86 --- include/boost/unordered/detail/foa/concurrent_table.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 6b37aebe..9a8be3f6 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -146,8 +146,8 @@ struct atomic_integral { operator Integral()const{return n.load(std::memory_order_relaxed);} void operator=(Integral m){n.store(m,std::memory_order_relaxed);} - void operator|=(Integral m){n.fetch_or(m,std::memory_order_relaxed);} - void operator&=(Integral m){n.fetch_and(m,std::memory_order_relaxed);} + void operator|=(Integral m){n.fetch_or(m,std::memory_order_acq_rel);} + void operator&=(Integral m){n.fetch_and(m,std::memory_order_acq_rel);} std::atomic n; }; From 4a7116b9962aecb7c0996dce5ec1fa84f1a16bec Mon Sep 17 00:00:00 2001 From: joaquintides Date: Wed, 22 Mar 2023 20:16:09 +0100 Subject: [PATCH 084/327] partially restored prior commit to nail down problem with Clang x86 --- include/boost/unordered/detail/foa/concurrent_table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 9a8be3f6..b56d7f1e 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -146,7 +146,7 @@ struct atomic_integral { operator Integral()const{return n.load(std::memory_order_relaxed);} void operator=(Integral m){n.store(m,std::memory_order_relaxed);} - void operator|=(Integral m){n.fetch_or(m,std::memory_order_acq_rel);} + void operator|=(Integral m){n.fetch_or(m,std::memory_order_relaxed);} void operator&=(Integral m){n.fetch_and(m,std::memory_order_acq_rel);} std::atomic n; From f2d4f1968b4972b7c43ebfb3322bb601906e0a3c Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 22 Mar 2023 12:49:05 -0700 Subject: [PATCH 085/327] Explicitly add B2_TARGETS to script invocation --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0cf3805..a3c88ae0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -206,7 +206,7 @@ jobs: - name: Run tests if: '!matrix.coverity' - run: ci/build.sh + run: B2_TARGETS=${{matrix.targets}} ci/build.sh - name: Upload coverage if: matrix.coverage From e1f11e840cddbfd60bc1647084bbd098be474d6a Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 22 Mar 2023 13:55:42 -0700 Subject: [PATCH 086/327] update clang tsan job to use libc++ --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3c88ae0..3aef56ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,7 +76,8 @@ jobs: compiler: clang-14, cxxstd: '17,20,2b', os: ubuntu-22.04, ccache_key: "san2" } - { name: "cfoa tsan (clang)", cxxstd: '11,14,17,20,2b', os: ubuntu-22.04, compiler: clang-14, - targets: 'libs/unordered/test//cfoa_tests', thread-sanitize: yes } + targets: 'libs/unordered/test//cfoa_tests', thread-sanitize: yes, + stdlib: libc++, install: 'clang-14 libc++-14-dev libc++abi-14-dev' } # OSX, clang - { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-11, } From 9e6b5a7e43698ae275e97a3a2f6ef420bd805c00 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 22 Mar 2023 15:07:36 -0700 Subject: [PATCH 087/327] Add tsan OSX job --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3aef56ec..973e9e27 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,6 +82,7 @@ jobs: # OSX, clang - { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-11, } - { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-12, sanitize: yes } + - { compiler: clang, cxxstd: '11,14,17,2a', os: macos-12, thread-sanitize: yes, targets: 'libs/unordered/test//cfoa_tests' } timeout-minutes: 180 runs-on: ${{matrix.os}} From 1070c9b69e1bc9d3492dd9e760411d12583cdb76 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 22 Mar 2023 15:11:55 -0700 Subject: [PATCH 088/327] Attempt to add tsan to Drone CI --- .drone.jsonnet | 21 +++++++++++++++++++++ .drone/drone.sh | 4 +++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index 51340c4c..447bf3f2 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -11,6 +11,7 @@ local triggers = local ubsan = { UBSAN: '1', UBSAN_OPTIONS: 'print_stacktrace=1' }; local asan = { ASAN: '1' }; +local tsan = { TSAN: '1' }; local linux_pipeline(name, image, environment, packages = "", sources = [], arch = "amd64") = { @@ -224,6 +225,13 @@ local windows_pipeline(name, image, environment, arch = "amd64") = "g++-12-multilib", ), + linux_pipeline( + "Linux 22.04 GCC 12 64 TSAN (11,14,17,20,2b)", + "cppalliance/droneubuntu2204:1", + { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '11,14,17,20,2b', ADDRMD: '64', TARGET: 'libs/unordered/test//cfoa_tests' } + tsan, + "g++-12-multilib", + ), + linux_pipeline( "Linux 16.04 Clang 3.5", "cppalliance/droneubuntu1604:1", @@ -343,6 +351,13 @@ local windows_pipeline(name, image, environment, arch = "amd64") = "clang-14", ), + linux_pipeline( + "Linux 22.04 Clang 14 libc++ 64 TSAN", + "cppalliance/droneubuntu2204:1", + { TOOLSET: 'clang', COMPILER: 'clang++-14', ADDRMD: '64', TARGET: 'libs/unordered/test//cfoa_tests', CXXSTD: '11,14,17,20', STDLIB: libc++ } + tsan, + "clang-14 libc++-14-dev libc++abi-14-dev", + ), + linux_pipeline( "Linux 22.04 Clang 15", "cppalliance/droneubuntu2204:1", @@ -362,6 +377,12 @@ local windows_pipeline(name, image, environment, arch = "amd64") = xcode_version = "13.4.1", osx_version = "monterey", arch = "arm64", ), + macos_pipeline( + "MacOS 12.4 Xcode 13.4.1 TSAN", + { TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '11,14,1z', TARGET: 'libs/unordered/test//cfoa_tests' } + tsan, + xcode_version = "13.4.1", osx_version = "monterey", arch = "arm64", + ), + windows_pipeline( "Windows VS2015 msvc-14.0", "cppalliance/dronevs2015", diff --git a/.drone/drone.sh b/.drone/drone.sh index 2f2125df..b892890d 100755 --- a/.drone/drone.sh +++ b/.drone/drone.sh @@ -7,6 +7,8 @@ set -ex export PATH=~/.local/bin:/usr/local/bin:$PATH +: ${TARGET:="libs/$LIBRARY/test"} + DRONE_BUILD_DIR=$(pwd) BOOST_BRANCH=develop @@ -22,4 +24,4 @@ python tools/boostdep/depinst/depinst.py $LIBRARY ./b2 -d0 headers echo "using $TOOLSET : : $COMPILER ;" > ~/user-config.jam -./b2 -j3 libs/$LIBRARY/test toolset=$TOOLSET cxxstd=$CXXSTD variant=debug,release ${ADDRMD:+address-model=$ADDRMD} ${UBSAN:+undefined-sanitizer=norecover debug-symbols=on} ${ASAN:+address-sanitizer=norecover debug-symbols=on} ${LINKFLAGS:+linkflags=$LINKFLAGS} +./b2 -j3 $TARGET toolset=$TOOLSET cxxstd=$CXXSTD variant=debug,release ${ADDRMD:+address-model=$ADDRMD} ${STDLIB:+$STDLIB} ${UBSAN:+undefined-sanitizer=norecover debug-symbols=on} ${ASAN:+address-sanitizer=norecover debug-symbols=on} ${TSAN:+thread-sanitizer=norecover debug-symbols=on} ${LINKFLAGS:+linkflags=$LINKFLAGS} From 99eab2dfb19f4d3b5762fa113ebc374db8f34160 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 22 Mar 2023 15:14:16 -0700 Subject: [PATCH 089/327] Fix missing quotes in jsonnet file --- .drone.jsonnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index 447bf3f2..b153482e 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -354,7 +354,7 @@ local windows_pipeline(name, image, environment, arch = "amd64") = linux_pipeline( "Linux 22.04 Clang 14 libc++ 64 TSAN", "cppalliance/droneubuntu2204:1", - { TOOLSET: 'clang', COMPILER: 'clang++-14', ADDRMD: '64', TARGET: 'libs/unordered/test//cfoa_tests', CXXSTD: '11,14,17,20', STDLIB: libc++ } + tsan, + { TOOLSET: 'clang', COMPILER: 'clang++-14', ADDRMD: '64', TARGET: 'libs/unordered/test//cfoa_tests', CXXSTD: '11,14,17,20', STDLIB: 'libc++' } + tsan, "clang-14 libc++-14-dev libc++abi-14-dev", ), From 297fb2e8c47b872d6679dfd66fbe638d90dbd287 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 23 Mar 2023 09:41:36 +0100 Subject: [PATCH 090/327] abode by TSan on Neon --- include/boost/unordered/detail/foa/core.hpp | 31 +++++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 5d2ad1ee..f70b75dc 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -225,7 +225,7 @@ struct group15 inline int match(std::size_t hash)const { return _mm_movemask_epi8( - _mm_cmpeq_epi8(load_si128(),_mm_set1_epi32(match_word(hash))))&0x7FFF; + _mm_cmpeq_epi8(load_metadata(),_mm_set1_epi32(match_word(hash))))&0x7FFF; } inline bool is_not_overflowed(std::size_t hash)const @@ -250,7 +250,7 @@ struct group15 inline int match_available()const { return _mm_movemask_epi8( - _mm_cmpeq_epi8(load_si128(),_mm_setzero_si128()))&0x7FFF; + _mm_cmpeq_epi8(load_metadata(),_mm_setzero_si128()))&0x7FFF; } inline bool is_occupied(std::size_t pos)const @@ -276,7 +276,7 @@ private: static constexpr unsigned char available_=0, sentinel_=1; - inline __m128i load_si128()const + inline __m128i load_metadata()const { #if defined(BOOST_UNORDERED_THREAD_SANITIZER) /* ThreadSanitizer complains on 1-byte atomic writes combined with @@ -416,8 +416,7 @@ struct group15 inline int match(std::size_t hash)const { return simde_mm_movemask_epi8(vceqq_u8( - vld1q_u8(reinterpret_cast(m)), - vdupq_n_u8(reduced_hash(hash))))&0x7FFF; + load_metadata(),vdupq_n_u8(reduced_hash(hash))))&0x7FFF; } inline bool is_not_overflowed(std::size_t hash)const @@ -442,8 +441,7 @@ struct group15 inline int match_available()const { return simde_mm_movemask_epi8(vceqq_u8( - vld1q_u8(reinterpret_cast(m)), - vdupq_n_u8(0)))&0x7FFF; + load_metadata(),vdupq_n_u8(0)))&0x7FFF; } inline bool is_occupied(std::size_t pos)const @@ -460,8 +458,7 @@ struct group15 inline int match_occupied()const { return simde_mm_movemask_epi8(vcgtq_u8( - vld1q_u8(reinterpret_cast(m)), - vdupq_n_u8(0)))&0x7FFF; + load_metadata(),vdupq_n_u8(0)))&0x7FFF; } private: @@ -471,6 +468,22 @@ private: static constexpr unsigned char available_=0, sentinel_=1; + inline uint8x16_t load_metadata()const + { +#if defined(BOOST_UNORDERED_THREAD_SANITIZER) + /* ThreadSanitizer complains on 1-byte atomic writes combined with + * 16-byte atomic reads. + */ + + alignas(16) uint8_t data[16]={ + m[ 0],m[ 1],m[ 2],m[ 3],m[ 4],m[ 5],m[ 6],m[ 7], + m[ 8],m[ 9],m[10],m[11],m[12],m[13],m[14],m[15]}; + return vld1q_u8(data); +#else + return vld1q_u8(reinterpret_cast(m)); +#endif + } + inline static unsigned char reduced_hash(std::size_t hash) { static constexpr unsigned char table[]={ From eedbc6bcf767663ae4260ada9a805a47c6852557 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 22 Mar 2023 15:32:27 -0700 Subject: [PATCH 091/327] Add missing `stdlib=` to drone.sh --- .drone/drone.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone/drone.sh b/.drone/drone.sh index b892890d..60f06760 100755 --- a/.drone/drone.sh +++ b/.drone/drone.sh @@ -24,4 +24,4 @@ python tools/boostdep/depinst/depinst.py $LIBRARY ./b2 -d0 headers echo "using $TOOLSET : : $COMPILER ;" > ~/user-config.jam -./b2 -j3 $TARGET toolset=$TOOLSET cxxstd=$CXXSTD variant=debug,release ${ADDRMD:+address-model=$ADDRMD} ${STDLIB:+$STDLIB} ${UBSAN:+undefined-sanitizer=norecover debug-symbols=on} ${ASAN:+address-sanitizer=norecover debug-symbols=on} ${TSAN:+thread-sanitizer=norecover debug-symbols=on} ${LINKFLAGS:+linkflags=$LINKFLAGS} +./b2 -j3 $TARGET toolset=$TOOLSET cxxstd=$CXXSTD variant=debug,release ${ADDRMD:+address-model=$ADDRMD} ${STDLIB:+stdlib=$STDLIB} ${UBSAN:+undefined-sanitizer=norecover debug-symbols=on} ${ASAN:+address-sanitizer=norecover debug-symbols=on} ${TSAN:+thread-sanitizer=norecover debug-symbols=on} ${LINKFLAGS:+linkflags=$LINKFLAGS} From d9515fdc2fdba6013939934e54d068c24a93e6bb Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 23 Mar 2023 10:59:29 -0700 Subject: [PATCH 092/327] Add insert(initializer_list) --- include/boost/unordered/concurrent_flat_map.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index c21660b8..34963c84 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -139,6 +139,11 @@ namespace boost { } } + void insert(std::initializer_list ilist) + { + this->insert(ilist.begin(), ilist.end()); + } + template std::size_t visit_all(F f) { return table_.visit_all(std::move(f)); @@ -147,6 +152,7 @@ namespace boost { /// Hash Policy /// void rehash(size_type n) { table_.rehash(n); } + void reserve(size_type n) { table_.reserve(n); } }; } // namespace unordered } // namespace boost From ad86f9211bb86b0d66243a9acba2c6c5e7a09f1f Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 23 Mar 2023 10:59:38 -0700 Subject: [PATCH 093/327] Flesh out tests for insertion --- test/cfoa/insert_tests.cpp | 204 ++++++++++++++++++++++++++----------- 1 file changed, 143 insertions(+), 61 deletions(-) diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index ea2db80e..c12fa695 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -184,57 +184,67 @@ namespace { return subslices; } + template void thread_runner(std::vector& values, F f) + { + std::vector threads; + auto subslices = split(values, num_threads); + + for (std::size_t i = 0; i < num_threads; ++i) { + threads.emplace_back([&f, &subslices, i] { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + auto s = subslices[i]; + f(s); + }); + } + for (auto& t : threads) { + t.join(); + } + } + struct lvalue_inserter_type { template void operator()(std::vector& values, X& x) { - std::vector threads; - auto subslices = split(values, num_threads); - - for (std::size_t i = 0; i < num_threads; ++i) { - threads.emplace_back([&x, &subslices, i] { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - { - auto s = subslices[i]; - for (auto const& r : s) { - bool b = x.insert(r); - (void)b; - } + std::atomic_uint64_t num_inserts{0}; + thread_runner(values, [&x, &num_inserts](boost::span s) { + for (auto const& r : s) { + bool b = x.insert(r); + if (b) { + ++num_inserts; } - }); - } - for (auto& t : threads) { - t.join(); - } + } + }); + BOOST_TEST_EQ(num_inserts, x.size()); } } lvalue_inserter; + struct norehash_lvalue_inserter_type : public lvalue_inserter_type + { + template void operator()(std::vector& values, X& x) + { + x.reserve(values.size()); + lvalue_inserter_type::operator()(values, x); + BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); + BOOST_TEST_EQ(raii::move_constructor, 0); + } + } norehash_lvalue_inserter; + struct rvalue_inserter_type { template void operator()(std::vector& values, X& x) { BOOST_TEST_EQ(raii::copy_constructor, 0); - std::vector threads; - auto subslices = split(values, num_threads); - - for (std::size_t i = 0; i < num_threads; ++i) { - threads.emplace_back([&x, &subslices, i] { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - { - auto s = subslices[i]; - for (auto& r : s) { - bool b = x.insert(std::move(r)); - (void)b; - } + std::atomic_uint64_t num_inserts{0}; + thread_runner(values, [&x, &num_inserts](boost::span s) { + for (auto& r : s) { + bool b = x.insert(std::move(r)); + if (b) { + ++num_inserts; } - }); - } - for (auto& t : threads) { - t.join(); - } + } + }); + BOOST_TEST_EQ(num_inserts, x.size()); if (std::is_same::value) { BOOST_TEST_EQ(raii::copy_constructor, x.size()); @@ -244,26 +254,33 @@ namespace { } } rvalue_inserter; + struct norehash_rvalue_inserter_type : public rvalue_inserter_type + { + template void operator()(std::vector& values, X& x) + { + x.reserve(values.size()); + + BOOST_TEST_EQ(raii::copy_constructor, 0); + BOOST_TEST_EQ(raii::move_constructor, 0); + + rvalue_inserter_type::operator()(values, x); + + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_EQ(raii::move_constructor, x.size()); + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0); + BOOST_TEST_EQ(raii::move_constructor, 2 * x.size()); + } + } + } norehash_rvalue_inserter; + struct iterator_range_inserter_type { template void operator()(std::vector& values, X& x) { - std::vector threads; - auto subslices = split(values, num_threads); - - for (std::size_t i = 0; i < num_threads; ++i) { - threads.emplace_back([&x, &subslices, i] { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - { - auto s = subslices[i]; - x.insert(s.begin(), s.end()); - } - }); - } - for (auto& t : threads) { - t.join(); - } + thread_runner( + values, [&x](boost::span s) { x.insert(s.begin(), s.end()); }); } } iterator_range_inserter; @@ -271,11 +288,8 @@ namespace { void insert(X*, G gen, F inserter, test::random_generator rg) { auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); - BOOST_TEST_GT(values.size(), 0u); - auto reference_map = boost::unordered_flat_map(values.begin(), values.end()); - raii::reset_counts(); { @@ -287,8 +301,8 @@ namespace { using value_type = typename X::value_type; BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { - if (BOOST_TEST(reference_map.contains(kv.first)) && - rg == test::sequential) { + BOOST_TEST(reference_map.contains(kv.first)); + if (rg == test::sequential) { BOOST_TEST_EQ(kv.second, reference_map[kv.first]); } })); @@ -307,6 +321,68 @@ namespace { BOOST_TEST_EQ(raii::move_assignment, 0); } + template void insert_initializer_list(X*) + { + using value_type = typename X::value_type; + + std::initializer_list values{ + value_type{raii{0}, raii{0}}, + value_type{raii{1}, raii{1}}, + value_type{raii{2}, raii{2}}, + value_type{raii{3}, raii{3}}, + value_type{raii{4}, raii{4}}, + value_type{raii{5}, raii{5}}, + value_type{raii{6}, raii{6}}, + value_type{raii{6}, raii{6}}, + value_type{raii{7}, raii{7}}, + value_type{raii{8}, raii{8}}, + value_type{raii{9}, raii{9}}, + value_type{raii{10}, raii{10}}, + value_type{raii{9}, raii{9}}, + value_type{raii{8}, raii{8}}, + value_type{raii{7}, raii{7}}, + value_type{raii{6}, raii{6}}, + value_type{raii{5}, raii{5}}, + value_type{raii{4}, raii{4}}, + value_type{raii{3}, raii{3}}, + value_type{raii{2}, raii{2}}, + value_type{raii{1}, raii{1}}, + value_type{raii{0}, raii{0}}, + }; + + std::vector dummy; + + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + X x; + + thread_runner( + dummy, [&x, &values](boost::span) { x.insert(values); }); + + BOOST_TEST_EQ(x.size(), reference_map.size()); + + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map[kv.first]); + })); + } + + 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 @@ -317,10 +393,16 @@ using test::sequential; // clang-format off UNORDERED_TEST( - insert, ((map)) - ((value_type_generator)(init_type_generator)) - ((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter)) - ((default_generator)(sequential)(limited_range))) + insert_initializer_list, + ((map))) + +UNORDERED_TEST( + insert, + ((map)) + ((value_type_generator)(init_type_generator)) + ((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter) + (norehash_lvalue_inserter)(norehash_rvalue_inserter)) + ((default_generator)(sequential)(limited_range))) // clang-format on RUN_TESTS() From 99d5b9b1bb4326a12958b63d8a2eb03830f9aee9 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 24 Mar 2023 08:57:51 -0700 Subject: [PATCH 094/327] Avoid unsupported typedef on earlier compilers --- test/cfoa/insert_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index c12fa695..631c8877 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -205,7 +205,7 @@ namespace { { template void operator()(std::vector& values, X& x) { - std::atomic_uint64_t num_inserts{0}; + std::atomic num_inserts{0}; thread_runner(values, [&x, &num_inserts](boost::span s) { for (auto const& r : s) { bool b = x.insert(r); @@ -235,7 +235,7 @@ namespace { { BOOST_TEST_EQ(raii::copy_constructor, 0); - std::atomic_uint64_t num_inserts{0}; + std::atomic num_inserts{0}; thread_runner(values, [&x, &num_inserts](boost::span s) { for (auto& r : s) { bool b = x.insert(std::move(r)); From d085c4054107c601bbebe90206b07f3e62e185b5 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 24 Mar 2023 18:37:53 +0100 Subject: [PATCH 095/327] relaxed all ops of atomic_integral --- include/boost/unordered/detail/foa/concurrent_table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index b56d7f1e..6b37aebe 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -147,7 +147,7 @@ struct atomic_integral operator Integral()const{return n.load(std::memory_order_relaxed);} void operator=(Integral m){n.store(m,std::memory_order_relaxed);} void operator|=(Integral m){n.fetch_or(m,std::memory_order_relaxed);} - void operator&=(Integral m){n.fetch_and(m,std::memory_order_acq_rel);} + void operator&=(Integral m){n.fetch_and(m,std::memory_order_relaxed);} std::atomic n; }; From 49ac1035bb84f30c52090ded4112cee15ee76b94 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 24 Mar 2023 19:49:59 +0100 Subject: [PATCH 096/327] (partially) implemented shared/exclusive visitation --- .../unordered/detail/foa/concurrent_table.hpp | 70 +++++++++++++------ 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 6b37aebe..d61b519d 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -350,18 +350,23 @@ public: { auto lck=shared_access(); auto hash=this->hash_for(x); - return unprotected_visit(x,this->position_for(hash),hash,f); + return unprotected_visit( + group_exclusive{},x,this->position_for(hash),hash,f); } template BOOST_FORCEINLINE std::size_t visit(const Key& x,F f)const { - return const_cast(this)-> - visit(x,[&](const value_type& v){f(v);}); + auto lck=shared_access(); + auto hash=this->hash_for(x); + return unprotected_visit( + group_shared{},x,this->position_for(hash),hash, + [&](const value_type& v){f(v);}); } template std::size_t visit_all(F f) { + // TODO: replace with group-protected algorithm auto lck=shared_access(); std::size_t res=0; this->for_all_elements([&](element_type* p){ @@ -373,6 +378,7 @@ public: template std::size_t visit_all(F f)const { + // TODO: replace with group-protected algorithm return const_cast(this)-> visit_all([&](const value_type& v){f(v);}); } @@ -383,16 +389,18 @@ public: { auto lck=shared_access(); for_all_elements_exec( - std::forward(policy), + group_exclusive{},std::forward(policy), [&](element_type* p){f(type_policy::value_from(*p));}); } template void visit_all(ExecutionPolicy&& policy,F f)const { - return const_cast(this)-> - visit_all( - std::forward(policy),[&](const value_type& v){f(v);}); + auto lck=shared_access(); + for_all_elements_exec( + group_shared{},std::forward(policy), + [&](element_type* p) + {f(const_cast(type_policy::value_from(*p)));}); } #endif @@ -479,7 +487,7 @@ public: auto lck=shared_access(); auto hash=this->hash_for(x); return (std::size_t)unprotected_internal_visit( - x,this->position_for(hash),hash, + group_exclusive{},x,this->position_for(hash),hash, [&,this](group_type* pg,unsigned int n,element_type* p) { if(f(const_cast(type_policy::value_from(*p)))){ @@ -491,6 +499,7 @@ public: template std::size_t erase_if(F&& f) { + // TODO: replace with group-protected algorithm auto lck=shared_access(); return super::erase_if_impl(std::forward(f)); } @@ -502,7 +511,7 @@ public: { auto lck=shared_access(); for_all_elements_exec( - std::forward(policy), + group_exclusive{},std::forward(policy), [&,this](group_type* pg,unsigned int n,element_type* p) { if(f(const_cast(type_policy::value_from(*p)))){ @@ -529,6 +538,7 @@ public: template void merge(concurrent_table& x) { + // TODO: replace with group-protected algorithm // TODO: consider grabbing shared access on *this at this level auto lck=x.shared_access(); // TODO: can deadlock if x1.merge(x2) while x2.merge(x1) x.for_all_elements([&,this](group_type* pg,unsigned int n,element_type* p){ @@ -683,6 +693,21 @@ private: } #endif + /* Tag-dispatched shared/exclusive group access */ + + using group_shared=std::false_type; + using group_exclusive=std::true_type; + + group_shared_lock_guard access(group_shared,std::size_t pos)const + { + return shared_access(pos); + } + + group_exclusive_lock_guard access(group_exclusive,std::size_t pos)const + { + return exclusive_access(pos); + } + std::size_t unprotected_size()const { std::size_t m=this->ml; @@ -707,12 +732,13 @@ private: bool rollback_=false; }; - template + template BOOST_FORCEINLINE std::size_t unprotected_visit( + GroupAccessMode access_mode, const Key& x,std::size_t pos0,std::size_t hash,F&& f)const { return unprotected_internal_visit( - x,pos0,hash, + access_mode,x,pos0,hash, [&](group_type*,unsigned int,element_type* p) {f(type_policy::value_from(*p));}); } @@ -723,8 +749,9 @@ private: #pragma warning(disable:4800) #endif - template + template BOOST_FORCEINLINE std::size_t unprotected_internal_visit( + GroupAccessMode access_mode, const Key& x,std::size_t pos0,std::size_t hash,F&& f)const { prober pb(pos0); @@ -735,7 +762,7 @@ private: if(mask){ auto p=this->arrays.elements+pos*N; this->prefetch_elements(p); - auto lck=shared_access(pos); + auto lck=access(access_mode,pos); do{ auto n=unchecked_countr_zero(mask); if(BOOST_LIKELY( @@ -863,7 +890,8 @@ private: for(;;){ startover: boost::uint32_t counter=insert_counter(pos0); - if(unprotected_visit(k,pos0,hash,std::forward(f)))return 0; + if(unprotected_visit( + group_exclusive{},k,pos0,hash,std::forward(f)))return 0; reserve_size rsize(*this); if(BOOST_LIKELY(rsize.succeeded())){ @@ -899,17 +927,19 @@ private: } #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) - template - auto for_all_elements_exec(ExecutionPolicy&& policy,F f) + template + auto for_all_elements_exec( + GroupAccessMode access_mode,ExecutionPolicy&& policy,F f) ->decltype(f(nullptr),void()) { for_all_elements_exec( - std::forward(policy), + access_mode,std::forward(policy), [&](group_type*,unsigned int,element_type* p){f(p);}); } - template - auto for_all_elements_exec(ExecutionPolicy&& policy,F f) + template + auto for_all_elements_exec( + GroupAccessMode access_mode,ExecutionPolicy&& policy,F f) ->decltype(f(nullptr,0,nullptr),void()) { auto lck=shared_access(); @@ -919,7 +949,7 @@ private: [&,this](group_type& g){ std::size_t pos=&g-first; auto p=this->arrays.elements+pos*N; - auto lck=exclusive_access(pos); + auto lck=access(access_mode,pos); auto mask=this->match_really_occupied(&g,last); while(mask){ auto n=unchecked_countr_zero(mask); From 8b5539756bffb9a9f059ac178da8c03bdd1fe8e8 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 24 Mar 2023 14:35:01 -0700 Subject: [PATCH 097/327] Partially implement insert_or_assign() --- .../boost/unordered/concurrent_flat_map.hpp | 14 +++ test/cfoa/insert_tests.cpp | 109 ++++++++++++++++-- 2 files changed, 111 insertions(+), 12 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 34963c84..6f5c9ff3 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -149,6 +149,20 @@ namespace boost { return table_.visit_all(std::move(f)); } + template bool insert_or_assign(key_type const& k, M&& obj) + { + return table_.try_emplace_or_visit( + k, [&](value_type& m) { m.second = std::forward(obj); }, + std::forward(obj)); + } + + template bool insert_or_assign(key_type&& k, M&& obj) + { + return table_.try_emplace_or_visit( + std::move(k), [&](value_type& m) { m.second = std::forward(obj); }, + std::forward(obj)); + } + /// Hash Policy /// void rehash(size_type n) { table_.rehash(n); } diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index 631c8877..96ac926d 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -32,7 +32,7 @@ struct raii int x_ = -1; raii() { ++default_constructor; } - explicit raii(int const x) : x_{x} { ++default_constructor; } + raii(int const x) : x_{x} { ++default_constructor; } raii(raii const& rhs) : x_{rhs.x_} { ++copy_constructor; } raii(raii&& rhs) noexcept : x_{rhs.x_} { @@ -147,24 +147,22 @@ namespace { template std::vector > split( - std::vector& vec, std::size_t const nt /* num threads*/) + boost::span s, std::size_t const nt /* num threads*/) { std::vector > subslices; subslices.reserve(nt); - boost::span s(vec); - - auto a = vec.size() / nt; + auto a = s.size() / nt; auto b = a; - if (vec.size() % nt != 0) { + if (s.size() % nt != 0) { ++b; } auto num_a = nt; auto num_b = std::size_t{0}; - if (nt * b > vec.size()) { - num_a = nt * b - vec.size(); + if (nt * b > s.size()) { + num_a = nt * b - s.size(); num_b = nt - num_a; } @@ -187,7 +185,7 @@ namespace { template void thread_runner(std::vector& values, F f) { std::vector threads; - auto subslices = split(values, num_threads); + auto subslices = split(values, num_threads); for (std::size_t i = 0; i < num_threads; ++i) { threads.emplace_back([&f, &subslices, i] { @@ -215,6 +213,8 @@ namespace { } }); BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(raii::copy_assignment, 0); + BOOST_TEST_EQ(raii::move_assignment, 0); } } lvalue_inserter; @@ -251,6 +251,9 @@ namespace { } else { BOOST_TEST_EQ(raii::copy_constructor, 0); } + + BOOST_TEST_EQ(raii::copy_assignment, 0); + BOOST_TEST_EQ(raii::move_assignment, 0); } } rvalue_inserter; @@ -281,9 +284,79 @@ namespace { { thread_runner( values, [&x](boost::span s) { x.insert(s.begin(), s.end()); }); + + BOOST_TEST_EQ(raii::copy_assignment, 0); + BOOST_TEST_EQ(raii::move_assignment, 0); } } iterator_range_inserter; + struct lvalue_insert_or_assign_copy_assign_type + { + template void operator()(std::vector& values, X& x) + { + thread_runner(values, [&x](boost::span s) { + for (auto& r : s) { + x.insert_or_assign(r.first, r.second); + } + }); + + BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); + // don't check move construction count here because of rehashing + BOOST_TEST_GT(raii::move_constructor, 0); + BOOST_TEST_EQ(raii::copy_assignment, values.size() - x.size()); + BOOST_TEST_EQ(raii::move_assignment, 0); + } + } lvalue_insert_or_assign_copy_assign; + + struct lvalue_insert_or_assign_move_assign_type + { + template void operator()(std::vector& values, X& x) + { + thread_runner(values, [&x](boost::span s) { + for (auto& r : s) { + x.insert_or_assign(r.first, std::move(r.second)); + } + }); + + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_GT(raii::move_constructor, x.size()); // rehashing + BOOST_TEST_EQ(raii::copy_assignment, 0); + BOOST_TEST_EQ(raii::move_assignment, values.size() - x.size()); + } + } lvalue_insert_or_assign_move_assign; + + struct rvalue_insert_or_assign_copy_assign_type + { + template void operator()(std::vector& values, X& x) + { + thread_runner(values, [&x](boost::span s) { + for (auto& r : s) { + x.insert_or_assign(std::move(r.first), r.second); + } + }); + + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_GT(raii::move_constructor, x.size()); // rehashing + BOOST_TEST_EQ(raii::copy_assignment, values.size() - x.size()); + BOOST_TEST_EQ(raii::move_assignment, 0); + } + } rvalue_insert_or_assign_copy_assign; + + struct rvalue_insert_or_assign_move_assign_type + { + template void operator()(std::vector& values, X& x) + { + thread_runner(values, [&x](boost::span s) { + for (auto& r : s) { + x.insert_or_assign(std::move(r.first), std::move(r.second)); + } + }); + + BOOST_TEST_EQ(raii::copy_assignment, 0); + BOOST_TEST_EQ(raii::move_assignment, values.size() - x.size()); + } + } rvalue_insert_or_assign_move_assign; + template void insert(X*, G gen, F inserter, test::random_generator rg) { @@ -316,9 +389,6 @@ namespace { 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); } template void insert_initializer_list(X*) @@ -403,6 +473,21 @@ UNORDERED_TEST( ((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter) (norehash_lvalue_inserter)(norehash_rvalue_inserter)) ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + insert, + ((map)) + ((value_type_generator)) + ((lvalue_insert_or_assign_copy_assign)(lvalue_insert_or_assign_move_assign)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + insert, + ((map)) + ((init_type_generator)) + ((lvalue_insert_or_assign_copy_assign)(lvalue_insert_or_assign_move_assign) + (rvalue_insert_or_assign_copy_assign)(rvalue_insert_or_assign_move_assign)) + ((default_generator)(sequential)(limited_range))) // clang-format on RUN_TESTS() From 7415721f7d32be60bb874d10cb45d2e202471e71 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 25 Mar 2023 12:28:44 +0100 Subject: [PATCH 098/327] completed shared/exclusive visitation --- .../unordered/detail/foa/concurrent_table.hpp | 175 ++++++++++++------ 1 file changed, 114 insertions(+), 61 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index d61b519d..d2fba57b 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -346,16 +346,16 @@ public: } template - BOOST_FORCEINLINE std::size_t visit(const Key& x,F f) + BOOST_FORCEINLINE std::size_t visit(const Key& x,F&& f) { auto lck=shared_access(); auto hash=this->hash_for(x); return unprotected_visit( - group_exclusive{},x,this->position_for(hash),hash,f); + group_exclusive{},x,this->position_for(hash),hash,std::forward(f)); } template - BOOST_FORCEINLINE std::size_t visit(const Key& x,F f)const + BOOST_FORCEINLINE std::size_t visit(const Key& x,F&& f)const { auto lck=shared_access(); auto hash=this->hash_for(x); @@ -364,40 +364,43 @@ public: [&](const value_type& v){f(v);}); } - template std::size_t visit_all(F f) + template std::size_t visit_all(F&& f) { - // TODO: replace with group-protected algorithm auto lck=shared_access(); std::size_t res=0; - this->for_all_elements([&](element_type* p){ + for_all_elements(group_exclusive{},[&](element_type* p){ f(type_policy::value_from(*p)); ++res; }); return res; } - template std::size_t visit_all(F f)const + template std::size_t visit_all(F&& f)const { - // TODO: replace with group-protected algorithm - return const_cast(this)-> - visit_all([&](const value_type& v){f(v);}); + auto lck=shared_access(); + std::size_t res=0; + for_all_elements(group_shared{},[&](element_type* p){ + f(const_cast(type_policy::value_from(*p))); + ++res; + }); + return res; } #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) template - void visit_all(ExecutionPolicy&& policy,F f) + void visit_all(ExecutionPolicy&& policy,F&& f) { auto lck=shared_access(); - for_all_elements_exec( + for_all_elements( group_exclusive{},std::forward(policy), [&](element_type* p){f(type_policy::value_from(*p));}); } template - void visit_all(ExecutionPolicy&& policy,F f)const + void visit_all(ExecutionPolicy&& policy,F&& f)const { auto lck=shared_access(); - for_all_elements_exec( + for_all_elements( group_shared{},std::forward(policy), [&](element_type* p) {f(const_cast(type_policy::value_from(*p)));}); @@ -444,35 +447,47 @@ public: } template - BOOST_FORCEINLINE bool try_emplace_or_visit(Key&& x,F f,Args&&... args) + BOOST_FORCEINLINE bool try_emplace_or_visit(Key&& x,F&& f,Args&&... args) { return emplace_or_visit_impl( - f,try_emplace_args_t{},std::forward(x),std::forward(args)...); + group_exclusive{},std::forward(f), + try_emplace_args_t{},std::forward(x),std::forward(args)...); } template - BOOST_FORCEINLINE bool emplace_or_visit(F f,Args&&... args) + BOOST_FORCEINLINE bool emplace_or_visit(F&& f,Args&&... args) { - return construct_and_emplace_or_visit(f,std::forward(args)...); + return construct_and_emplace_or_visit( + group_exclusive{},std::forward(f),std::forward(args)...); } template - BOOST_FORCEINLINE bool insert_or_visit(const init_type& x,F f) - {return emplace_or_visit_impl(f,x);} + BOOST_FORCEINLINE bool insert_or_visit(const init_type& x,F&& f) + { + return emplace_or_visit_impl(group_exclusive{},std::forward(f),x); + } template - BOOST_FORCEINLINE bool insert_or_visit(init_type&& x,F f) - {return emplace_or_visit_impl(f,std::move(x));} + BOOST_FORCEINLINE bool insert_or_visit(init_type&& x,F&& f) + { + return emplace_or_visit_impl( + group_exclusive{},std::forward(f),std::move(x)); + } /* typename=void tilts call ambiguities in favor of init_type */ template - BOOST_FORCEINLINE bool insert_or_visit(const value_type& x,F f) - {return emplace_or_visit_impl(f,x);} + BOOST_FORCEINLINE bool insert_or_visit(const value_type& x,F&& f) + { + return emplace_or_visit_impl(group_exclusive{},std::forward(f),x); + } template - BOOST_FORCEINLINE bool insert_or_visit(value_type&& x,F f) - {return emplace_or_visit_impl(f,std::move(x));} + BOOST_FORCEINLINE bool insert_or_visit(value_type&& x,F&& f) + { + return emplace_or_visit_impl( + group_exclusive{},std::forward(f),std::move(x)); + } template BOOST_FORCEINLINE std::size_t erase(Key&& x) @@ -481,7 +496,7 @@ public: } template - BOOST_FORCEINLINE auto erase_if(Key&& x,F f)->typename std::enable_if< + BOOST_FORCEINLINE auto erase_if(Key&& x,F&& f)->typename std::enable_if< !is_execution_policy::value,std::size_t>::type { auto lck=shared_access(); @@ -499,21 +514,27 @@ public: template std::size_t erase_if(F&& f) { - // TODO: replace with group-protected algorithm auto lck=shared_access(); - return super::erase_if_impl(std::forward(f)); + std::size_t s=size(); + for_all_elements( + group_exclusive{}, + [&,this](group_type* pg,unsigned int n,element_type* p){ + if(f(const_cast(type_policy::value_from(*p)))){ + super::erase(pg,n,p); + } + }); + return std::size_t(s-size()); } #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) template - auto erase_if(ExecutionPolicy&& policy,F f)->typename std::enable_if< + auto erase_if(ExecutionPolicy&& policy,F&& f)->typename std::enable_if< is_execution_policy::value,void>::type { auto lck=shared_access(); - for_all_elements_exec( + for_all_elements( group_exclusive{},std::forward(policy), - [&,this](group_type* pg,unsigned int n,element_type* p) - { + [&,this](group_type* pg,unsigned int n,element_type* p){ if(f(const_cast(type_policy::value_from(*p)))){ super::erase(pg,n,p); } @@ -538,13 +559,15 @@ public: template void merge(concurrent_table& x) { - // TODO: replace with group-protected algorithm // TODO: consider grabbing shared access on *this at this level - auto lck=x.shared_access(); // TODO: can deadlock if x1.merge(x2) while x2.merge(x1) - x.for_all_elements([&,this](group_type* pg,unsigned int n,element_type* p){ - erase_on_exit e{x,pg,n,p}; - if(!emplace_impl(type_policy::move(*p)))e.rollback(); - }); + // TODO: can deadlock if x1.merge(x2) while x2.merge(x1) + auto lck=x.shared_access(); + x.for_all_elements( + group_exclusive{}, + [&,this](group_type* pg,unsigned int n,element_type* p){ + erase_on_exit e{x,pg,n,p}; + if(!emplace_impl(type_policy::move(*p)))e.rollback(); + }); } template @@ -609,9 +632,9 @@ public: } template - friend std::size_t erase_if(concurrent_table& x,Predicate pr) + friend std::size_t erase_if(concurrent_table& x,Predicate&& pr) { - return x.erase_if(pr); + return x.erase_if(std::forward(pr)); } private: @@ -789,49 +812,52 @@ private: BOOST_FORCEINLINE bool construct_and_emplace(Args&&... args) { return construct_and_emplace_or_visit( - [](value_type&){},std::forward(args)...); + group_shared{},[](const value_type&){},std::forward(args)...); } - template - BOOST_FORCEINLINE bool construct_and_emplace_or_visit(F&& f,Args&&... args) + template + BOOST_FORCEINLINE bool construct_and_emplace_or_visit( + GroupAccessMode access_mode,F&& f,Args&&... args) { auto lck=shared_access(); auto x=alloc_make_insert_type( this->al(),std::forward(args)...); int res=unprotected_norehash_emplace_or_visit( - std::forward(f),type_policy::move(x.value())); + access_mode,std::forward(f),type_policy::move(x.value())); if(BOOST_LIKELY(res>=0))return res!=0; lck.unlock(); rehash_if_full(); return noinline_emplace_or_visit( - std::forward(f),type_policy::move(x.value())); + access_mode,std::forward(f),type_policy::move(x.value())); } template BOOST_FORCEINLINE bool emplace_impl(Args&&... args) { return emplace_or_visit_impl( - [](value_type&){},std::forward(args)...); + group_shared{},[](const value_type&){},std::forward(args)...); } - template - BOOST_NOINLINE bool noinline_emplace_or_visit(F&& f,Args&&... args) + template + BOOST_NOINLINE bool noinline_emplace_or_visit( + GroupAccessMode access_mode,F&& f,Args&&... args) { return emplace_or_visit_impl( - std::forward(f),std::forward(args)...); + access_mode,std::forward(f),std::forward(args)...); } - template - BOOST_FORCEINLINE bool emplace_or_visit_impl(F&& f,Args&&... args) + template + BOOST_FORCEINLINE bool emplace_or_visit_impl( + GroupAccessMode access_mode,F&& f,Args&&... args) { for(;;){ { auto lck=shared_access(); int res=unprotected_norehash_emplace_or_visit( - std::forward(f),std::forward(args)...); + access_mode,std::forward(f),std::forward(args)...); if(BOOST_LIKELY(res>=0))return res!=0; } rehash_if_full(); @@ -879,9 +905,10 @@ private: bool commit_=false; }; - template + template BOOST_FORCEINLINE int - unprotected_norehash_emplace_or_visit(F&& f,Args&&... args) + unprotected_norehash_emplace_or_visit( + GroupAccessMode access_mode,F&& f,Args&&... args) { const auto &k=this->key_from(std::forward(args)...); auto hash=this->hash_for(k); @@ -891,14 +918,14 @@ private: startover: boost::uint32_t counter=insert_counter(pos0); if(unprotected_visit( - group_exclusive{},k,pos0,hash,std::forward(f)))return 0; + access_mode,k,pos0,hash,std::forward(f)))return 0; reserve_size rsize(*this); if(BOOST_LIKELY(rsize.succeeded())){ for(prober pb(pos0);;pb.next(this->arrays.groups_size_mask)){ auto pos=pb.get(); auto pg=this->arrays.groups+pos; - auto lck=exclusive_access(pos); + auto lck=access(group_exclusive{},pos); auto mask=pg->match_available(); if(BOOST_LIKELY(mask!=0)){ auto n=unchecked_countr_zero(mask); @@ -926,23 +953,49 @@ private: if(this->size_==this->ml)this->unchecked_rehash_for_growth(); } + template + auto for_all_elements(GroupAccessMode access_mode,F f) + ->decltype(f(nullptr),void()) + { + for_all_elements( + access_mode,[&](group_type*,unsigned int,element_type* p){f(p);}); + } + + template + auto for_all_elements(GroupAccessMode access_mode,F f) + ->decltype(f(nullptr,0,nullptr),void()) + { + auto p=this->arrays.elements; + if(!p)return; + for(auto pg=this->arrays.groups,last=pg+this->arrays.groups_size_mask+1; + pg!=last;++pg,p+=N){ + auto lck=access(access_mode,pg-this->arrays.groups); + auto mask=this->match_really_occupied(pg,last); + while(mask){ + auto n=unchecked_countr_zero(mask); + f(pg,n,p+n); + mask&=mask-1; + } + } + } + #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) template - auto for_all_elements_exec( + auto for_all_elements( GroupAccessMode access_mode,ExecutionPolicy&& policy,F f) ->decltype(f(nullptr),void()) { - for_all_elements_exec( + for_all_elements( access_mode,std::forward(policy), [&](group_type*,unsigned int,element_type* p){f(p);}); } template - auto for_all_elements_exec( + auto for_all_elements( GroupAccessMode access_mode,ExecutionPolicy&& policy,F f) ->decltype(f(nullptr,0,nullptr),void()) { - auto lck=shared_access(); + if(!this->arrays.elements)return; auto first=this->arrays.groups, last=first+this->arrays.groups_size_mask+1; std::for_each(std::forward(policy),first,last, From 240f5f94b1bc27efd73722f2d80bc88a4722a7b5 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 25 Mar 2023 12:31:49 +0100 Subject: [PATCH 099/327] moved erase_if_impl from foa::table_core into foa::table --- include/boost/unordered/detail/foa/core.hpp | 10 ---------- include/boost/unordered/detail/foa/table.hpp | 11 +++++++++-- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index f70b75dc..79d8313d 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1616,16 +1616,6 @@ public: return it; } - template - std::size_t erase_if_impl(Predicate&& pr) - { - std::size_t s=size(); - for_all_elements([&,this](group_type* pg,unsigned int n,element_type* p){ - if(pr(type_policy::value_from(*p))) erase(pg,n,p); - }); - return std::size_t(s-size()); - } - template void for_all_elements(F f)const { diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 1cc23154..4ac77d26 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -420,9 +420,16 @@ public: using super::reserve; template - friend std::size_t erase_if(table& x,Predicate pr) + friend std::size_t erase_if(table& x,Predicate&& pr) { - return x.erase_if_impl(pr); + std::size_t s=size(); + this->for_all_elements( + [&,this](group_type* pg,unsigned int n,element_type* p){ + if(pr(const_cast(type_policy::value_from(*p))){ + this->erase(pg,n,p); + } + }); + return std::size_t(s-size()); } private: From 88c70b885d1e5582e12554d9534a371a3c34123e Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 25 Mar 2023 12:37:10 +0100 Subject: [PATCH 100/327] fixed previous commit --- include/boost/unordered/detail/foa/table.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 4ac77d26..fc5c90d0 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -423,10 +423,10 @@ public: friend std::size_t erase_if(table& x,Predicate&& pr) { std::size_t s=size(); - this->for_all_elements( - [&,this](group_type* pg,unsigned int n,element_type* p){ + x.for_all_elements( + [&](group_type* pg,unsigned int n,element_type* p){ if(pr(const_cast(type_policy::value_from(*p))){ - this->erase(pg,n,p); + x.erase(pg,n,p); } }); return std::size_t(s-size()); From a85df27d4c638085da9c47e9382c57001b3886b0 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 25 Mar 2023 12:47:19 +0100 Subject: [PATCH 101/327] fixed previous commit --- include/boost/unordered/detail/foa/table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index fc5c90d0..8840c9ce 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -425,7 +425,7 @@ public: std::size_t s=size(); x.for_all_elements( [&](group_type* pg,unsigned int n,element_type* p){ - if(pr(const_cast(type_policy::value_from(*p))){ + if(pr(const_cast(type_policy::value_from(*p)))){ x.erase(pg,n,p); } }); From ae9f35073bb278096e4ccb5a1b53ad69f1e52755 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 25 Mar 2023 17:17:00 +0100 Subject: [PATCH 102/327] avoided sign conversion warning --- include/boost/unordered/detail/foa/concurrent_table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index d2fba57b..f85827c5 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -969,7 +969,7 @@ private: if(!p)return; for(auto pg=this->arrays.groups,last=pg+this->arrays.groups_size_mask+1; pg!=last;++pg,p+=N){ - auto lck=access(access_mode,pg-this->arrays.groups); + auto lck=access(access_mode,(std::size_t)(pg-this->arrays.groups)); auto mask=this->match_really_occupied(pg,last); while(mask){ auto n=unchecked_countr_zero(mask); From 37ff9de0dbe9223e2b8a44d1178a9a9a9ea157cf Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 25 Mar 2023 17:35:37 +0100 Subject: [PATCH 103/327] fixed remaining errors in erase_if(foa::table&,...) --- include/boost/unordered/detail/foa/table.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 8840c9ce..c9a1c93d 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -422,14 +422,14 @@ public: template friend std::size_t erase_if(table& x,Predicate&& pr) { - std::size_t s=size(); + std::size_t s=x.size(); x.for_all_elements( [&](group_type* pg,unsigned int n,element_type* p){ if(pr(const_cast(type_policy::value_from(*p)))){ - x.erase(pg,n,p); + x.super::erase(pg,n,p); } }); - return std::size_t(s-size()); + return std::size_t(s-x.size()); } private: From 8dbe380d6c42689610c021e18c333ae26fd65ea7 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 25 Mar 2023 17:37:02 +0100 Subject: [PATCH 104/327] changed predicate arg to an lvalue ref to avoid ADL ambiguities --- include/boost/unordered/detail/foa/table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index c9a1c93d..3145a14d 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -420,7 +420,7 @@ public: using super::reserve; template - friend std::size_t erase_if(table& x,Predicate&& pr) + friend std::size_t erase_if(table& x,Predicate& pr) { std::size_t s=x.size(); x.for_all_elements( From 40c5c6907a76d5bbd60cbfa495f8dc4ee2140828 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 25 Mar 2023 18:50:25 +0100 Subject: [PATCH 105/327] fixed const-casting in erase_if(foa::table&,...) --- include/boost/unordered/detail/foa/table.hpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 3145a14d..5713ccda 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -422,10 +422,16 @@ public: template friend std::size_t erase_if(table& x,Predicate& pr) { + using value_reference=typename std::conditional< + std::is_same::value, + const_reference, + reference + >::type; + std::size_t s=x.size(); x.for_all_elements( [&](group_type* pg,unsigned int n,element_type* p){ - if(pr(const_cast(type_policy::value_from(*p)))){ + if(pr(const_cast(type_policy::value_from(*p)))){ x.super::erase(pg,n,p); } }); From 5f4172c13f25bdc85596ddd4f6cb9d2107a2c34d Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 27 Mar 2023 10:17:25 +0200 Subject: [PATCH 106/327] refactored visitation an const-protection of args to visitation functions --- .../unordered/detail/foa/concurrent_table.hpp | 122 +++++++++++------- 1 file changed, 74 insertions(+), 48 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index f85827c5..abfca0bd 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -348,62 +348,40 @@ public: template BOOST_FORCEINLINE std::size_t visit(const Key& x,F&& f) { - auto lck=shared_access(); - auto hash=this->hash_for(x); - return unprotected_visit( - group_exclusive{},x,this->position_for(hash),hash,std::forward(f)); + return visit_impl(group_exclusive{},x,std::forward(f)); } template BOOST_FORCEINLINE std::size_t visit(const Key& x,F&& f)const { - auto lck=shared_access(); - auto hash=this->hash_for(x); - return unprotected_visit( - group_shared{},x,this->position_for(hash),hash, - [&](const value_type& v){f(v);}); + return visit_impl(group_shared{},x,std::forward(f)); } template std::size_t visit_all(F&& f) { - auto lck=shared_access(); - std::size_t res=0; - for_all_elements(group_exclusive{},[&](element_type* p){ - f(type_policy::value_from(*p)); - ++res; - }); - return res; + return visit_all_impl(group_exclusive{},std::forward(f)); } template std::size_t visit_all(F&& f)const { - auto lck=shared_access(); - std::size_t res=0; - for_all_elements(group_shared{},[&](element_type* p){ - f(const_cast(type_policy::value_from(*p))); - ++res; - }); - return res; + return visit_all_impl(group_shared{},std::forward(f)); } #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) template void visit_all(ExecutionPolicy&& policy,F&& f) { - auto lck=shared_access(); - for_all_elements( - group_exclusive{},std::forward(policy), - [&](element_type* p){f(type_policy::value_from(*p));}); + visit_all_impl( + group_exclusive{}, + std::forward(policy),std::forward(f)); } template void visit_all(ExecutionPolicy&& policy,F&& f)const { - auto lck=shared_access(); - for_all_elements( - group_shared{},std::forward(policy), - [&](element_type* p) - {f(const_cast(type_policy::value_from(*p)));}); + visit_all_impl( + group_shared{}, + std::forward(policy),std::forward(f)); } #endif @@ -505,7 +483,7 @@ public: group_exclusive{},x,this->position_for(hash),hash, [&,this](group_type* pg,unsigned int n,element_type* p) { - if(f(const_cast(type_policy::value_from(*p)))){ + if(f(cast_for(group_exclusive{},type_policy::value_from(*p)))){ super::erase(pg,n,p); } }); @@ -515,15 +493,15 @@ public: std::size_t erase_if(F&& f) { auto lck=shared_access(); - std::size_t s=size(); + std::size_t s=unprotected_size(); for_all_elements( group_exclusive{}, [&,this](group_type* pg,unsigned int n,element_type* p){ - if(f(const_cast(type_policy::value_from(*p)))){ + if(f(cast_for(group_exclusive{},type_policy::value_from(*p)))){ super::erase(pg,n,p); } }); - return std::size_t(s-size()); + return std::size_t(s-unprotected_size()); } #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) @@ -535,7 +513,7 @@ public: for_all_elements( group_exclusive{},std::forward(policy), [&,this](group_type* pg,unsigned int n,element_type* p){ - if(f(const_cast(type_policy::value_from(*p)))){ + if(f(cast_for(group_exclusive{},type_policy::value_from(*p)))){ super::erase(pg,n,p); } }); @@ -731,12 +709,17 @@ private: return exclusive_access(pos); } - std::size_t unprotected_size()const - { - std::size_t m=this->ml; - std::size_t s=this->size_; - return s<=m?s:m; - } + /* Const casts value_type& according to the level of group access for + * safe passing to visitation functions. When type_policy is set-like, + * access is always const regardless of group access. + */ + + static const value_type& cast_for(group_shared,value_type& x){return x;} + static typename std::conditional< + std::is_same::value, + const value_type&, + value_type& + >::type cast_for(group_exclusive,value_type& x){return x;} struct erase_on_exit { @@ -755,6 +738,42 @@ private: bool rollback_=false; }; + template + BOOST_FORCEINLINE std::size_t visit_impl( + GroupAccessMode access_mode,const Key& x,F&& f)const + { + auto lck=shared_access(); + auto hash=this->hash_for(x); + return unprotected_visit( + access_mode,x,this->position_for(hash),hash,std::forward(f)); + } + + template + std::size_t visit_all_impl(GroupAccessMode access_mode,F&& f)const + { + auto lck=shared_access(); + std::size_t res=0; + for_all_elements(access_mode,[&](element_type* p){ + f(cast_for(access_mode,type_policy::value_from(*p))); + ++res; + }); + return res; + } + +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) + template + void visit_all_impl( + GroupAccessMode access_mode,ExecutionPolicy&& policy,F&& f)const + { + auto lck=shared_access(); + for_all_elements( + access_mode,std::forward(policy), + [&](element_type* p){ + f(cast_for(access_mode,type_policy::value_from(*p))); + }); + } +#endif + template BOOST_FORCEINLINE std::size_t unprotected_visit( GroupAccessMode access_mode, @@ -763,7 +782,7 @@ private: return unprotected_internal_visit( access_mode,x,pos0,hash, [&](group_type*,unsigned int,element_type* p) - {f(type_policy::value_from(*p));}); + {f(cast_for(access_mode,type_policy::value_from(*p)));}); } #if defined(BOOST_MSVC) @@ -808,6 +827,13 @@ private: #pragma warning(pop) /* C4800 */ #endif + std::size_t unprotected_size()const + { + std::size_t m=this->ml; + std::size_t s=this->size_; + return s<=m?s:m; + } + template BOOST_FORCEINLINE bool construct_and_emplace(Args&&... args) { @@ -954,7 +980,7 @@ private: } template - auto for_all_elements(GroupAccessMode access_mode,F f) + auto for_all_elements(GroupAccessMode access_mode,F f)const ->decltype(f(nullptr),void()) { for_all_elements( @@ -962,7 +988,7 @@ private: } template - auto for_all_elements(GroupAccessMode access_mode,F f) + auto for_all_elements(GroupAccessMode access_mode,F f)const ->decltype(f(nullptr,0,nullptr),void()) { auto p=this->arrays.elements; @@ -982,7 +1008,7 @@ private: #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) template auto for_all_elements( - GroupAccessMode access_mode,ExecutionPolicy&& policy,F f) + GroupAccessMode access_mode,ExecutionPolicy&& policy,F f)const ->decltype(f(nullptr),void()) { for_all_elements( @@ -992,7 +1018,7 @@ private: template auto for_all_elements( - GroupAccessMode access_mode,ExecutionPolicy&& policy,F f) + GroupAccessMode access_mode,ExecutionPolicy&& policy,F f)const ->decltype(f(nullptr,0,nullptr),void()) { if(!this->arrays.elements)return; From 02c3bd4072c60046b28fe12a097bdb69bbde2941 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 27 Mar 2023 11:32:07 +0200 Subject: [PATCH 107/327] stylistic --- include/boost/unordered/detail/foa/concurrent_table.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index abfca0bd..83360cbc 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -279,7 +279,8 @@ using concurrent_table_core_impl=table_core< #endif template -class concurrent_table:concurrent_table_core_impl +class concurrent_table: + concurrent_table_core_impl { using super=concurrent_table_core_impl; using type_policy=typename super::type_policy; From 45ebc1e555b65f4a3bdebde95b59560b172e983d Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 27 Mar 2023 13:39:14 +0200 Subject: [PATCH 108/327] fixed call ambiguities in insert_or_visit --- .../unordered/detail/foa/concurrent_table.hpp | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 83360cbc..4272bedc 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -453,16 +453,26 @@ public: group_exclusive{},std::forward(f),std::move(x)); } - /* typename=void tilts call ambiguities in favor of init_type */ + /* SFINAE tilts call ambiguities in favor of init_type */ - template - BOOST_FORCEINLINE bool insert_or_visit(const value_type& x,F&& f) + template + BOOST_FORCEINLINE auto insert_or_visit(const Value& x,F&& f) + ->typename std::enable_if< + !std::is_same::value&& + std::is_same::value, + bool + >::type { return emplace_or_visit_impl(group_exclusive{},std::forward(f),x); } - template - BOOST_FORCEINLINE bool insert_or_visit(value_type&& x,F&& f) + template + BOOST_FORCEINLINE auto insert_or_visit(Value&& x,F&& f) + ->typename std::enable_if< + !std::is_same::value&& + std::is_same::value, + bool + >::type { return emplace_or_visit_impl( group_exclusive{},std::forward(f),std::move(x)); From ce674c8007db335ab6163a82b3347514d7ede964 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 27 Mar 2023 15:06:28 -0700 Subject: [PATCH 109/327] Add transparent insert_or_assign --- .../boost/unordered/concurrent_flat_map.hpp | 12 ++ test/cfoa/insert_tests.cpp | 103 ++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 6f5c9ff3..a20a6776 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -16,6 +16,7 @@ #define BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP #include +#include #include #include @@ -163,6 +164,17 @@ namespace boost { std::forward(obj)); } + template + typename std::enable_if< + detail::are_transparent::value, bool>::type + insert_or_assign(K&& k, M&& obj) + { + return table_.try_emplace_or_visit( + std::forward(k), + [&](value_type& m) { m.second = std::forward(obj); }, + std::forward(obj)); + } + /// Hash Policy /// void rehash(size_type n) { table_.rehash(n); } diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index 96ac926d..c860c2ff 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -19,6 +20,26 @@ constexpr std::size_t const num_threads = 16; +struct transparent_hash +{ + using is_transparent = void; + + template std::size_t operator()(T const& t) const noexcept + { + return boost::hash()(t); + } +}; + +struct transparent_key_equal +{ + using is_transparent = void; + + template bool operator()(T const& lhs, U const& rhs) const + { + return lhs == rhs; + } +}; + struct raii { static std::atomic_int default_constructor; @@ -70,6 +91,25 @@ struct raii return !(lhs == rhs); } + friend bool operator==(raii const& lhs, int const x) + { + return lhs.x_ == x; + } + friend bool operator!=(raii const& lhs, int const x) + { + return !(lhs.x_ == x); + } + + friend bool operator==(int const x, raii const& rhs) + { + return rhs.x_ == x; + } + + friend bool operator!=(int const x, raii const& rhs) + { + return !(rhs.x_ == x); + } + friend std::ostream& operator<<(std::ostream& os, raii const& rhs) { os << "{ x_: " << rhs.x_ << " }"; @@ -300,6 +340,7 @@ namespace { } }); + BOOST_TEST_EQ(raii::default_constructor, 0); BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); // don't check move construction count here because of rehashing BOOST_TEST_GT(raii::move_constructor, 0); @@ -318,6 +359,7 @@ namespace { } }); + BOOST_TEST_EQ(raii::default_constructor, 0); BOOST_TEST_EQ(raii::copy_constructor, x.size()); BOOST_TEST_GT(raii::move_constructor, x.size()); // rehashing BOOST_TEST_EQ(raii::copy_assignment, 0); @@ -335,6 +377,7 @@ namespace { } }); + BOOST_TEST_EQ(raii::default_constructor, 0); BOOST_TEST_EQ(raii::copy_constructor, x.size()); BOOST_TEST_GT(raii::move_constructor, x.size()); // rehashing BOOST_TEST_EQ(raii::copy_assignment, values.size() - x.size()); @@ -352,11 +395,62 @@ namespace { } }); + BOOST_TEST_EQ(raii::default_constructor, 0); BOOST_TEST_EQ(raii::copy_assignment, 0); BOOST_TEST_EQ(raii::move_assignment, values.size() - x.size()); } } rvalue_insert_or_assign_move_assign; + struct transparent_insert_or_assign_copy_assign_type + { + template void operator()(std::vector& values, X& x) + { + using is_transparent = + typename boost::make_void::type; + + boost::ignore_unused(); + + BOOST_TEST_EQ(raii::default_constructor, 0); + + thread_runner(values, [&x](boost::span s) { + for (auto& r : s) { + x.insert_or_assign(r.first.x_, r.second); + } + }); + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_GT(raii::move_constructor, x.size()); // rehashing + BOOST_TEST_EQ(raii::copy_assignment, values.size() - x.size()); + BOOST_TEST_EQ(raii::move_assignment, 0); + } + } transparent_insert_or_assign_copy_assign; + + struct transparent_insert_or_assign_move_assign_type + { + template void operator()(std::vector& values, X& x) + { + using is_transparent = + typename boost::make_void::type; + + boost::ignore_unused(); + + thread_runner(values, [&x](boost::span s) { + for (auto& r : s) { + x.insert_or_assign(r.first.x_, std::move(r.second)); + } + }); + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + BOOST_TEST_EQ(raii::copy_constructor, 0); + BOOST_TEST_GT(raii::move_constructor, 2 * x.size()); // rehashing + BOOST_TEST_EQ(raii::copy_assignment, 0); + BOOST_TEST_EQ(raii::move_assignment, values.size() - x.size()); + } + } transparent_insert_or_assign_move_assign; + template void insert(X*, G gen, F inserter, test::random_generator rg) { @@ -454,6 +548,8 @@ namespace { } boost::unordered::concurrent_flat_map* map; + boost::unordered::concurrent_flat_map* transparent_map; } // namespace @@ -488,6 +584,13 @@ UNORDERED_TEST( ((lvalue_insert_or_assign_copy_assign)(lvalue_insert_or_assign_move_assign) (rvalue_insert_or_assign_copy_assign)(rvalue_insert_or_assign_move_assign)) ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + insert, + ((transparent_map)) + ((init_type_generator)) + ((transparent_insert_or_assign_copy_assign)(transparent_insert_or_assign_move_assign)) + ((default_generator)(sequential)(limited_range))) // clang-format on RUN_TESTS() From e4072747bb9ddc8850c634da108124b19ea42625 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 28 Mar 2023 14:28:42 -0700 Subject: [PATCH 110/327] Implement insert_or_visit() --- .../boost/unordered/concurrent_flat_map.hpp | 34 +++ test/cfoa/insert_tests.cpp | 276 +++++++++++++++--- 2 files changed, 277 insertions(+), 33 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index a20a6776..5b31af89 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -175,6 +175,40 @@ namespace boost { std::forward(obj)); } + template bool insert_or_visit(value_type const& obj, F f) + { + return table_.insert_or_visit(obj, std::move(f)); + } + + template bool insert_or_visit(value_type&& obj, F f) + { + return table_.insert_or_visit(std::move(obj), std::move(f)); + } + + template bool insert_or_visit(init_type const& obj, F f) + { + return table_.insert_or_visit(obj, std::move(f)); + } + + template bool insert_or_visit(init_type&& obj, F f) + { + return table_.insert_or_visit(std::move(obj), std::move(f)); + } + + template + void insert_or_visit(InputIterator first, InputIterator last, F f) + { + for (; first != last; ++first) { + table_.insert_or_visit(*first, f); + } + } + + template + void insert_or_visit(std::initializer_list ilist, F f) + { + this->insert_or_visit(ilist.begin(), ilist.end(), std::move(f)); + } + /// Hash Policy /// void rehash(size_type n) { table_.rehash(n); } diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index c860c2ff..3e0ea267 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -91,19 +91,13 @@ struct raii return !(lhs == rhs); } - friend bool operator==(raii const& lhs, int const x) - { - return lhs.x_ == x; - } + friend bool operator==(raii const& lhs, int const x) { return lhs.x_ == x; } friend bool operator!=(raii const& lhs, int const x) { return !(lhs.x_ == x); } - friend bool operator==(int const x, raii const& rhs) - { - return rhs.x_ == x; - } + friend bool operator==(int const x, raii const& rhs) { return rhs.x_ == x; } friend bool operator!=(int const x, raii const& rhs) { @@ -396,6 +390,8 @@ namespace { }); BOOST_TEST_EQ(raii::default_constructor, 0); + BOOST_TEST_EQ(raii::copy_constructor, 0); + BOOST_TEST_GE(raii::move_constructor, 2 * x.size()); BOOST_TEST_EQ(raii::copy_assignment, 0); BOOST_TEST_EQ(raii::move_assignment, values.size() - x.size()); } @@ -451,6 +447,179 @@ namespace { } } transparent_insert_or_assign_move_assign; + struct lvalue_insert_or_visit_const_visitor_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.insert_or_visit( + r, [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 0); + BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); + // don't check move construction count here because of rehashing + BOOST_TEST_GT(raii::move_constructor, 0); + BOOST_TEST_EQ(raii::move_assignment, 0); + } + } lvalue_insert_or_visit_const_visitor; + + struct lvalue_insert_or_visit_mut_visitor_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = + x.insert_or_visit(r, [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 0); + BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); + // don't check move construction count here because of rehashing + BOOST_TEST_GT(raii::move_constructor, 0); + BOOST_TEST_EQ(raii::move_assignment, 0); + } + } lvalue_insert_or_visit_mut_visitor; + + struct rvalue_insert_or_visit_const_visitor_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.insert_or_visit( + std::move(r), [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 0); + + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0); + BOOST_TEST_GE(raii::move_constructor, 2 * x.size()); + } + } + } rvalue_insert_or_visit_const_visitor; + + struct rvalue_insert_or_visit_mut_visitor_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.insert_or_visit( + std::move(r), [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 0); + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0); + BOOST_TEST_GE(raii::move_constructor, 2 * x.size()); + } + } + } rvalue_insert_or_visit_mut_visitor; + + struct iterator_range_insert_or_visit_const_visitor_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_invokes](boost::span s) { + x.insert_or_visit( + s.begin(), s.end(), [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + }); + + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 0); + BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); + BOOST_TEST_GT(raii::move_constructor, 0); + } + } iterator_range_insert_or_visit_const_visitor; + + struct iterator_range_insert_or_visit_mut_visitor_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_invokes](boost::span s) { + x.insert_or_visit( + s.begin(), s.end(), [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + }); + + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 0); + BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); + BOOST_TEST_GT(raii::move_constructor, 0); + } + } iterator_range_insert_or_visit_mut_visitor; + template void insert(X*, G gen, F inserter, test::random_generator rg) { @@ -521,30 +690,75 @@ namespace { raii::reset_counts(); { - X x; + { + X x; - thread_runner( - dummy, [&x, &values](boost::span) { x.insert(values); }); + thread_runner( + dummy, [&x, &values](boost::span) { x.insert(values); }); - BOOST_TEST_EQ(x.size(), reference_map.size()); + BOOST_TEST_EQ(x.size(), reference_map.size()); - BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { - BOOST_TEST(reference_map.contains(kv.first)); - BOOST_TEST_EQ(kv.second, reference_map[kv.first]); - })); + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map[kv.first]); + })); + } + + 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_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); + { + { + std::atomic num_invokes{0}; - BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + - raii::move_constructor, - raii::destructor); + X x; - BOOST_TEST_EQ(raii::copy_assignment, 0); - BOOST_TEST_EQ(raii::move_assignment, 0); + thread_runner(dummy, [&x, &values, &num_invokes](boost::span) { + x.insert_or_visit(values, [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }); + + x.insert_or_visit( + values, [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + }); + + BOOST_TEST_EQ(num_invokes, (values.size() - x.size()) + + (num_threads - 1) * values.size() + + num_threads * values.size()); + BOOST_TEST_EQ(x.size(), reference_map.size()); + + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map[kv.first]); + })); + } + + 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; @@ -567,14 +781,10 @@ UNORDERED_TEST( ((map)) ((value_type_generator)(init_type_generator)) ((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter) - (norehash_lvalue_inserter)(norehash_rvalue_inserter)) - ((default_generator)(sequential)(limited_range))) - -UNORDERED_TEST( - insert, - ((map)) - ((value_type_generator)) - ((lvalue_insert_or_assign_copy_assign)(lvalue_insert_or_assign_move_assign)) + (norehash_lvalue_inserter)(norehash_rvalue_inserter) + (lvalue_insert_or_visit_const_visitor)(lvalue_insert_or_visit_mut_visitor) + (rvalue_insert_or_visit_const_visitor)(rvalue_insert_or_visit_mut_visitor) + (iterator_range_insert_or_visit_const_visitor)(iterator_range_insert_or_visit_mut_visitor)) ((default_generator)(sequential)(limited_range))) UNORDERED_TEST( From 1c48f665ea1375aaa49a6ccbc4ebf714b8f368a3 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 28 Mar 2023 14:30:29 -0700 Subject: [PATCH 111/327] Add alias target for foa container tests --- test/Jamfile.v2 | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index eb971b37..ee5a2018 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -158,6 +158,55 @@ run exception/rehash_exception_tests.cpp : : : $(CPP11) BOOST_UNORD 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 ; +alias foa_tests : + hash_is_avalanching_test + foa_fwd_set_test + foa_fwd_map_test + foa_compile_set + foa_compile_map + foa_noexcept_tests + foa_incomplete_test + foa_simple_tests + foa_equivalent_keys_tests + foa_constructor_tests + foa_copy_tests + foa_move_tests + foa_post_move_tests + foa_assign_tests + foa_insert_tests + foa_insert_hint_tests + foa_emplace_tests + foa_erase_tests + foa_merge_tests + foa_find_tests + foa_at_tests + foa_load_factor_tests + foa_rehash_tests + foa_equality_tests + foa_swap_tests + foa_transparent_tests + foa_reserve_tests + foa_contains_tests + foa_erase_if + foa_scary_tests + foa_init_type_insert_tests + foa_max_load_tests + foa_extract_tests + foa_node_handle_tests + foa_uses_allocator + foa_link_test + foa_scoped_allocator + foa_constructor_exception_tests + foa_copy_exception_tests + foa_assign_exception_tests + foa_move_assign_exception_tests + foa_insert_exception_tests + foa_erase_exception_tests + foa_rehash_exception_tests + foa_swap_exception_tests + foa_merge_exception_tests +; + rule build_cfoa ( name ) { run cfoa/$(name).cpp : : : $(CPP11) : cfoa_$(name) ; From 7b1af37b9c53790678174f549c262b19bd496bf3 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 28 Mar 2023 14:37:52 -0700 Subject: [PATCH 112/327] Update RAII counters to use unsigned integers instead of signed --- test/cfoa/insert_tests.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index 3e0ea267..67ba65b5 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -42,13 +42,13 @@ struct transparent_key_equal 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 default_constructor; + static std::atomic copy_constructor; + static std::atomic move_constructor; + static std::atomic destructor; - static std::atomic_int copy_assignment; - static std::atomic_int move_assignment; + static std::atomic copy_assignment; + static std::atomic move_assignment; int x_ = -1; @@ -128,12 +128,12 @@ struct raii } }; -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::atomic raii::default_constructor{0}; +std::atomic raii::copy_constructor{0}; +std::atomic raii::move_constructor{0}; +std::atomic raii::destructor{0}; +std::atomic raii::copy_assignment{0}; +std::atomic raii::move_assignment{0}; std::size_t hash_value(raii const& r) noexcept { From 4482031329b9c886c931ad420bf8209e2970a2b4 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Wed, 29 Mar 2023 16:45:15 +0200 Subject: [PATCH 113/327] made concurrent_table::thread_counter static --- include/boost/unordered/detail/foa/concurrent_table.hpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 4272bedc..5bd4d510 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -656,7 +656,6 @@ private: shared_lock_guard shared_access()const { - // TODO: make this more sophisticated (even distribution) thread_local auto id=(++thread_counter)%mutexes.size(); return shared_lock_guard{mutexes[id]}; @@ -1051,11 +1050,13 @@ private: } #endif - /* TODO: thread_counter should be static */ - mutable std::atomic thread_counter{0}; - mutable multimutex_type mutexes; + static std::atomic thread_counter; + mutable multimutex_type mutexes; }; +template +std::atomic concurrent_table::thread_counter=0; + #if defined(BOOST_MSVC) #pragma warning(pop) /* C4714 */ #endif From 62cf58d1ca21759e32266244131c22c94edc218e Mon Sep 17 00:00:00 2001 From: joaquintides Date: Wed, 29 Mar 2023 18:29:15 +0200 Subject: [PATCH 114/327] sprinkled some inlines --- .../unordered/detail/foa/concurrent_table.hpp | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 5bd4d510..9111dadb 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -654,51 +654,51 @@ private: concurrent_table&& x,const Allocator& al_,exclusive_lock_guard): super{std::move(x),al_}{} - shared_lock_guard shared_access()const + inline shared_lock_guard shared_access()const { thread_local auto id=(++thread_counter)%mutexes.size(); return shared_lock_guard{mutexes[id]}; } - exclusive_lock_guard exclusive_access()const + inline exclusive_lock_guard exclusive_access()const { return exclusive_lock_guard{mutexes}; } - exclusive_bilock_guard exclusive_access( + inline exclusive_bilock_guard exclusive_access( const concurrent_table& x,const concurrent_table& y) { return {x.mutexes,y.mutexes}; } #if defined(BOOST_UNORDERED_EMBEDDED_GROUP_ACCESS) - group_shared_lock_guard shared_access(std::size_t pos)const + inline group_shared_lock_guard shared_access(std::size_t pos)const { return this->arrays.groups[pos].shared_access(); } - group_exclusive_lock_guard exclusive_access(std::size_t pos)const + inline group_exclusive_lock_guard exclusive_access(std::size_t pos)const { return this->arrays.groups[pos].exclusive_access(); } - group_insert_counter_type& insert_counter(std::size_t pos)const + inline group_insert_counter_type& insert_counter(std::size_t pos)const { return this->arrays.groups[pos].insert_counter(); } #else - group_shared_lock_guard shared_access(std::size_t pos)const + inline group_shared_lock_guard shared_access(std::size_t pos)const { return this->arrays.group_accesses[pos].shared_access(); } - group_exclusive_lock_guard exclusive_access(std::size_t pos)const + inline group_exclusive_lock_guard exclusive_access(std::size_t pos)const { return this->arrays.group_accesses[pos].exclusive_access(); } - group_insert_counter_type& insert_counter(std::size_t pos)const + inline group_insert_counter_type& insert_counter(std::size_t pos)const { return this->arrays.group_accesses[pos].insert_counter(); } @@ -709,12 +709,13 @@ private: using group_shared=std::false_type; using group_exclusive=std::true_type; - group_shared_lock_guard access(group_shared,std::size_t pos)const + inline group_shared_lock_guard access(group_shared,std::size_t pos)const { return shared_access(pos); } - group_exclusive_lock_guard access(group_exclusive,std::size_t pos)const + inline group_exclusive_lock_guard access( + group_exclusive,std::size_t pos)const { return exclusive_access(pos); } @@ -724,8 +725,9 @@ private: * access is always const regardless of group access. */ - static const value_type& cast_for(group_shared,value_type& x){return x;} - static typename std::conditional< + static inline const value_type& cast_for(group_shared,value_type& x) + {return x;} + static inline typename std::conditional< std::is_same::value, const value_type&, value_type& From 846de7ca82922f568360b6726e2bea88110cfeae Mon Sep 17 00:00:00 2001 From: joaquintides Date: Wed, 29 Mar 2023 19:17:57 +0200 Subject: [PATCH 115/327] added cvisit overloads to concurrent_table --- .../unordered/detail/foa/concurrent_table.hpp | 81 ++++++++++++++++--- 1 file changed, 71 insertions(+), 10 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 9111dadb..9f0b6908 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -310,6 +310,15 @@ public: using allocator_type=typename super::allocator_type; using size_type=typename super::size_type; +private: + template + using enable_if_is_value_type=typename std::enable_if< + !std::is_same::value&& + std::is_same::value, + T + >::type; + +public: concurrent_table( std::size_t n=default_bucket_count,const Hash& h_=Hash(), const Pred& pred_=Pred(),const Allocator& al_=Allocator()): @@ -358,6 +367,12 @@ public: return visit_impl(group_shared{},x,std::forward(f)); } + template + BOOST_FORCEINLINE std::size_t cvisit(const Key& x,F&& f)const + { + return visit(x,std::forward(f)); + } + template std::size_t visit_all(F&& f) { return visit_all_impl(group_exclusive{},std::forward(f)); @@ -368,6 +383,11 @@ public: return visit_all_impl(group_shared{},std::forward(f)); } + template std::size_t cvisit_all(F&& f)const + { + return visit_all(std::forward(f)); + } + #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) template void visit_all(ExecutionPolicy&& policy,F&& f) @@ -384,6 +404,12 @@ public: group_shared{}, std::forward(policy),std::forward(f)); } + + template + void cvisit_all(ExecutionPolicy&& policy,F&& f)const + { + visit_all(std::forward(policy),std::forward(f)); + } #endif bool empty()const noexcept{return size()==0;} @@ -433,6 +459,14 @@ public: try_emplace_args_t{},std::forward(x),std::forward(args)...); } + template + BOOST_FORCEINLINE bool try_emplace_or_cvisit(Key&& x,F&& f,Args&&... args) + { + return emplace_or_visit_impl( + group_shared{},std::forward(f), + try_emplace_args_t{},std::forward(x),std::forward(args)...); + } + template BOOST_FORCEINLINE bool emplace_or_visit(F&& f,Args&&... args) { @@ -440,12 +474,25 @@ public: group_exclusive{},std::forward(f),std::forward(args)...); } + template + BOOST_FORCEINLINE bool emplace_or_cvisit(F&& f,Args&&... args) + { + return construct_and_emplace_or_visit( + group_shared{},std::forward(f),std::forward(args)...); + } + template BOOST_FORCEINLINE bool insert_or_visit(const init_type& x,F&& f) { return emplace_or_visit_impl(group_exclusive{},std::forward(f),x); } + template + BOOST_FORCEINLINE bool insert_or_cvisit(const init_type& x,F&& f) + { + return emplace_or_visit_impl(group_shared{},std::forward(f),x); + } + template BOOST_FORCEINLINE bool insert_or_visit(init_type&& x,F&& f) { @@ -453,31 +500,45 @@ public: group_exclusive{},std::forward(f),std::move(x)); } + template + BOOST_FORCEINLINE bool insert_or_cvisit(init_type&& x,F&& f) + { + return emplace_or_visit_impl( + group_shared{},std::forward(f),std::move(x)); + } + /* SFINAE tilts call ambiguities in favor of init_type */ template BOOST_FORCEINLINE auto insert_or_visit(const Value& x,F&& f) - ->typename std::enable_if< - !std::is_same::value&& - std::is_same::value, - bool - >::type + ->enable_if_is_value_type { return emplace_or_visit_impl(group_exclusive{},std::forward(f),x); } + template + BOOST_FORCEINLINE auto insert_or_cvisit(const Value& x,F&& f) + ->enable_if_is_value_type + { + return emplace_or_visit_impl(group_shared{},std::forward(f),x); + } + template BOOST_FORCEINLINE auto insert_or_visit(Value&& x,F&& f) - ->typename std::enable_if< - !std::is_same::value&& - std::is_same::value, - bool - >::type + ->enable_if_is_value_type { return emplace_or_visit_impl( group_exclusive{},std::forward(f),std::move(x)); } + template + BOOST_FORCEINLINE auto insert_or_cvisit(Value&& x,F&& f) + ->enable_if_is_value_type + { + return emplace_or_visit_impl( + group_shared{},std::forward(f),std::move(x)); + } + template BOOST_FORCEINLINE std::size_t erase(Key&& x) { From 27b4c62bd2e0ecc8331b357b45fd2047944b9d61 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Wed, 29 Mar 2023 20:35:48 +0200 Subject: [PATCH 116/327] fixed initialization of concurrent_table::thread:counter --- include/boost/unordered/detail/foa/concurrent_table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 9f0b6908..fda9b7c8 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -1118,7 +1118,7 @@ private: }; template -std::atomic concurrent_table::thread_counter=0; +std::atomic concurrent_table::thread_counter={}; #if defined(BOOST_MSVC) #pragma warning(pop) /* C4714 */ From 13b4fd7133d412dab22bbe49422a1de925bdc7f3 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 29 Mar 2023 12:16:09 -0700 Subject: [PATCH 117/327] Fix sign-compare warnings --- test/cfoa/insert_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index 67ba65b5..4ceae25e 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -337,7 +337,7 @@ namespace { BOOST_TEST_EQ(raii::default_constructor, 0); BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); // don't check move construction count here because of rehashing - BOOST_TEST_GT(raii::move_constructor, 0); + BOOST_TEST_GT(raii::move_constructor, 0u); BOOST_TEST_EQ(raii::copy_assignment, values.size() - x.size()); BOOST_TEST_EQ(raii::move_assignment, 0); } From 0c90585511a9e684e9f5b9df3c23bf01e5f33a59 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 29 Mar 2023 15:00:32 -0700 Subject: [PATCH 118/327] Refactor myriad test helpers into separate file --- test/cfoa/helpers.hpp | 229 +++++++++++++++++++++++++++++++++++++ test/cfoa/insert_tests.cpp | 224 +----------------------------------- 2 files changed, 230 insertions(+), 223 deletions(-) create mode 100644 test/cfoa/helpers.hpp diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp new file mode 100644 index 00000000..45d9af40 --- /dev/null +++ b/test/cfoa/helpers.hpp @@ -0,0 +1,229 @@ +#ifndef BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP +#define BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP + +#include "../helpers/generators.hpp" +#include "../helpers/test.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include + +constexpr std::size_t const num_threads = 16; + +struct transparent_hash +{ + using is_transparent = void; + + template std::size_t operator()(T const& t) const noexcept + { + return boost::hash()(t); + } +}; + +struct transparent_key_equal +{ + using is_transparent = void; + + template bool operator()(T const& lhs, U const& rhs) const + { + return lhs == rhs; + } +}; + +struct raii +{ + static std::atomic default_constructor; + static std::atomic copy_constructor; + static std::atomic move_constructor; + static std::atomic destructor; + + static std::atomic copy_assignment; + static std::atomic move_assignment; + + int x_ = -1; + + raii() { ++default_constructor; } + 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; + } + + 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); + } + + friend bool operator==(raii const& lhs, int const x) { return lhs.x_ == x; } + friend bool operator!=(raii const& lhs, int const x) + { + return !(lhs.x_ == x); + } + + friend bool operator==(int const x, raii const& rhs) { return rhs.x_ == x; } + + friend bool operator!=(int const x, raii const& rhs) + { + return !(rhs.x_ == x); + } + + 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; + } + + static void reset_counts() + { + default_constructor = 0; + copy_constructor = 0; + move_constructor = 0; + destructor = 0; + copy_assignment = 0; + move_assignment = 0; + } +}; + +std::atomic raii::default_constructor{0}; +std::atomic raii::copy_constructor{0}; +std::atomic raii::move_constructor{0}; +std::atomic raii::destructor{0}; +std::atomic raii::copy_assignment{0}; +std::atomic raii::move_assignment{0}; + +std::size_t hash_value(raii const& r) noexcept +{ + boost::hash hasher; + return hasher(r.x_); +} + +template +auto make_random_values(std::size_t count, F f) -> std::vector +{ + using vector_type = std::vector; + + vector_type v; + v.reserve(count); + for (std::size_t i = 0; i < count; ++i) { + v.emplace_back(f()); + } + return v; +} + +struct value_type_generator_type +{ + std::pair operator()(test::random_generator rg) + { + int* p = nullptr; + int a = generate(p, rg); + int b = generate(p, rg); + return std::make_pair(raii{a}, raii{b}); + } +} value_type_generator; + +struct init_type_generator_type +{ + std::pair operator()(test::random_generator rg) + { + int* p = nullptr; + int a = generate(p, rg); + int b = generate(p, rg); + return std::make_pair(raii{a}, raii{b}); + } +} init_type_generator; + +template +std::vector > split( + boost::span s, std::size_t const nt /* num threads*/) +{ + std::vector > subslices; + subslices.reserve(nt); + + auto a = s.size() / nt; + auto b = a; + if (s.size() % nt != 0) { + ++b; + } + + auto num_a = nt; + auto num_b = std::size_t{0}; + + if (nt * b > s.size()) { + num_a = nt * b - s.size(); + num_b = nt - num_a; + } + + auto sub_b = s.subspan(0, num_b * b); + auto sub_a = s.subspan(num_b * b); + + for (std::size_t i = 0; i < num_b; ++i) { + subslices.push_back(sub_b.subspan(i * b, b)); + } + + for (std::size_t i = 0; i < num_a; ++i) { + auto const is_last = i == (num_a - 1); + subslices.push_back( + sub_a.subspan(i * a, is_last ? boost::dynamic_extent : a)); + } + + return subslices; +} + +template void thread_runner(std::vector& values, F f) +{ + std::vector threads; + auto subslices = split(values, num_threads); + + for (std::size_t i = 0; i < num_threads; ++i) { + threads.emplace_back([&f, &subslices, i] { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + auto s = subslices[i]; + f(s); + }); + } + for (auto& t : threads) { + t.join(); + } +} + +#endif // BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP \ No newline at end of file diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index 4ceae25e..79caea1e 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -2,237 +2,15 @@ // 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 "helpers.hpp" #include -#include #include -#include -#include - -#include -#include -#include -#include -#include - -constexpr std::size_t const num_threads = 16; - -struct transparent_hash -{ - using is_transparent = void; - - template std::size_t operator()(T const& t) const noexcept - { - return boost::hash()(t); - } -}; - -struct transparent_key_equal -{ - using is_transparent = void; - - template bool operator()(T const& lhs, U const& rhs) const - { - return lhs == rhs; - } -}; - -struct raii -{ - static std::atomic default_constructor; - static std::atomic copy_constructor; - static std::atomic move_constructor; - static std::atomic destructor; - - static std::atomic copy_assignment; - static std::atomic move_assignment; - - int x_ = -1; - - raii() { ++default_constructor; } - 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; - } - - 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); - } - - friend bool operator==(raii const& lhs, int const x) { return lhs.x_ == x; } - friend bool operator!=(raii const& lhs, int const x) - { - return !(lhs.x_ == x); - } - - friend bool operator==(int const x, raii const& rhs) { return rhs.x_ == x; } - - friend bool operator!=(int const x, raii const& rhs) - { - return !(rhs.x_ == x); - } - - 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; - } - - static void reset_counts() - { - default_constructor = 0; - copy_constructor = 0; - move_constructor = 0; - destructor = 0; - copy_assignment = 0; - move_assignment = 0; - } -}; - -std::atomic raii::default_constructor{0}; -std::atomic raii::copy_constructor{0}; -std::atomic raii::move_constructor{0}; -std::atomic raii::destructor{0}; -std::atomic raii::copy_assignment{0}; -std::atomic raii::move_assignment{0}; - -std::size_t hash_value(raii const& r) noexcept -{ - boost::hash hasher; - return hasher(r.x_); -} - -template -auto make_random_values(std::size_t count, F f) -> std::vector -{ - using vector_type = std::vector; - - vector_type v; - v.reserve(count); - for (std::size_t i = 0; i < count; ++i) { - v.emplace_back(f()); - } - return v; -} namespace { test::seed_t initialize_seed(78937); - struct value_type_generator_type - { - std::pair operator()(test::random_generator rg) - { - int* p = nullptr; - int a = generate(p, rg); - int b = generate(p, rg); - return std::make_pair(raii{a}, raii{b}); - } - } value_type_generator; - - struct init_type_generator_type - { - std::pair operator()(test::random_generator rg) - { - int* p = nullptr; - int a = generate(p, rg); - int b = generate(p, rg); - return std::make_pair(raii{a}, raii{b}); - } - } init_type_generator; - - template - std::vector > split( - boost::span s, std::size_t const nt /* num threads*/) - { - std::vector > subslices; - subslices.reserve(nt); - - auto a = s.size() / nt; - auto b = a; - if (s.size() % nt != 0) { - ++b; - } - - auto num_a = nt; - auto num_b = std::size_t{0}; - - if (nt * b > s.size()) { - num_a = nt * b - s.size(); - num_b = nt - num_a; - } - - auto sub_b = s.subspan(0, num_b * b); - auto sub_a = s.subspan(num_b * b); - - for (std::size_t i = 0; i < num_b; ++i) { - subslices.push_back(sub_b.subspan(i * b, b)); - } - - for (std::size_t i = 0; i < num_a; ++i) { - auto const is_last = i == (num_a - 1); - subslices.push_back( - sub_a.subspan(i * a, is_last ? boost::dynamic_extent : a)); - } - - return subslices; - } - - template void thread_runner(std::vector& values, F f) - { - std::vector threads; - auto subslices = split(values, num_threads); - - for (std::size_t i = 0; i < num_threads; ++i) { - threads.emplace_back([&f, &subslices, i] { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - auto s = subslices[i]; - f(s); - }); - } - for (auto& t : threads) { - t.join(); - } - } - struct lvalue_inserter_type { template void operator()(std::vector& values, X& x) From 5e316ebc88b740116c95959b8f32723d6bbbf2c9 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 29 Mar 2023 15:00:54 -0700 Subject: [PATCH 119/327] Add initial draft of erase() --- .../boost/unordered/concurrent_flat_map.hpp | 7 ++ test/Jamfile.v2 | 3 +- test/cfoa/erase_tests.cpp | 85 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 test/cfoa/erase_tests.cpp diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 5b31af89..ccc028ba 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -123,6 +123,11 @@ namespace boost { size_type size() const noexcept { return table_.size(); } + BOOST_ATTRIBUTE_NODISCARD bool empty() const noexcept + { + return size() == 0; + } + /// Modifiers /// @@ -209,6 +214,8 @@ namespace boost { this->insert_or_visit(ilist.begin(), ilist.end(), std::move(f)); } + size_type erase(key_type const& k) { return table_.erase(k); } + /// Hash Policy /// void rehash(size_type n) { table_.rehash(n); } diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index ee5a2018..160e32b2 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -213,5 +213,6 @@ rule build_cfoa ( name ) } build_cfoa insert_tests ; +build_cfoa erase_tests ; -alias cfoa_tests : cfoa_insert_tests ; +alias cfoa_tests : cfoa_insert_tests cfoa_erase_tests ; diff --git a/test/cfoa/erase_tests.cpp b/test/cfoa/erase_tests.cpp new file mode 100644 index 00000000..1db36905 --- /dev/null +++ b/test/cfoa/erase_tests.cpp @@ -0,0 +1,85 @@ +// 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 + +#include + +namespace { + test::seed_t initialize_seed(3292023); + + struct lvalue_eraser_type + { + template void operator()(std::vector& values, X& x) + { + thread_runner(values, [&values, &x](boost::span) { + for (auto const& k : values) { + x.erase(k.first); + } + }); + + BOOST_TEST_EQ(x.size(), 0); + BOOST_TEST(x.empty()); + } + } lvalue_eraser; + + template + void erase(X*, G gen, F eraser, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + X x; + + x.insert(values.begin(), values.end()); + + BOOST_TEST_EQ(x.size(), reference_map.size()); + + using value_type = typename X::value_type; + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + if (rg == test::sequential) { + BOOST_TEST_EQ(kv.second, reference_map[kv.first]); + } + })); + + eraser(values, x); + } + + 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::unordered::concurrent_flat_map* map; + // boost::unordered::concurrent_flat_map* transparent_map; + +} // namespace + +using test::default_generator; +using test::limited_range; +using test::sequential; + +// clang-format off +UNORDERED_TEST( + erase, + ((map)) + ((value_type_generator)(init_type_generator)) + ((lvalue_eraser)) + ((default_generator)(sequential)(limited_range))) + +// clang-format on + +RUN_TESTS() From f468fb77e00459476878122e013987fca3e871c5 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 29 Mar 2023 15:55:22 -0700 Subject: [PATCH 120/327] Hopefully fix sign-conversion warnings --- test/cfoa/erase_tests.cpp | 10 ++-- test/cfoa/insert_tests.cpp | 110 ++++++++++++++++++------------------- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/test/cfoa/erase_tests.cpp b/test/cfoa/erase_tests.cpp index 1db36905..7dfdba2d 100644 --- a/test/cfoa/erase_tests.cpp +++ b/test/cfoa/erase_tests.cpp @@ -21,7 +21,7 @@ namespace { } }); - BOOST_TEST_EQ(x.size(), 0); + BOOST_TEST_EQ(x.size(), 0u); BOOST_TEST(x.empty()); } } lvalue_eraser; @@ -52,10 +52,10 @@ namespace { eraser(values, x); } - 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_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + raii::move_constructor, diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index 79caea1e..eda91b9f 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -25,8 +25,8 @@ namespace { } }); BOOST_TEST_EQ(num_inserts, x.size()); - BOOST_TEST_EQ(raii::copy_assignment, 0); - BOOST_TEST_EQ(raii::move_assignment, 0); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); } } lvalue_inserter; @@ -37,7 +37,7 @@ namespace { x.reserve(values.size()); lvalue_inserter_type::operator()(values, x); BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); - BOOST_TEST_EQ(raii::move_constructor, 0); + BOOST_TEST_EQ(raii::move_constructor, 0u); } } norehash_lvalue_inserter; @@ -45,7 +45,7 @@ namespace { { template void operator()(std::vector& values, X& x) { - BOOST_TEST_EQ(raii::copy_constructor, 0); + BOOST_TEST_EQ(raii::copy_constructor, 0u); std::atomic num_inserts{0}; thread_runner(values, [&x, &num_inserts](boost::span s) { @@ -61,11 +61,11 @@ namespace { if (std::is_same::value) { BOOST_TEST_EQ(raii::copy_constructor, x.size()); } else { - BOOST_TEST_EQ(raii::copy_constructor, 0); + BOOST_TEST_EQ(raii::copy_constructor, 0u); } - BOOST_TEST_EQ(raii::copy_assignment, 0); - BOOST_TEST_EQ(raii::move_assignment, 0); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); } } rvalue_inserter; @@ -75,8 +75,8 @@ namespace { { x.reserve(values.size()); - BOOST_TEST_EQ(raii::copy_constructor, 0); - BOOST_TEST_EQ(raii::move_constructor, 0); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_EQ(raii::move_constructor, 0u); rvalue_inserter_type::operator()(values, x); @@ -84,7 +84,7 @@ namespace { BOOST_TEST_EQ(raii::copy_constructor, x.size()); BOOST_TEST_EQ(raii::move_constructor, x.size()); } else { - BOOST_TEST_EQ(raii::copy_constructor, 0); + BOOST_TEST_EQ(raii::copy_constructor, 0u); BOOST_TEST_EQ(raii::move_constructor, 2 * x.size()); } } @@ -97,8 +97,8 @@ namespace { thread_runner( values, [&x](boost::span s) { x.insert(s.begin(), s.end()); }); - BOOST_TEST_EQ(raii::copy_assignment, 0); - BOOST_TEST_EQ(raii::move_assignment, 0); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); } } iterator_range_inserter; @@ -112,12 +112,12 @@ namespace { } }); - BOOST_TEST_EQ(raii::default_constructor, 0); + BOOST_TEST_EQ(raii::default_constructor, 0u); BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); // don't check move construction count here because of rehashing BOOST_TEST_GT(raii::move_constructor, 0u); BOOST_TEST_EQ(raii::copy_assignment, values.size() - x.size()); - BOOST_TEST_EQ(raii::move_assignment, 0); + BOOST_TEST_EQ(raii::move_assignment, 0u); } } lvalue_insert_or_assign_copy_assign; @@ -131,10 +131,10 @@ namespace { } }); - BOOST_TEST_EQ(raii::default_constructor, 0); + BOOST_TEST_EQ(raii::default_constructor, 0u); BOOST_TEST_EQ(raii::copy_constructor, x.size()); BOOST_TEST_GT(raii::move_constructor, x.size()); // rehashing - BOOST_TEST_EQ(raii::copy_assignment, 0); + BOOST_TEST_EQ(raii::copy_assignment, 0u); BOOST_TEST_EQ(raii::move_assignment, values.size() - x.size()); } } lvalue_insert_or_assign_move_assign; @@ -149,11 +149,11 @@ namespace { } }); - BOOST_TEST_EQ(raii::default_constructor, 0); + BOOST_TEST_EQ(raii::default_constructor, 0u); BOOST_TEST_EQ(raii::copy_constructor, x.size()); BOOST_TEST_GT(raii::move_constructor, x.size()); // rehashing BOOST_TEST_EQ(raii::copy_assignment, values.size() - x.size()); - BOOST_TEST_EQ(raii::move_assignment, 0); + BOOST_TEST_EQ(raii::move_assignment, 0u); } } rvalue_insert_or_assign_copy_assign; @@ -167,10 +167,10 @@ namespace { } }); - BOOST_TEST_EQ(raii::default_constructor, 0); - BOOST_TEST_EQ(raii::copy_constructor, 0); + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_EQ(raii::copy_constructor, 0u); BOOST_TEST_GE(raii::move_constructor, 2 * x.size()); - BOOST_TEST_EQ(raii::copy_assignment, 0); + BOOST_TEST_EQ(raii::copy_assignment, 0u); BOOST_TEST_EQ(raii::move_assignment, values.size() - x.size()); } } rvalue_insert_or_assign_move_assign; @@ -185,7 +185,7 @@ namespace { boost::ignore_unused(); - BOOST_TEST_EQ(raii::default_constructor, 0); + BOOST_TEST_EQ(raii::default_constructor, 0u); thread_runner(values, [&x](boost::span s) { for (auto& r : s) { @@ -197,7 +197,7 @@ namespace { BOOST_TEST_EQ(raii::copy_constructor, x.size()); BOOST_TEST_GT(raii::move_constructor, x.size()); // rehashing BOOST_TEST_EQ(raii::copy_assignment, values.size() - x.size()); - BOOST_TEST_EQ(raii::move_assignment, 0); + BOOST_TEST_EQ(raii::move_assignment, 0u); } } transparent_insert_or_assign_copy_assign; @@ -218,9 +218,9 @@ namespace { }); BOOST_TEST_EQ(raii::default_constructor, x.size()); - BOOST_TEST_EQ(raii::copy_constructor, 0); + BOOST_TEST_EQ(raii::copy_constructor, 0u); BOOST_TEST_GT(raii::move_constructor, 2 * x.size()); // rehashing - BOOST_TEST_EQ(raii::copy_assignment, 0); + BOOST_TEST_EQ(raii::copy_assignment, 0u); BOOST_TEST_EQ(raii::move_assignment, values.size() - x.size()); } } transparent_insert_or_assign_move_assign; @@ -248,11 +248,11 @@ namespace { BOOST_TEST_EQ(num_inserts, x.size()); BOOST_TEST_EQ(num_invokes, values.size() - x.size()); - BOOST_TEST_EQ(raii::default_constructor, 0); + BOOST_TEST_EQ(raii::default_constructor, 0u); BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); // don't check move construction count here because of rehashing - BOOST_TEST_GT(raii::move_constructor, 0); - BOOST_TEST_EQ(raii::move_assignment, 0); + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); } } lvalue_insert_or_visit_const_visitor; @@ -279,11 +279,11 @@ namespace { BOOST_TEST_EQ(num_inserts, x.size()); BOOST_TEST_EQ(num_invokes, values.size() - x.size()); - BOOST_TEST_EQ(raii::default_constructor, 0); + BOOST_TEST_EQ(raii::default_constructor, 0u); BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); // don't check move construction count here because of rehashing - BOOST_TEST_GT(raii::move_constructor, 0); - BOOST_TEST_EQ(raii::move_assignment, 0); + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); } } lvalue_insert_or_visit_mut_visitor; @@ -310,13 +310,13 @@ namespace { BOOST_TEST_EQ(num_inserts, x.size()); BOOST_TEST_EQ(num_invokes, values.size() - x.size()); - BOOST_TEST_EQ(raii::default_constructor, 0); + BOOST_TEST_EQ(raii::default_constructor, 0u); if (std::is_same::value) { BOOST_TEST_EQ(raii::copy_constructor, x.size()); BOOST_TEST_GE(raii::move_constructor, x.size()); } else { - BOOST_TEST_EQ(raii::copy_constructor, 0); + BOOST_TEST_EQ(raii::copy_constructor, 0u); BOOST_TEST_GE(raii::move_constructor, 2 * x.size()); } } @@ -345,12 +345,12 @@ namespace { BOOST_TEST_EQ(num_inserts, x.size()); BOOST_TEST_EQ(num_invokes, values.size() - x.size()); - BOOST_TEST_EQ(raii::default_constructor, 0); + BOOST_TEST_EQ(raii::default_constructor, 0u); if (std::is_same::value) { BOOST_TEST_EQ(raii::copy_constructor, x.size()); BOOST_TEST_GE(raii::move_constructor, x.size()); } else { - BOOST_TEST_EQ(raii::copy_constructor, 0); + BOOST_TEST_EQ(raii::copy_constructor, 0u); BOOST_TEST_GE(raii::move_constructor, 2 * x.size()); } } @@ -371,9 +371,9 @@ namespace { BOOST_TEST_EQ(num_invokes, values.size() - x.size()); - BOOST_TEST_EQ(raii::default_constructor, 0); + BOOST_TEST_EQ(raii::default_constructor, 0u); BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); - BOOST_TEST_GT(raii::move_constructor, 0); + BOOST_TEST_GT(raii::move_constructor, 0u); } } iterator_range_insert_or_visit_const_visitor; @@ -392,9 +392,9 @@ namespace { BOOST_TEST_EQ(num_invokes, values.size() - x.size()); - BOOST_TEST_EQ(raii::default_constructor, 0); + BOOST_TEST_EQ(raii::default_constructor, 0u); BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); - BOOST_TEST_GT(raii::move_constructor, 0); + BOOST_TEST_GT(raii::move_constructor, 0u); } } iterator_range_insert_or_visit_mut_visitor; @@ -422,10 +422,10 @@ namespace { })); } - 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_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + raii::move_constructor, @@ -482,17 +482,17 @@ namespace { })); } - 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_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); 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_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); } { @@ -525,17 +525,17 @@ namespace { })); } - 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_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); 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_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); } } From 6399dfd1e43fd7085e9f7af89168a44ddb3d5673 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 30 Mar 2023 10:08:38 +0200 Subject: [PATCH 121/327] made erase_if returned count exact --- include/boost/unordered/detail/foa/concurrent_table.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index fda9b7c8..8fdc41a8 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -564,16 +564,17 @@ public: template std::size_t erase_if(F&& f) { - auto lck=shared_access(); - std::size_t s=unprotected_size(); + auto lck=shared_access(); + std::size_t res=0; for_all_elements( group_exclusive{}, [&,this](group_type* pg,unsigned int n,element_type* p){ if(f(cast_for(group_exclusive{},type_policy::value_from(*p)))){ super::erase(pg,n,p); + ++res; } }); - return std::size_t(s-unprotected_size()); + return res; } #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) From f02afbc815db3a1ed500e7a8926a4fb883886140 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 30 Mar 2023 12:13:58 +0200 Subject: [PATCH 122/327] simplified scoped_bilock impl --- .../unordered/detail/foa/concurrent_table.hpp | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 8fdc41a8..63cdcb7a 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -54,6 +54,10 @@ struct alignas(64) cacheline_protected:T using T::T; }; +#if defined(BOOST_MSVC) +#pragma warning(pop) /* C4324 */ +#endif + template class multimutex { @@ -105,40 +109,37 @@ private: Mutex &m; }; -/* copied from boost/multi_index/detail/scoped_bilock.hpp */ +/* inspired by boost/multi_index/detail/scoped_bilock.hpp */ template class scoped_bilock { public: - scoped_bilock(Mutex& m1,Mutex& m2)noexcept:mutex_eq{&m1==&m2} + scoped_bilock(Mutex& m1,Mutex& m2)noexcept { bool mutex_lt=std::less{}(&m1,&m2); - ::new (static_cast(&storage1)) lock_guard_type(mutex_lt?m1:m2); - if(!mutex_eq) - ::new (static_cast(&storage2)) lock_guard_type(mutex_lt?m2:m1); + pm1=mutex_lt?&m1:&m2; + pm1->lock(); + if(&m1==&m2){ + pm2=nullptr; + } + else{ + pm2=mutex_lt?&m2:&m1; + pm2->lock(); + } } ~scoped_bilock()noexcept { - reinterpret_cast(&storage1)->~lock_guard_type(); - if(!mutex_eq) - reinterpret_cast(&storage2)->~lock_guard_type(); + if(pm2)pm2->unlock(); + pm1->unlock(); } private: - using lock_guard_type=lock_guard; - - bool mutex_eq; - alignas(lock_guard_type) unsigned char storage1[sizeof(lock_guard_type)], - storage2[sizeof(lock_guard_type)]; + Mutex *pm1,*pm2; }; -#if defined(BOOST_MSVC) -#pragma warning(pop) /* C4324 */ -#endif - /* TODO: describe atomic_integral and group_access */ template From 6a4728add0ba620f587e6ae086f3dea2193d76e3 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 30 Mar 2023 12:39:10 +0200 Subject: [PATCH 123/327] removed implementation of scoped_bilock copy ctor --- include/boost/unordered/detail/foa/concurrent_table.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 63cdcb7a..5ec0f346 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -94,7 +94,7 @@ private: bool owns=true; }; -/* VS in pre-C++17 mode has trouble returning std::lock_guard due to +/* VS in pre-C++17 mode can't implement RVO for std::lock_guard due to * its copy constructor being deleted. */ @@ -130,6 +130,9 @@ public: } } + /* not used but VS in pre-C++17 mode needs to see it for RVO */ + scoped_bilock(const scoped_bilock&); + ~scoped_bilock()noexcept { if(pm2)pm2->unlock(); From e86bb5cce28705916f8d55217797ded62c6b993f Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 30 Mar 2023 14:50:19 +0200 Subject: [PATCH 124/327] stylistic --- include/boost/unordered/detail/foa/concurrent_table.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 5ec0f346..55ec8519 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -791,13 +791,15 @@ private: * access is always const regardless of group access. */ - static inline const value_type& cast_for(group_shared,value_type& x) - {return x;} + static inline const value_type& + cast_for(group_shared,value_type& x){return x;} + static inline typename std::conditional< std::is_same::value, const value_type&, value_type& - >::type cast_for(group_exclusive,value_type& x){return x;} + >::type + cast_for(group_exclusive,value_type& x){return x;} struct erase_on_exit { From 7003e91d44958bac7cd32e2eece2fe07b4c9f79e Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 30 Mar 2023 11:43:12 -0700 Subject: [PATCH 125/327] Harden erase() tests --- test/cfoa/erase_tests.cpp | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/test/cfoa/erase_tests.cpp b/test/cfoa/erase_tests.cpp index 7dfdba2d..d673f4d3 100644 --- a/test/cfoa/erase_tests.cpp +++ b/test/cfoa/erase_tests.cpp @@ -15,14 +15,37 @@ namespace { { template void operator()(std::vector& values, X& x) { - thread_runner(values, [&values, &x](boost::span) { + std::atomic num_erased{0}; + auto const old_size = x.size(); + + auto const old_dc = +raii::default_constructor; + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + auto const old_d = +raii::destructor; + + BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + + raii::move_constructor, + raii::destructor + 2 * x.size()); + + thread_runner(values, [&values, &num_erased, &x](boost::span) { for (auto const& k : values) { - x.erase(k.first); + auto count = x.erase(k.first); + num_erased += count; + BOOST_TEST_LE(count, 1u); + BOOST_TEST_GE(count, 0u); } }); + BOOST_TEST_EQ(raii::default_constructor, old_dc); + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + + BOOST_TEST_EQ(raii::destructor, old_d + 2 * old_size); + BOOST_TEST_EQ(x.size(), 0u); BOOST_TEST(x.empty()); + BOOST_TEST_EQ(num_erased, old_size); } } lvalue_eraser; From af4cdf8fab3f3f5cb68f5b0dde38bc8f862370be Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 30 Mar 2023 12:41:18 -0700 Subject: [PATCH 126/327] Shorten "transparent" to "transp" --- test/cfoa/helpers.hpp | 4 ++-- test/cfoa/insert_tests.cpp | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index 45d9af40..8fb4fdd3 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -16,7 +16,7 @@ constexpr std::size_t const num_threads = 16; -struct transparent_hash +struct transp_hash { using is_transparent = void; @@ -26,7 +26,7 @@ struct transparent_hash } }; -struct transparent_key_equal +struct transp_key_equal { using is_transparent = void; diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index eda91b9f..8b26a51f 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -175,7 +175,7 @@ namespace { } } rvalue_insert_or_assign_move_assign; - struct transparent_insert_or_assign_copy_assign_type + struct trans_insert_or_assign_copy_assign_type { template void operator()(std::vector& values, X& x) { @@ -199,9 +199,9 @@ namespace { BOOST_TEST_EQ(raii::copy_assignment, values.size() - x.size()); BOOST_TEST_EQ(raii::move_assignment, 0u); } - } transparent_insert_or_assign_copy_assign; + } trans_insert_or_assign_copy_assign; - struct transparent_insert_or_assign_move_assign_type + struct trans_insert_or_assign_move_assign_type { template void operator()(std::vector& values, X& x) { @@ -223,7 +223,7 @@ namespace { BOOST_TEST_EQ(raii::copy_assignment, 0u); BOOST_TEST_EQ(raii::move_assignment, values.size() - x.size()); } - } transparent_insert_or_assign_move_assign; + } trans_insert_or_assign_move_assign; struct lvalue_insert_or_visit_const_visitor_type { @@ -540,8 +540,8 @@ namespace { } boost::unordered::concurrent_flat_map* map; - boost::unordered::concurrent_flat_map* transparent_map; + boost::unordered::concurrent_flat_map* trans_map; } // namespace @@ -575,9 +575,9 @@ UNORDERED_TEST( UNORDERED_TEST( insert, - ((transparent_map)) + ((trans_map)) ((init_type_generator)) - ((transparent_insert_or_assign_copy_assign)(transparent_insert_or_assign_move_assign)) + ((trans_insert_or_assign_copy_assign)(trans_insert_or_assign_move_assign)) ((default_generator)(sequential)(limited_range))) // clang-format on From 53e20a2a1bb31efadf5ac680bbe2193c7c2a0bf1 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 30 Mar 2023 12:41:31 -0700 Subject: [PATCH 127/327] Add transparent impl of erase() --- .../boost/unordered/concurrent_flat_map.hpp | 8 +++ test/cfoa/erase_tests.cpp | 49 ++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index ccc028ba..7a2095c5 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -216,6 +216,14 @@ namespace boost { size_type erase(key_type const& k) { return table_.erase(k); } + template + typename std::enable_if< + detail::are_transparent::value, size_type>::type + erase(K&& k) + { + return table_.erase(std::forward(k)); + } + /// Hash Policy /// void rehash(size_type n) { table_.rehash(n); } diff --git a/test/cfoa/erase_tests.cpp b/test/cfoa/erase_tests.cpp index d673f4d3..80573776 100644 --- a/test/cfoa/erase_tests.cpp +++ b/test/cfoa/erase_tests.cpp @@ -49,6 +49,44 @@ namespace { } } lvalue_eraser; + struct transp_lvalue_eraser_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_erased{0}; + auto const old_size = x.size(); + + auto const old_dc = +raii::default_constructor; + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + auto const old_d = +raii::destructor; + + BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + + raii::move_constructor, + raii::destructor + 2 * x.size()); + + thread_runner(values, [&values, &num_erased, &x](boost::span) { + for (auto const& k : values) { + auto count = x.erase(k.first.x_); + num_erased += count; + BOOST_TEST_LE(count, 1u); + BOOST_TEST_GE(count, 0u); + } + }); + + BOOST_TEST_EQ(raii::default_constructor, old_dc); + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + + BOOST_TEST_EQ(raii::destructor, old_d + 2 * old_size); + + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST(x.empty()); + BOOST_TEST_EQ(num_erased, old_size); + } + } transp_lvalue_eraser; + template void erase(X*, G gen, F eraser, test::random_generator rg) { @@ -86,8 +124,8 @@ namespace { } boost::unordered::concurrent_flat_map* map; - // boost::unordered::concurrent_flat_map* transparent_map; + boost::unordered::concurrent_flat_map* transparent_map; } // namespace @@ -103,6 +141,13 @@ UNORDERED_TEST( ((lvalue_eraser)) ((default_generator)(sequential)(limited_range))) +UNORDERED_TEST( + erase, + ((transparent_map)) + ((value_type_generator)(init_type_generator)) + ((lvalue_eraser)(transp_lvalue_eraser)) + ((default_generator)(sequential)(limited_range))) + // clang-format on RUN_TESTS() From e00d700457e55815745c22fc4459852a1c686b9c Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 31 Mar 2023 11:32:29 +0200 Subject: [PATCH 128/327] experiment with unaligned embedded group --- include/boost/unordered/detail/foa/core.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 79d8313d..df08a10a 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -180,7 +180,7 @@ struct group15 struct dummy_group_type { - alignas(16) unsigned char storage[N+1]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0}; + /*alignas(16)*/ unsigned char storage[N+1]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0}; }; inline void initialize() @@ -289,7 +289,7 @@ private: (char)m[ 7],(char)m[ 6],(char)m[ 5],(char)m[ 4], (char)m[ 3],(char)m[ 2],(char)m[ 1],(char)m[ 0]); #else - return _mm_load_si128(reinterpret_cast(m)); + return /*_mm_load_si128*/_mm_loadu_si128(reinterpret_cast(m)); #endif } @@ -359,7 +359,7 @@ private: return at(N); } - alignas(16) slot_type m[16]; + /*alignas(16)*/ slot_type m[16]; }; #elif defined(BOOST_UNORDERED_LITTLE_ENDIAN_NEON) From e9b3ad4a5fe2e9e13d227eff7db93bbff2794b0c Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 31 Mar 2023 13:07:02 +0200 Subject: [PATCH 129/327] reverted --- include/boost/unordered/detail/foa/core.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index df08a10a..79d8313d 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -180,7 +180,7 @@ struct group15 struct dummy_group_type { - /*alignas(16)*/ unsigned char storage[N+1]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0}; + alignas(16) unsigned char storage[N+1]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0}; }; inline void initialize() @@ -289,7 +289,7 @@ private: (char)m[ 7],(char)m[ 6],(char)m[ 5],(char)m[ 4], (char)m[ 3],(char)m[ 2],(char)m[ 1],(char)m[ 0]); #else - return /*_mm_load_si128*/_mm_loadu_si128(reinterpret_cast(m)); + return _mm_load_si128(reinterpret_cast(m)); #endif } @@ -359,7 +359,7 @@ private: return at(N); } - /*alignas(16)*/ slot_type m[16]; + alignas(16) slot_type m[16]; }; #elif defined(BOOST_UNORDERED_LITTLE_ENDIAN_NEON) From 863984a7c84843e5c1d7cf2ad421a6abd54c2a81 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 31 Mar 2023 17:28:21 +0200 Subject: [PATCH 130/327] fixed erase_if(x,f) return value calculation --- include/boost/unordered/detail/foa/concurrent_table.hpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 55ec8519..f3f136e2 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -553,16 +553,19 @@ public: BOOST_FORCEINLINE auto erase_if(Key&& x,F&& f)->typename std::enable_if< !is_execution_policy::value,std::size_t>::type { - auto lck=shared_access(); - auto hash=this->hash_for(x); - return (std::size_t)unprotected_internal_visit( + auto lck=shared_access(); + auto hash=this->hash_for(x); + std::size_t res=0; + unprotected_internal_visit( group_exclusive{},x,this->position_for(hash),hash, [&,this](group_type* pg,unsigned int n,element_type* p) { if(f(cast_for(group_exclusive{},type_policy::value_from(*p)))){ super::erase(pg,n,p); + res=1; } }); + return res; } template From b86dee9a18b9cee30b0afd02f3a93b611a24542a Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 31 Mar 2023 18:52:00 +0200 Subject: [PATCH 131/327] removed BOOST_UNORDERED_EMBEDDED_GROUP_ACCESS support --- .../unordered/detail/foa/concurrent_table.hpp | 95 +++++-------------- include/boost/unordered/detail/foa/core.hpp | 7 +- 2 files changed, 24 insertions(+), 78 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index f3f136e2..e61b8fc9 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -156,13 +156,13 @@ struct atomic_integral std::atomic n; }; +/* Group-level concurrency protection. It provides a rw mutex plus an + * atomic insertion counter for optimistic insertion (see + * unprotected_norehash_emplace_or_visit). + */ + struct group_access { - struct dummy_group_access_type - { - boost::uint32_t storage[2]={0,0}; - }; - using mutex_type=rw_spinlock; using shared_lock_guard=shared_lock; using exclusive_lock_guard=lock_guard; @@ -174,31 +174,25 @@ struct group_access private: mutex_type m; - insert_counter_type cnt; -}; - -template -struct concurrent_group:Group,group_access -{ - struct dummy_group_type - { - typename Group::dummy_group_type group_storage; - group_access::dummy_group_access_type access_storage; - }; + insert_counter_type cnt=0; }; template group_access* dummy_group_accesses() { - /* TODO: describe + /* Default group_access array to provide to empty containers without + * incurring dynamic allocation. Mutexes won't actually ever be used, + * (no successful reduced hash match) and insertion counters won't ever + * be incremented (insertions won't succeed as capacity()==0). */ - static group_access::dummy_group_access_type - storage[Size]={typename group_access::dummy_group_access_type(),}; + static group_access accesses[Size]; - return reinterpret_cast(storage); + return accesses; } +/* subclasses table_arrays to add an additional group_access array */ + template struct concurrent_table_arrays:table_arrays { @@ -225,7 +219,7 @@ struct concurrent_table_arrays:table_arrays access_traits::allocate(aal,arrays.groups_size_mask+1)); for(std::size_t i=0;i template using concurrent_table_core_impl=table_core< - TypePolicy, - -#if defined(BOOST_UNORDERED_EMBEDDED_GROUP_ACCESS) - concurrent_group>, - table_arrays, -#else - group15, - concurrent_table_arrays, -#endif - + TypePolicy,group15,concurrent_table_arrays, std::atomic,Hash,Pred,Allocator>; #include @@ -701,16 +686,9 @@ private: using shared_lock_guard=shared_lock; using exclusive_lock_guard=lock_guard; using exclusive_bilock_guard=scoped_bilock; - -#if defined(BOOST_UNORDERED_EMBEDDED_GROUP_ACCESS) - using group_shared_lock_guard=typename group_type::shared_lock_guard; - using group_exclusive_lock_guard=typename group_type::exclusive_lock_guard; - using group_insert_counter_type=typename group_type::insert_counter_type; -#else using group_shared_lock_guard=typename group_access::shared_lock_guard; using group_exclusive_lock_guard=typename group_access::exclusive_lock_guard; using group_insert_counter_type=typename group_access::insert_counter_type; -#endif concurrent_table(const concurrent_table& x,exclusive_lock_guard): super{x}{} @@ -741,38 +719,6 @@ private: return {x.mutexes,y.mutexes}; } -#if defined(BOOST_UNORDERED_EMBEDDED_GROUP_ACCESS) - inline group_shared_lock_guard shared_access(std::size_t pos)const - { - return this->arrays.groups[pos].shared_access(); - } - - inline group_exclusive_lock_guard exclusive_access(std::size_t pos)const - { - return this->arrays.groups[pos].exclusive_access(); - } - - inline group_insert_counter_type& insert_counter(std::size_t pos)const - { - return this->arrays.groups[pos].insert_counter(); - } -#else - inline group_shared_lock_guard shared_access(std::size_t pos)const - { - return this->arrays.group_accesses[pos].shared_access(); - } - - inline group_exclusive_lock_guard exclusive_access(std::size_t pos)const - { - return this->arrays.group_accesses[pos].exclusive_access(); - } - - inline group_insert_counter_type& insert_counter(std::size_t pos)const - { - return this->arrays.group_accesses[pos].insert_counter(); - } -#endif - /* Tag-dispatched shared/exclusive group access */ using group_shared=std::false_type; @@ -780,13 +726,18 @@ private: inline group_shared_lock_guard access(group_shared,std::size_t pos)const { - return shared_access(pos); + return this->arrays.group_accesses[pos].shared_access(); } inline group_exclusive_lock_guard access( group_exclusive,std::size_t pos)const { - return exclusive_access(pos); + return this->arrays.group_accesses[pos].exclusive_access(); + } + + inline group_insert_counter_type& insert_counter(std::size_t pos)const + { + return this->arrays.group_accesses[pos].insert_counter(); } /* Const casts value_type& according to the level of group access for diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 79d8313d..f672d0c3 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -937,15 +937,10 @@ struct table_arrays reinterpret_cast(p))%sizeof(group_type); arrays.groups=reinterpret_cast(p); - for (std::size_t i=0;i Date: Fri, 31 Mar 2023 19:36:31 +0200 Subject: [PATCH 132/327] use memset for group array initialization only when permissible --- include/boost/unordered/detail/foa/core.hpp | 35 +++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index f672d0c3..acbbc053 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -163,11 +163,12 @@ static constexpr std::size_t default_bucket_count=0; * "logical" 128-bit word, and so forth. With this layout, match can be * implemented with 4 ANDs, 3 shifts, 2 XORs, 1 OR and 1 NOT. * - * TODO: Explain IntegralWrapper. - * - * group15 has no user-defined ctor so that it's a trivial type and can be - * initialized via memset etc. Where needed, group15::initialize sets the - * metadata to all zeros. + * IntegralWrapper is used to implement group15's underlying + * metadata: it behaves as a plain integral for foa::table or introduces + * atomic ops for foa::concurrent_table. If IntegralWrapper<...> is trivially + * constructible, so is group15, in which case it can be initialized via memset + * etc. Where needed, group15::initialize resets the metadata to the all + * zeros (default state). */ #if defined(BOOST_UNORDERED_SSE2) @@ -937,10 +938,10 @@ struct table_arrays reinterpret_cast(p))%sizeof(group_type); arrays.groups=reinterpret_cast(p); - /* memset is faster/not slower than initializing groups individually. - * This assumes all zeros is group_type's default layout. - */ - std::memset(arrays.groups,0,sizeof(group_type)*groups_size); + initialize_groups( + arrays.groups,groups_size, + std::integral_constant< + bool,std::is_trivially_constructible::value>{}); arrays.groups[groups_size-1].set_sentinel(); } return arrays; @@ -976,6 +977,22 @@ struct table_arrays return (buffer_bytes+sizeof(value_type)-1)/sizeof(value_type); } + static void initialize_groups( + group_type* groups_,std::size_t size,std::true_type /* memset */) + { + /* Faster/not slower than manual, assumes all zeros is group_type's + * default layout. + */ + + std::memset(groups_,0,sizeof(group_type)*size); + } + + static void initialize_groups( + group_type* groups_,std::size_t size,std::false_type /* manual */) + { + while(size--!=0)::new (groups_++) group_type(); + } + std::size_t groups_size_index; std::size_t groups_size_mask; group_type *groups; From 3be466966d989cc8d29bf8e11229e2e7a1590e7d Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 31 Mar 2023 19:46:59 +0200 Subject: [PATCH 133/327] avoided copy elision for the initialization of an atomic --- include/boost/unordered/detail/foa/concurrent_table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index e61b8fc9..bcece6af 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -174,7 +174,7 @@ struct group_access private: mutex_type m; - insert_counter_type cnt=0; + insert_counter_type cnt{0}; }; template From 19202d2a8d124d236a398774570d67a32934fc47 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 1 Apr 2023 09:09:22 +0200 Subject: [PATCH 134/327] avoided memset-related GCC warning --- include/boost/unordered/detail/foa/core.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index acbbc053..a7e73c85 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -980,11 +980,14 @@ struct table_arrays static void initialize_groups( group_type* groups_,std::size_t size,std::true_type /* memset */) { - /* Faster/not slower than manual, assumes all zeros is group_type's + /* memset faster/not slower than manual, assumes all zeros is group_type's * default layout. + * reinterpret_cast: GCC may complain about group_type not being trivially + * copy-assignable when we're relying on trivial copy constructibility. */ - std::memset(groups_,0,sizeof(group_type)*size); + std::memset( + reinterpret_cast(groups_),0,sizeof(group_type)*size); } static void initialize_groups( From 946491489e967f847f9742a349ca410cb0f22c13 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 1 Apr 2023 12:06:39 +0200 Subject: [PATCH 135/327] added workaround for lack of std::is_trivially_constructible in GCC<5.0 --- include/boost/unordered/detail/foa/core.hpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index a7e73c85..b7c546c1 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -941,7 +942,14 @@ struct table_arrays initialize_groups( arrays.groups,groups_size, std::integral_constant< - bool,std::is_trivially_constructible::value>{}); + bool, +#if BOOST_WORKAROUND(BOOST_LIBSTDCXX_VERSION,<50000) + /* std::is_trivially_constructible not provided */ + boost::has_trivial_constructor::value +#else + std::is_trivially_constructible::value +#endif + >{}); arrays.groups[groups_size-1].set_sentinel(); } return arrays; From 1309361a02fc317b0763e402beb1d50b8d1d6c26 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 31 Mar 2023 13:20:03 -0700 Subject: [PATCH 136/327] Remove erroneous move() calls --- include/boost/unordered/concurrent_flat_map.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 7a2095c5..6923e879 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -152,7 +152,7 @@ namespace boost { template std::size_t visit_all(F f) { - return table_.visit_all(std::move(f)); + return table_.visit_all(f); } template bool insert_or_assign(key_type const& k, M&& obj) @@ -182,22 +182,22 @@ namespace boost { template bool insert_or_visit(value_type const& obj, F f) { - return table_.insert_or_visit(obj, std::move(f)); + return table_.insert_or_visit(obj, f); } template bool insert_or_visit(value_type&& obj, F f) { - return table_.insert_or_visit(std::move(obj), std::move(f)); + return table_.insert_or_visit(std::move(obj), f); } template bool insert_or_visit(init_type const& obj, F f) { - return table_.insert_or_visit(obj, std::move(f)); + return table_.insert_or_visit(obj, f); } template bool insert_or_visit(init_type&& obj, F f) { - return table_.insert_or_visit(std::move(obj), std::move(f)); + return table_.insert_or_visit(std::move(obj), f); } template @@ -211,7 +211,7 @@ namespace boost { template void insert_or_visit(std::initializer_list ilist, F f) { - this->insert_or_visit(ilist.begin(), ilist.end(), std::move(f)); + this->insert_or_visit(ilist.begin(), ilist.end(), f); } size_type erase(key_type const& k) { return table_.erase(k); } From cf39fc4c383e8edbf21df3293ec8906c8ae8693c Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 31 Mar 2023 13:20:27 -0700 Subject: [PATCH 137/327] Implement transparent erase_if, unary erase_if --- .../boost/unordered/concurrent_flat_map.hpp | 15 ++ test/cfoa/erase_tests.cpp | 168 +++++++++++++++++- 2 files changed, 178 insertions(+), 5 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 6923e879..0e9c0f96 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -224,6 +224,21 @@ namespace boost { return table_.erase(std::forward(k)); } + template size_type erase_if(key_type const& k, F f) + { + return table_.erase_if(k, f); + } + + template + typename std::enable_if< + detail::are_transparent::value, size_type>::type + erase_if(K&& k, F f) + { + return table_.erase_if(std::forward(k), f); + } + + template size_type erase_if(F f) { return table_.erase_if(f); } + /// Hash Policy /// void rehash(size_type n) { table_.rehash(n); } diff --git a/test/cfoa/erase_tests.cpp b/test/cfoa/erase_tests.cpp index 80573776..fdcc1833 100644 --- a/test/cfoa/erase_tests.cpp +++ b/test/cfoa/erase_tests.cpp @@ -66,8 +66,8 @@ namespace { raii::move_constructor, raii::destructor + 2 * x.size()); - thread_runner(values, [&values, &num_erased, &x](boost::span) { - for (auto const& k : values) { + thread_runner(values, [&num_erased, &x](boost::span s) { + for (auto const& k : s) { auto count = x.erase(k.first.x_); num_erased += count; BOOST_TEST_LE(count, 1u); @@ -79,7 +79,7 @@ namespace { BOOST_TEST_EQ(raii::copy_constructor, old_cc); BOOST_TEST_EQ(raii::move_constructor, old_mc); - BOOST_TEST_EQ(raii::destructor, old_d + 2 * old_size); + BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased); BOOST_TEST_EQ(x.size(), 0u); BOOST_TEST(x.empty()); @@ -87,6 +87,164 @@ namespace { } } transp_lvalue_eraser; + struct lvalue_eraser_if_type + { + template void operator()(std::vector& values, X& x) + { + using value_type = typename X::value_type; + + std::atomic num_erased{0}; + + auto const old_size = x.size(); + + auto const old_dc = +raii::default_constructor; + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + auto const old_d = +raii::destructor; + + auto max = 0; + x.visit_all([&max](value_type const& v) { + if (v.second.x_ > max) { + max = v.second.x_; + } + }); + + auto threshold = max / 2; + + auto expected_erasures = 0u; + x.visit_all([&expected_erasures, threshold](value_type const& v) { + if (v.second.x_ > threshold) { + ++expected_erasures; + } + }); + + thread_runner(values, [&num_erased, &x, threshold](boost::span s) { + for (auto const& k : s) { + auto count = x.erase_if(k.first, + [threshold](value_type& v) { return v.second.x_ > threshold; }); + num_erased += count; + BOOST_TEST_LE(count, 1u); + BOOST_TEST_GE(count, 0u); + } + }); + + BOOST_TEST_EQ(num_erased, expected_erasures); + BOOST_TEST_EQ(x.size(), old_size - num_erased); + + BOOST_TEST_EQ(raii::default_constructor, old_dc); + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + + BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased); + } + } lvalue_eraser_if; + + struct transp_lvalue_eraser_if_type + { + template void operator()(std::vector& values, X& x) + { + using value_type = typename X::value_type; + + std::atomic num_erased{0}; + + auto const old_size = x.size(); + + auto const old_dc = +raii::default_constructor; + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + auto const old_d = +raii::destructor; + + auto max = 0; + x.visit_all([&max](value_type const& v) { + if (v.second.x_ > max) { + max = v.second.x_; + } + }); + + auto threshold = max / 2; + + auto expected_erasures = 0u; + x.visit_all([&expected_erasures, threshold](value_type const& v) { + if (v.second.x_ > threshold) { + ++expected_erasures; + } + }); + + thread_runner(values, [&num_erased, &x, threshold](boost::span s) { + for (auto const& k : s) { + auto count = x.erase_if(k.first.x_, + [threshold](value_type& v) { return v.second.x_ > threshold; }); + num_erased += count; + BOOST_TEST_LE(count, 1u); + BOOST_TEST_GE(count, 0u); + } + }); + + BOOST_TEST_EQ(num_erased, expected_erasures); + BOOST_TEST_EQ(x.size(), old_size - num_erased); + + BOOST_TEST_EQ(raii::default_constructor, old_dc); + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + + BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased); + } + } transp_lvalue_eraser_if; + + struct erase_if_type + { + template void operator()(std::vector& values, X& x) + { + using value_type = typename X::value_type; + + std::atomic num_erased{0}; + + auto const old_size = x.size(); + + auto const old_dc = +raii::default_constructor; + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + auto const old_d = +raii::destructor; + + auto max = 0; + x.visit_all([&max](value_type const& v) { + if (v.second.x_ > max) { + max = v.second.x_; + } + }); + + auto threshold = max / 2; + + auto expected_erasures = 0u; + x.visit_all([&expected_erasures, threshold](value_type const& v) { + if (v.second.x_ > threshold) { + ++expected_erasures; + } + }); + + thread_runner(values, [&num_erased, &x, threshold](boost::span s) { + for (auto const& k : s) { + (void)k; + auto count = x.erase_if( + [threshold](value_type& v) { return v.second.x_ > threshold; }); + num_erased += count; + } + }); + + BOOST_TEST_EQ(num_erased, expected_erasures); + BOOST_TEST_EQ(x.size(), old_size - num_erased); + + BOOST_TEST_EQ(raii::default_constructor, old_dc); + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + + BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased); + } + } erase_if; + template void erase(X*, G gen, F eraser, test::random_generator rg) { @@ -138,14 +296,14 @@ UNORDERED_TEST( erase, ((map)) ((value_type_generator)(init_type_generator)) - ((lvalue_eraser)) + ((lvalue_eraser)(lvalue_eraser_if)(erase_if)) ((default_generator)(sequential)(limited_range))) UNORDERED_TEST( erase, ((transparent_map)) ((value_type_generator)(init_type_generator)) - ((lvalue_eraser)(transp_lvalue_eraser)) + ((transp_lvalue_eraser)(transp_lvalue_eraser_if)) ((default_generator)(sequential)(limited_range))) // clang-format on From 52bf2bf7a29c844782edfaddd57e0812bd8dfa36 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 31 Mar 2023 14:25:09 -0700 Subject: [PATCH 138/327] Add insert_or_cvisit() overloads --- .../boost/unordered/concurrent_flat_map.hpp | 59 +++++++++++++++ test/cfoa/insert_tests.cpp | 74 +++++++++---------- 2 files changed, 96 insertions(+), 37 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 0e9c0f96..92afca7e 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,15 @@ #include #include +#define BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) \ + static_assert(boost::callable_traits::is_invocable::value, \ + "The provided Callable must be invocable with `value_type&`"); + +#define BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) \ + static_assert( \ + boost::callable_traits::is_invocable::value, \ + "The provided Callable must be invocable with `value_type const&`"); + namespace boost { namespace unordered { namespace detail { @@ -182,27 +192,32 @@ namespace boost { template bool insert_or_visit(value_type const& obj, F f) { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.insert_or_visit(obj, f); } template bool insert_or_visit(value_type&& obj, F f) { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.insert_or_visit(std::move(obj), f); } template bool insert_or_visit(init_type const& obj, F f) { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.insert_or_visit(obj, f); } template bool insert_or_visit(init_type&& obj, F f) { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.insert_or_visit(std::move(obj), f); } template void insert_or_visit(InputIterator first, InputIterator last, F f) { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) for (; first != last; ++first) { table_.insert_or_visit(*first, f); } @@ -211,6 +226,47 @@ namespace boost { template void insert_or_visit(std::initializer_list ilist, F f) { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + this->insert_or_visit(ilist.begin(), ilist.end(), f); + } + + template bool insert_or_cvisit(value_type const& obj, F f) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.insert_or_cvisit(obj, f); + } + + template bool insert_or_cvisit(value_type&& obj, F f) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.insert_or_cvisit(std::move(obj), f); + } + + template bool insert_or_cvisit(init_type const& obj, F f) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.insert_or_cvisit(obj, f); + } + + template bool insert_or_cvisit(init_type&& obj, F f) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.insert_or_cvisit(std::move(obj), f); + } + + template + void insert_or_cvisit(InputIterator first, InputIterator last, F f) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + for (; first != last; ++first) { + table_.insert_or_cvisit(*first, f); + } + } + + template + void insert_or_cvisit(std::initializer_list ilist, F f) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) this->insert_or_visit(ilist.begin(), ilist.end(), f); } @@ -247,4 +303,7 @@ namespace boost { } // namespace unordered } // namespace boost +#undef BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE +#undef BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE + #endif // BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP \ No newline at end of file diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index 8b26a51f..d4dacc11 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -225,7 +225,7 @@ namespace { } } trans_insert_or_assign_move_assign; - struct lvalue_insert_or_visit_const_visitor_type + struct lvalue_insert_or_cvisit_type { template void operator()(std::vector& values, X& x) { @@ -233,7 +233,7 @@ namespace { std::atomic num_invokes{0}; thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { for (auto& r : s) { - bool b = x.insert_or_visit( + bool b = x.insert_or_cvisit( r, [&num_invokes](typename X::value_type const& v) { (void)v; ++num_invokes; @@ -254,9 +254,9 @@ namespace { BOOST_TEST_GT(raii::move_constructor, 0u); BOOST_TEST_EQ(raii::move_assignment, 0u); } - } lvalue_insert_or_visit_const_visitor; + } lvalue_insert_or_cvisit; - struct lvalue_insert_or_visit_mut_visitor_type + struct lvalue_insert_or_visit_type { template void operator()(std::vector& values, X& x) { @@ -285,9 +285,9 @@ namespace { BOOST_TEST_GT(raii::move_constructor, 0u); BOOST_TEST_EQ(raii::move_assignment, 0u); } - } lvalue_insert_or_visit_mut_visitor; + } lvalue_insert_or_visit; - struct rvalue_insert_or_visit_const_visitor_type + struct rvalue_insert_or_cvisit_type { template void operator()(std::vector& values, X& x) { @@ -295,7 +295,7 @@ namespace { std::atomic num_invokes{0}; thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { for (auto& r : s) { - bool b = x.insert_or_visit( + bool b = x.insert_or_cvisit( std::move(r), [&num_invokes](typename X::value_type const& v) { (void)v; ++num_invokes; @@ -320,9 +320,9 @@ namespace { BOOST_TEST_GE(raii::move_constructor, 2 * x.size()); } } - } rvalue_insert_or_visit_const_visitor; + } rvalue_insert_or_cvisit; - struct rvalue_insert_or_visit_mut_visitor_type + struct rvalue_insert_or_visit_type { template void operator()(std::vector& values, X& x) { @@ -354,9 +354,30 @@ namespace { BOOST_TEST_GE(raii::move_constructor, 2 * x.size()); } } - } rvalue_insert_or_visit_mut_visitor; + } rvalue_insert_or_visit; - struct iterator_range_insert_or_visit_const_visitor_type + struct iterator_range_insert_or_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_invokes](boost::span s) { + x.insert_or_cvisit( + s.begin(), s.end(), [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + }); + + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); + BOOST_TEST_GT(raii::move_constructor, 0u); + } + } iterator_range_insert_or_cvisit; + + struct iterator_range_insert_or_visit_type { template void operator()(std::vector& values, X& x) { @@ -375,28 +396,7 @@ namespace { BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); BOOST_TEST_GT(raii::move_constructor, 0u); } - } iterator_range_insert_or_visit_const_visitor; - - struct iterator_range_insert_or_visit_mut_visitor_type - { - template void operator()(std::vector& values, X& x) - { - std::atomic num_invokes{0}; - thread_runner(values, [&x, &num_invokes](boost::span s) { - x.insert_or_visit( - s.begin(), s.end(), [&num_invokes](typename X::value_type const& v) { - (void)v; - ++num_invokes; - }); - }); - - BOOST_TEST_EQ(num_invokes, values.size() - x.size()); - - BOOST_TEST_EQ(raii::default_constructor, 0u); - BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); - BOOST_TEST_GT(raii::move_constructor, 0u); - } - } iterator_range_insert_or_visit_mut_visitor; + } iterator_range_insert_or_visit; template void insert(X*, G gen, F inserter, test::random_generator rg) @@ -507,7 +507,7 @@ namespace { ++num_invokes; }); - x.insert_or_visit( + x.insert_or_cvisit( values, [&num_invokes](typename X::value_type const& v) { (void)v; ++num_invokes; @@ -560,9 +560,9 @@ UNORDERED_TEST( ((value_type_generator)(init_type_generator)) ((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter) (norehash_lvalue_inserter)(norehash_rvalue_inserter) - (lvalue_insert_or_visit_const_visitor)(lvalue_insert_or_visit_mut_visitor) - (rvalue_insert_or_visit_const_visitor)(rvalue_insert_or_visit_mut_visitor) - (iterator_range_insert_or_visit_const_visitor)(iterator_range_insert_or_visit_mut_visitor)) + (lvalue_insert_or_cvisit)(lvalue_insert_or_visit) + (rvalue_insert_or_cvisit)(rvalue_insert_or_visit) + (iterator_range_insert_or_cvisit)(iterator_range_insert_or_visit)) ((default_generator)(sequential)(limited_range))) UNORDERED_TEST( From f8fbbc3b76ffc4c85b5a90eed9d92990ffe361ba Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 3 Apr 2023 13:15:15 -0700 Subject: [PATCH 139/327] Add ExecutionPolicy overload of erase_if --- .../unordered/detail/foa/concurrent_table.hpp | 31 ++++++---- test/cfoa/erase_tests.cpp | 61 ++++++++++++++++++- 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index bcece6af..2461bbad 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -41,6 +41,23 @@ namespace boost{ namespace unordered{ namespace detail{ + +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) + +template +using is_execution_policy=std::is_execution_policy< + typename std::remove_cv< + typename std::remove_reference::type + >::type +>; + +#else + +template +using is_execution_policy=std::false_type; + +#endif + namespace foa{ #if defined(BOOST_MSVC) @@ -277,18 +294,6 @@ class concurrent_table: using super::N; using prober=typename super::prober; -#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) - template - using is_execution_policy=std::is_execution_policy< - typename std::remove_cv< - typename std::remove_reference::type - >::type - >; -#else - template - using is_execution_policy=std::false_type; -#endif - public: using key_type=typename super::key_type; using init_type=typename super::init_type; @@ -1060,7 +1065,7 @@ private: last=first+this->arrays.groups_size_mask+1; std::for_each(std::forward(policy),first,last, [&,this](group_type& g){ - std::size_t pos=&g-first; + std::size_t pos=static_cast(&g-first); auto p=this->arrays.elements+pos*N; auto lck=access(access_mode,pos); auto mask=this->match_really_occupied(&g,last); diff --git a/test/cfoa/erase_tests.cpp b/test/cfoa/erase_tests.cpp index fdcc1833..2066e07e 100644 --- a/test/cfoa/erase_tests.cpp +++ b/test/cfoa/erase_tests.cpp @@ -245,6 +245,63 @@ namespace { } } erase_if; + struct erase_if_exec_policy_type + { + template void operator()(std::vector& values, X& x) + { +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) + using value_type = typename X::value_type; + + std::atomic num_invokes{0}; + + auto const old_size = x.size(); + + auto const old_dc = +raii::default_constructor; + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + auto const old_d = +raii::destructor; + + auto max = 0; + x.visit_all([&max](value_type const& v) { + if (v.second.x_ > max) { + max = v.second.x_; + } + }); + + auto threshold = max / 2; + + auto expected_erasures = 0u; + x.visit_all([&expected_erasures, threshold](value_type const& v) { + if (v.second.x_ > threshold) { + ++expected_erasures; + } + }); + + thread_runner(values, [&num_invokes, &x, threshold](boost::span s) { + (void)s; + x.erase_if( + std::execution::par_unseq, [&num_invokes, threshold](value_type& v) { + ++num_invokes; + return v.second.x_ > threshold; + }); + }); + + BOOST_TEST_GE(+num_invokes, old_size); + BOOST_TEST_LE(+num_invokes, old_size * num_threads); + + BOOST_TEST_EQ(raii::default_constructor, old_dc); + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + + BOOST_TEST_EQ(raii::destructor, old_d + 2 * expected_erasures); +#else + (void)values; + (void)x; +#endif + } + } erase_if_exec_policy; + template void erase(X*, G gen, F eraser, test::random_generator rg) { @@ -296,14 +353,14 @@ UNORDERED_TEST( erase, ((map)) ((value_type_generator)(init_type_generator)) - ((lvalue_eraser)(lvalue_eraser_if)(erase_if)) + ((lvalue_eraser)(lvalue_eraser_if)(erase_if)(erase_if_exec_policy)) ((default_generator)(sequential)(limited_range))) UNORDERED_TEST( erase, ((transparent_map)) ((value_type_generator)(init_type_generator)) - ((transp_lvalue_eraser)(transp_lvalue_eraser_if)) + ((transp_lvalue_eraser)(transp_lvalue_eraser_if)(erase_if_exec_policy)) ((default_generator)(sequential)(limited_range))) // clang-format on From e3cbf03f471df0267b3423965b4d6952052dbed1 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 3 Apr 2023 14:11:51 -0700 Subject: [PATCH 140/327] Commit missing changes to erase_if() --- include/boost/unordered/concurrent_flat_map.hpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 92afca7e..1caf3a6c 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -287,12 +287,24 @@ namespace boost { template typename std::enable_if< - detail::are_transparent::value, size_type>::type + detail::are_transparent::value && + !detail::is_execution_policy::value, + size_type>::type erase_if(K&& k, F f) { return table_.erase_if(std::forward(k), f); } +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) + template + typename std::enable_if::value, + void>::type + erase_if(ExecPolicy p, F f) + { + table_.erase_if(p, f); + } +#endif + template size_type erase_if(F f) { return table_.erase_if(f); } /// Hash Policy From ddcab1c171f43835c2c49e33ec2611c28817a263 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 4 Apr 2023 15:16:52 -0700 Subject: [PATCH 141/327] Add impl of try_emplace, try_emplace_or_[c]visit --- .../boost/unordered/concurrent_flat_map.hpp | 60 +++ test/Jamfile.v2 | 7 +- test/cfoa/try_emplace_tests.cpp | 402 ++++++++++++++++++ 3 files changed, 468 insertions(+), 1 deletion(-) create mode 100644 test/cfoa/try_emplace_tests.cpp diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 1caf3a6c..bbab9d5c 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -270,6 +270,66 @@ namespace boost { this->insert_or_visit(ilist.begin(), ilist.end(), f); } + template + bool try_emplace(key_type const& k, Args&&... args) + { + return table_.try_emplace(k, std::forward(args)...); + } + + template bool try_emplace(key_type&& k, Args&&... args) + { + return table_.try_emplace(std::move(k), std::forward(args)...); + } + + template + typename std::enable_if< + detail::are_transparent::value, bool>::type + try_emplace(K&& k, Args&&... args) + { + return table_.try_emplace( + std::forward(k), std::forward(args)...); + } + + template + bool try_emplace_or_visit(key_type const& k, F f, Args&&... args) + { + return table_.try_emplace_or_visit(k, f, std::forward(args)...); + } + + template + bool try_emplace_or_cvisit(key_type const& k, F f, Args&&... args) + { + return table_.try_emplace_or_cvisit(k, f, std::forward(args)...); + } + + template + bool try_emplace_or_visit(key_type&& k, F f, Args&&... args) + { + return table_.try_emplace_or_visit( + std::move(k), f, std::forward(args)...); + } + + template + bool try_emplace_or_cvisit(key_type&& k, F f, Args&&... args) + { + return table_.try_emplace_or_cvisit( + std::move(k), f, std::forward(args)...); + } + + template + bool try_emplace_or_visit(K&& k, F f, Args&&... args) + { + return table_.try_emplace_or_visit( + std::forward(k), f, std::forward(args)...); + } + + template + bool try_emplace_or_cvisit(K&& k, F f, Args&&... args) + { + return table_.try_emplace_or_cvisit( + std::forward(k), f, std::forward(args)...); + } + size_type erase(key_type const& k) { return table_.erase(k); } template diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 160e32b2..83e60710 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -214,5 +214,10 @@ rule build_cfoa ( name ) build_cfoa insert_tests ; build_cfoa erase_tests ; +build_cfoa try_emplace_tests ; -alias cfoa_tests : cfoa_insert_tests cfoa_erase_tests ; +alias cfoa_tests : + cfoa_insert_tests + cfoa_erase_tests + cfoa_try_emplace_tests +; diff --git a/test/cfoa/try_emplace_tests.cpp b/test/cfoa/try_emplace_tests.cpp new file mode 100644 index 00000000..1dd39657 --- /dev/null +++ b/test/cfoa/try_emplace_tests.cpp @@ -0,0 +1,402 @@ +// 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 + +#include + +namespace { + test::seed_t initialize_seed(511933564); + + struct lvalue_try_emplacer_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + thread_runner(values, [&x, &num_inserts](boost::span s) { + for (auto const& r : s) { + bool b = x.try_emplace(r.first, r.second.x_); + if (b) { + ++num_inserts; + } + } + }); + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_EQ(raii::default_constructor, x.size()); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } lvalue_try_emplacer; + + struct norehash_lvalue_try_emplacer_type : public lvalue_try_emplacer_type + { + template void operator()(std::vector& values, X& x) + { + x.reserve(values.size()); + lvalue_try_emplacer_type::operator()(values, x); + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + } norehash_lvalue_try_emplacer; + + struct rvalue_try_emplacer_type + { + template void operator()(std::vector& values, X& x) + { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + + std::atomic num_inserts{0}; + thread_runner(values, [&x, &num_inserts](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace(std::move(r.first), r.second.x_); + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } rvalue_try_emplacer; + + struct norehash_rvalue_try_emplacer_type : public rvalue_try_emplacer_type + { + template void operator()(std::vector& values, X& x) + { + x.reserve(values.size()); + + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_EQ(raii::move_constructor, 0u); + + rvalue_try_emplacer_type::operator()(values, x); + + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_EQ(raii::move_constructor, 0); + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_EQ(raii::move_constructor, x.size()); + } + } + } norehash_rvalue_try_emplacer; + + struct transp_try_emplace_type + { + template void operator()(std::vector& values, X& x) + { + using is_transparent = + typename boost::make_void::type; + + boost::ignore_unused(); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + + std::atomic num_inserts{0}; + + thread_runner(values, [&x, &num_inserts](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace(r.first.x_, r.second.x_); + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(raii::default_constructor, 2 * x.size()); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } transp_try_emplace; + + struct norehash_transp_try_emplace_type : public transp_try_emplace_type + { + template void operator()(std::vector& values, X& x) + { + x.reserve(values.size()); + transp_try_emplace_type::operator()(values, x); + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + } norehash_transp_try_emplace; + + struct lvalue_try_emplace_or_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_or_cvisit( + r.first, + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }, + r.second.x_); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + // don't check move construction count here because of rehashing + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + } + } lvalue_try_emplace_or_cvisit; + + struct lvalue_try_emplace_or_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_or_visit( + r.first, + [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }, + r.second.x_); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + // don't check move construction count here because of rehashing + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + } + } lvalue_try_emplace_or_visit; + + struct rvalue_try_emplace_or_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_or_cvisit( + std::move(r.first), + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }, + r.second.x_); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } + } + } rvalue_try_emplace_or_cvisit; + + struct rvalue_try_emplace_or_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_or_visit( + std::move(r.first), + [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }, + r.second.x_); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } + } + } rvalue_try_emplace_or_visit; + + struct transp_try_emplace_or_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_or_cvisit( + r.first.x_, + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }, + r.second.x_); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + BOOST_TEST_EQ(raii::default_constructor, 2 * x.size()); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + } + } transp_try_emplace_or_cvisit; + + struct transp_try_emplace_or_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_or_visit( + r.first.x_, + [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }, + r.second.x_); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 2 * x.size()); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + } + } transp_try_emplace_or_visit; + + template + void try_emplace(X*, G gen, F try_emplacer, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + X x; + + try_emplacer(values, x); + + BOOST_TEST_EQ(x.size(), reference_map.size()); + + using value_type = typename X::value_type; + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + if (rg == test::sequential) { + BOOST_TEST_EQ(kv.second, reference_map[kv.first]); + } + })); + } + + BOOST_TEST_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); + + BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + + raii::move_constructor, + raii::destructor); + } + + boost::unordered::concurrent_flat_map* map; + boost::unordered::concurrent_flat_map* transp_map; + +} // namespace + +using test::default_generator; +using test::limited_range; +using test::sequential; + +// clang-format off +UNORDERED_TEST( + try_emplace, + ((map)) + ((value_type_generator)(init_type_generator)) + ((lvalue_try_emplacer)(norehash_lvalue_try_emplacer) + (rvalue_try_emplacer)(norehash_rvalue_try_emplacer) + (lvalue_try_emplace_or_cvisit)(lvalue_try_emplace_or_visit) + (rvalue_try_emplace_or_cvisit)(rvalue_try_emplace_or_visit)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + try_emplace, + ((transp_map)) + ((init_type_generator)) + ((transp_try_emplace)(norehash_transp_try_emplace) + (transp_try_emplace_or_cvisit)(transp_try_emplace_or_visit)) + ((default_generator)(sequential)(limited_range))) +// clang-format on + +RUN_TESTS() From a004e71dd0896f7ec4d9a29a86eadca349fa2ee6 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 5 Apr 2023 13:21:18 -0700 Subject: [PATCH 142/327] Implement emplace, emplace_or_[c]visit --- .../boost/unordered/concurrent_flat_map.hpp | 17 ++ test/Jamfile.v2 | 2 + test/cfoa/emplace_tests.cpp | 167 ++++++++++++++++++ test/cfoa/insert_tests.cpp | 1 + 4 files changed, 187 insertions(+) create mode 100644 test/cfoa/emplace_tests.cpp diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index bbab9d5c..bca9f61f 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -270,6 +270,23 @@ namespace boost { this->insert_or_visit(ilist.begin(), ilist.end(), f); } + template bool emplace(Args&&... args) + { + return table_.emplace(std::forward(args)...); + } + + template + bool emplace_or_visit(F f, Args&&... args) + { + return table_.emplace_or_visit(f, std::forward(args)...); + } + + template + bool emplace_or_cvisit(F f, Args&&... args) + { + return table_.emplace_or_cvisit(f, std::forward(args)...); + } + template bool try_emplace(key_type const& k, Args&&... args) { diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 83e60710..bd7891fc 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -215,9 +215,11 @@ rule build_cfoa ( name ) build_cfoa insert_tests ; build_cfoa erase_tests ; build_cfoa try_emplace_tests ; +build_cfoa emplace_tests ; alias cfoa_tests : cfoa_insert_tests cfoa_erase_tests cfoa_try_emplace_tests + cfoa_emplace_tests ; diff --git a/test/cfoa/emplace_tests.cpp b/test/cfoa/emplace_tests.cpp new file mode 100644 index 00000000..9e62122a --- /dev/null +++ b/test/cfoa/emplace_tests.cpp @@ -0,0 +1,167 @@ +// 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 + +#include + +namespace { + test::seed_t initialize_seed(335740237); + + struct lvalue_emplacer_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + thread_runner(values, [&x, &num_inserts](boost::span s) { + for (auto const& r : s) { + bool b = x.emplace(r.first.x_, r.second.x_); + if (b) { + ++num_inserts; + } + } + }); + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(raii::default_constructor, 2 * values.size()); + + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 2 * x.size()); + + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } lvalue_emplacer; + + struct norehash_lvalue_emplacer_type : public lvalue_emplacer_type + { + template void operator()(std::vector& values, X& x) + { + x.reserve(values.size()); + lvalue_emplacer_type::operator()(values, x); + BOOST_TEST_EQ(raii::move_constructor, 2 * x.size()); + } + } norehash_lvalue_emplacer; + + struct lvalue_emplace_or_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.emplace_or_cvisit( + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }, + r.first.x_, r.second.x_); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 2 * values.size()); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 2 * x.size()); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + } + } lvalue_emplace_or_cvisit; + + struct lvalue_emplace_or_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.emplace_or_visit( + [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }, + r.first.x_, r.second.x_); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 2 * values.size()); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 2 * x.size()); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + } + } lvalue_emplace_or_visit; + + template + void emplace(X*, G gen, F emplacer, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + X x; + + emplacer(values, x); + + BOOST_TEST_EQ(x.size(), reference_map.size()); + + using value_type = typename X::value_type; + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + if (rg == test::sequential) { + BOOST_TEST_EQ(kv.second, reference_map[kv.first]); + } + })); + } + + BOOST_TEST_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); + + BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + + raii::move_constructor, + raii::destructor); + } + + boost::unordered::concurrent_flat_map* map; + +} // namespace + +using test::default_generator; +using test::limited_range; +using test::sequential; + +// clang-format off + +UNORDERED_TEST( + emplace, + ((map)) + ((value_type_generator)(init_type_generator)) + ((lvalue_emplacer)(norehash_lvalue_emplacer) + (lvalue_emplace_or_cvisit)(lvalue_emplace_or_visit)) + ((default_generator)(sequential)(limited_range))) + +// clang-format on + +RUN_TESTS() diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index d4dacc11..01b628ef 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -25,6 +25,7 @@ namespace { } }); BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); BOOST_TEST_EQ(raii::copy_assignment, 0u); BOOST_TEST_EQ(raii::move_assignment, 0u); } From e4d2da40f2f2f6d3632840d0a01293e5ec86a39e Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 6 Apr 2023 09:36:08 -0700 Subject: [PATCH 143/327] Add missing static_assert()s --- include/boost/unordered/concurrent_flat_map.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index bca9f61f..fd941175 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -278,12 +278,14 @@ namespace boost { template bool emplace_or_visit(F f, Args&&... args) { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.emplace_or_visit(f, std::forward(args)...); } template bool emplace_or_cvisit(F f, Args&&... args) { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.emplace_or_cvisit(f, std::forward(args)...); } @@ -310,18 +312,21 @@ namespace boost { template bool try_emplace_or_visit(key_type const& k, F f, Args&&... args) { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.try_emplace_or_visit(k, f, std::forward(args)...); } template bool try_emplace_or_cvisit(key_type const& k, F f, Args&&... args) { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.try_emplace_or_cvisit(k, f, std::forward(args)...); } template bool try_emplace_or_visit(key_type&& k, F f, Args&&... args) { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.try_emplace_or_visit( std::move(k), f, std::forward(args)...); } @@ -329,6 +334,7 @@ namespace boost { template bool try_emplace_or_cvisit(key_type&& k, F f, Args&&... args) { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.try_emplace_or_cvisit( std::move(k), f, std::forward(args)...); } @@ -336,6 +342,7 @@ namespace boost { template bool try_emplace_or_visit(K&& k, F f, Args&&... args) { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.try_emplace_or_visit( std::forward(k), f, std::forward(args)...); } @@ -343,6 +350,7 @@ namespace boost { template bool try_emplace_or_cvisit(K&& k, F f, Args&&... args) { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.try_emplace_or_cvisit( std::forward(k), f, std::forward(args)...); } From 3bd28dc3d709d65b2b2251aa0b60cd09300dad15 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 6 Apr 2023 13:42:57 -0700 Subject: [PATCH 144/327] Implement basic version of single element visitation --- .../boost/unordered/concurrent_flat_map.hpp | 55 +++- test/Jamfile.v2 | 2 + test/cfoa/visit_tests.cpp | 308 ++++++++++++++++++ 3 files changed, 360 insertions(+), 5 deletions(-) create mode 100644 test/cfoa/visit_tests.cpp diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index fd941175..b9547f8d 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -138,6 +138,56 @@ namespace boost { return size() == 0; } + template std::size_t visit(key_type const& k, F f) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + return table_.visit(k, f); + } + + template std::size_t visit(key_type const& k, F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.visit(k, f); + } + + template std::size_t cvisit(key_type const& k, F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.visit(k, f); + } + + template + typename std::enable_if< + detail::are_transparent::value, std::size_t>::type + visit(K&& k, F f) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + return table_.visit(std::forward(k), f); + } + + template + typename std::enable_if< + detail::are_transparent::value, std::size_t>::type + visit(K&& k, F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.visit(std::forward(k), f); + } + + template + typename std::enable_if< + detail::are_transparent::value, std::size_t>::type + cvisit(K&& k, F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.visit(std::forward(k), f); + } + + template std::size_t visit_all(F f) + { + return table_.visit_all(f); + } + /// Modifiers /// @@ -160,11 +210,6 @@ namespace boost { this->insert(ilist.begin(), ilist.end()); } - template std::size_t visit_all(F f) - { - return table_.visit_all(f); - } - template bool insert_or_assign(key_type const& k, M&& obj) { return table_.try_emplace_or_visit( diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index bd7891fc..4443ffef 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -216,10 +216,12 @@ build_cfoa insert_tests ; build_cfoa erase_tests ; build_cfoa try_emplace_tests ; build_cfoa emplace_tests ; +build_cfoa visit_tests ; alias cfoa_tests : cfoa_insert_tests cfoa_erase_tests cfoa_try_emplace_tests cfoa_emplace_tests + cfoa_visit_tests ; diff --git a/test/cfoa/visit_tests.cpp b/test/cfoa/visit_tests.cpp new file mode 100644 index 00000000..f02a3ff3 --- /dev/null +++ b/test/cfoa/visit_tests.cpp @@ -0,0 +1,308 @@ +// 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 + +#include + +#include +#include + +namespace { + test::seed_t initialize_seed(335740237); + + struct lvalue_visitor_type + { + template + void operator()(std::vector& values, X& x, test::random_generator rg) + { + using value_type = typename X::value_type; + + std::atomic num_visits{0}; + std::atomic total_count{0}; + + auto mut_visitor = [&num_visits, rg](int r, int r2) { + return [&num_visits, r, r2, rg](value_type& v) { + BOOST_TEST_EQ(v.first.x_, r); + if (rg == test::sequential) { + BOOST_TEST_EQ(v.second.x_, r2); + } + ++num_visits; + }; + }; + + auto const_visitor = [&num_visits, rg](int r, int r2) { + return [&num_visits, r, r2, rg](value_type const& v) { + BOOST_TEST_EQ(v.first.x_, r); + if (rg == test::sequential) { + BOOST_TEST_EQ(v.second.x_, r2); + } + ++num_visits; + }; + }; + + { + thread_runner( + values, [&x, &mut_visitor, &total_count](boost::span s) { + for (auto const& val : s) { + auto r = val.first.x_; + BOOST_ASSERT(r >= 0); + auto r2 = val.second.x_; + + auto count = x.visit(val.first, mut_visitor(r, r2)); + + BOOST_TEST_EQ(count, 1u); + total_count += count; + + count = x.visit(val.second, mut_visitor(r, r2)); + BOOST_TEST_EQ(count, 0u); + } + }); + + BOOST_TEST_EQ(num_visits, values.size()); + BOOST_TEST_EQ(total_count, values.size()); + + num_visits = 0; + total_count = 0; + } + + { + thread_runner( + values, [&x, &const_visitor, &total_count](boost::span s) { + for (auto const& val : s) { + auto r = val.first.x_; + BOOST_ASSERT(r >= 0); + auto r2 = val.second.x_; + + auto const& y = x; + auto count = y.visit(val.first, const_visitor(r, r2)); + + BOOST_TEST_EQ(count, 1u); + total_count += count; + + count = y.visit(val.second, const_visitor(r, r2)); + BOOST_TEST_EQ(count, 0u); + } + }); + + BOOST_TEST_EQ(num_visits, values.size()); + BOOST_TEST_EQ(total_count, values.size()); + + num_visits = 0; + total_count = 0; + } + + { + thread_runner( + values, [&x, &const_visitor, &total_count](boost::span s) { + for (auto const& val : s) { + auto r = val.first.x_; + BOOST_ASSERT(r >= 0); + auto r2 = val.second.x_; + + auto count = x.cvisit(val.first, const_visitor(r, r2)); + + BOOST_TEST_EQ(count, 1u); + total_count += count; + + count = x.cvisit(val.second, const_visitor(r, r2)); + BOOST_TEST_EQ(count, 0u); + } + }); + + BOOST_TEST_EQ(num_visits, values.size()); + BOOST_TEST_EQ(total_count, values.size()); + + num_visits = 0; + total_count = 0; + } + } + } lvalue_visitor; + + struct transp_visitor_type + { + template + void operator()(std::vector& values, X& x, test::random_generator rg) + { + using value_type = typename X::value_type; + + std::atomic num_visits{0}; + std::atomic total_count{0}; + + auto mut_visitor = [&num_visits, rg](int r, int r2) { + return [&num_visits, r, r2, rg](value_type& v) { + BOOST_TEST_EQ(v.first.x_, r); + if (rg == test::sequential) { + BOOST_TEST_EQ(v.second.x_, r2); + } + ++num_visits; + }; + }; + + auto const_visitor = [&num_visits, rg](int r, int r2) { + return [&num_visits, r, r2, rg](value_type const& v) { + BOOST_TEST_EQ(v.first.x_, r); + if (rg == test::sequential) { + BOOST_TEST_EQ(v.second.x_, r2); + } + ++num_visits; + }; + }; + + { + thread_runner( + values, [&x, &mut_visitor, &total_count](boost::span s) { + for (auto const& val : s) { + auto r = val.first.x_; + BOOST_ASSERT(r >= 0); + auto r2 = val.second.x_; + + auto count = x.visit(val.first.x_, mut_visitor(r, r2)); + + BOOST_TEST_EQ(count, 1u); + total_count += count; + + count = x.visit(val.second.x_, mut_visitor(r, r2)); + BOOST_TEST_EQ(count, 0u); + } + }); + + BOOST_TEST_EQ(num_visits, values.size()); + BOOST_TEST_EQ(total_count, values.size()); + + num_visits = 0; + total_count = 0; + } + + { + thread_runner( + values, [&x, &const_visitor, &total_count](boost::span s) { + for (auto const& val : s) { + auto r = val.first.x_; + BOOST_ASSERT(r >= 0); + auto r2 = val.second.x_; + + auto const& y = x; + auto count = y.visit(val.first.x_, const_visitor(r, r2)); + + BOOST_TEST_EQ(count, 1u); + total_count += count; + + count = y.visit(val.second.x_, const_visitor(r, r2)); + BOOST_TEST_EQ(count, 0u); + } + }); + + BOOST_TEST_EQ(num_visits, values.size()); + BOOST_TEST_EQ(total_count, values.size()); + + num_visits = 0; + total_count = 0; + } + + { + thread_runner( + values, [&x, &const_visitor, &total_count](boost::span s) { + for (auto const& val : s) { + auto r = val.first.x_; + BOOST_ASSERT(r >= 0); + auto r2 = val.second.x_; + + auto count = x.cvisit(val.first.x_, const_visitor(r, r2)); + + BOOST_TEST_EQ(count, 1u); + total_count += count; + + count = x.cvisit(val.second.x_, const_visitor(r, r2)); + BOOST_TEST_EQ(count, 0u); + } + }); + + BOOST_TEST_EQ(num_visits, values.size()); + BOOST_TEST_EQ(total_count, values.size()); + + num_visits = 0; + total_count = 0; + } + } + } transp_visitor; + + template + void visit(X*, G gen, F visitor, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + for (auto& val : values) { + if (val.second.x_ == 0) { + val.second.x_ = 1; + } + val.second.x_ *= -1; + } + + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + X x; + for (auto const& v : values) { + x.insert(v); + } + BOOST_TEST_EQ(x.size(), reference_map.size()); + + std::uint64_t old_default_constructor = raii::default_constructor; + std::uint64_t old_copy_constructor = raii::copy_constructor; + std::uint64_t old_move_constructor = raii::move_constructor; + std::uint64_t old_copy_assignment = raii::copy_assignment; + std::uint64_t old_move_assignment = raii::move_assignment; + + visitor(values, x, rg); + + BOOST_TEST_EQ(old_default_constructor, raii::default_constructor); + BOOST_TEST_EQ(old_copy_constructor, raii::copy_constructor); + BOOST_TEST_EQ(old_move_constructor, raii::move_constructor); + BOOST_TEST_EQ(old_copy_assignment, raii::copy_assignment); + BOOST_TEST_EQ(old_move_assignment, raii::move_assignment); + } + + BOOST_TEST_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); + + BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + + raii::move_constructor, + raii::destructor); + } + + boost::unordered::concurrent_flat_map* map; + boost::unordered::concurrent_flat_map* transp_map; + +} // namespace + +using test::default_generator; +using test::limited_range; +using test::sequential; + +// clang-format off + +UNORDERED_TEST( + visit, + ((map)) + ((value_type_generator)(init_type_generator)) + ((lvalue_visitor)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + visit, + ((transp_map)) + ((value_type_generator)(init_type_generator)) + ((transp_visitor)) + ((default_generator)(sequential)(limited_range))) + +// clang-format on + +RUN_TESTS() From 91eb2ddbd1ac10fac04b676018ef79c4fab47624 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 7 Apr 2023 12:39:13 -0700 Subject: [PATCH 145/327] Fix sign comparison warning --- test/cfoa/try_emplace_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cfoa/try_emplace_tests.cpp b/test/cfoa/try_emplace_tests.cpp index 1dd39657..20af1a95 100644 --- a/test/cfoa/try_emplace_tests.cpp +++ b/test/cfoa/try_emplace_tests.cpp @@ -86,7 +86,7 @@ namespace { if (std::is_same::value) { BOOST_TEST_EQ(raii::copy_constructor, x.size()); - BOOST_TEST_EQ(raii::move_constructor, 0); + BOOST_TEST_EQ(raii::move_constructor, 0u); } else { BOOST_TEST_EQ(raii::copy_constructor, 0u); BOOST_TEST_EQ(raii::move_constructor, x.size()); From ed80cb14e2cfac026c0b86be8d46703bb4aee356 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 7 Apr 2023 12:39:39 -0700 Subject: [PATCH 146/327] Add visit_all() --- .../boost/unordered/concurrent_flat_map.hpp | 42 ++++ test/cfoa/visit_tests.cpp | 213 +++++++++++++----- 2 files changed, 198 insertions(+), 57 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index b9547f8d..2bea917e 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -185,9 +185,51 @@ namespace boost { template std::size_t visit_all(F f) { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.visit_all(f); } + template std::size_t visit_all(F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.visit_all(f); + } + + template std::size_t cvisit_all(F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.cvisit_all(f); + } + +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) + template + typename std::enable_if::value, + void>::type + visit_all(ExecPolicy p, F f) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + table_.visit_all(p, f); + } + + template + typename std::enable_if::value, + void>::type + visit_all(ExecPolicy p, F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + table_.visit_all(p, f); + } + + template + typename std::enable_if::value, + void>::type + cvisit_all(ExecPolicy p, F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + table_.cvisit_all(p, f); + } +#endif + /// Modifiers /// diff --git a/test/cfoa/visit_tests.cpp b/test/cfoa/visit_tests.cpp index f02a3ff3..ef526546 100644 --- a/test/cfoa/visit_tests.cpp +++ b/test/cfoa/visit_tests.cpp @@ -16,32 +16,24 @@ namespace { struct lvalue_visitor_type { - template - void operator()(std::vector& values, X& x, test::random_generator rg) + template + void operator()(std::vector& values, X& x, M const& reference_map) { using value_type = typename X::value_type; std::atomic num_visits{0}; std::atomic total_count{0}; - auto mut_visitor = [&num_visits, rg](int r, int r2) { - return [&num_visits, r, r2, rg](value_type& v) { - BOOST_TEST_EQ(v.first.x_, r); - if (rg == test::sequential) { - BOOST_TEST_EQ(v.second.x_, r2); - } - ++num_visits; - }; + auto mut_visitor = [&num_visits, &reference_map](value_type& v) { + BOOST_TEST(reference_map.contains(v.first)); + BOOST_TEST_EQ(v.second, reference_map.find(v.first)->second); + ++num_visits; }; - auto const_visitor = [&num_visits, rg](int r, int r2) { - return [&num_visits, r, r2, rg](value_type const& v) { - BOOST_TEST_EQ(v.first.x_, r); - if (rg == test::sequential) { - BOOST_TEST_EQ(v.second.x_, r2); - } - ++num_visits; - }; + auto const_visitor = [&num_visits, &reference_map](value_type const& v) { + BOOST_TEST(reference_map.contains(v.first)); + BOOST_TEST_EQ(v.second, reference_map.find(v.first)->second); + ++num_visits; }; { @@ -50,14 +42,12 @@ namespace { for (auto const& val : s) { auto r = val.first.x_; BOOST_ASSERT(r >= 0); - auto r2 = val.second.x_; - - auto count = x.visit(val.first, mut_visitor(r, r2)); + auto count = x.visit(val.first, mut_visitor); BOOST_TEST_EQ(count, 1u); total_count += count; - count = x.visit(val.second, mut_visitor(r, r2)); + count = x.visit(val.second, mut_visitor); BOOST_TEST_EQ(count, 0u); } }); @@ -75,15 +65,14 @@ namespace { for (auto const& val : s) { auto r = val.first.x_; BOOST_ASSERT(r >= 0); - auto r2 = val.second.x_; auto const& y = x; - auto count = y.visit(val.first, const_visitor(r, r2)); + auto count = y.visit(val.first, const_visitor); BOOST_TEST_EQ(count, 1u); total_count += count; - count = y.visit(val.second, const_visitor(r, r2)); + count = y.visit(val.second, const_visitor); BOOST_TEST_EQ(count, 0u); } }); @@ -101,14 +90,13 @@ namespace { for (auto const& val : s) { auto r = val.first.x_; BOOST_ASSERT(r >= 0); - auto r2 = val.second.x_; - auto count = x.cvisit(val.first, const_visitor(r, r2)); + auto count = x.cvisit(val.first, const_visitor); BOOST_TEST_EQ(count, 1u); total_count += count; - count = x.cvisit(val.second, const_visitor(r, r2)); + count = x.cvisit(val.second, const_visitor); BOOST_TEST_EQ(count, 0u); } }); @@ -124,32 +112,24 @@ namespace { struct transp_visitor_type { - template - void operator()(std::vector& values, X& x, test::random_generator rg) + template + void operator()(std::vector& values, X& x, M const& reference_map) { using value_type = typename X::value_type; std::atomic num_visits{0}; std::atomic total_count{0}; - auto mut_visitor = [&num_visits, rg](int r, int r2) { - return [&num_visits, r, r2, rg](value_type& v) { - BOOST_TEST_EQ(v.first.x_, r); - if (rg == test::sequential) { - BOOST_TEST_EQ(v.second.x_, r2); - } - ++num_visits; - }; + auto mut_visitor = [&num_visits, &reference_map](value_type& v) { + BOOST_TEST(reference_map.contains(v.first)); + BOOST_TEST_EQ(v.second, reference_map.find(v.first)->second); + ++num_visits; }; - auto const_visitor = [&num_visits, rg](int r, int r2) { - return [&num_visits, r, r2, rg](value_type const& v) { - BOOST_TEST_EQ(v.first.x_, r); - if (rg == test::sequential) { - BOOST_TEST_EQ(v.second.x_, r2); - } - ++num_visits; - }; + auto const_visitor = [&num_visits, &reference_map](value_type const& v) { + BOOST_TEST(reference_map.contains(v.first)); + BOOST_TEST_EQ(v.second, reference_map.find(v.first)->second); + ++num_visits; }; { @@ -158,14 +138,13 @@ namespace { for (auto const& val : s) { auto r = val.first.x_; BOOST_ASSERT(r >= 0); - auto r2 = val.second.x_; - auto count = x.visit(val.first.x_, mut_visitor(r, r2)); + auto count = x.visit(val.first.x_, mut_visitor); BOOST_TEST_EQ(count, 1u); total_count += count; - count = x.visit(val.second.x_, mut_visitor(r, r2)); + count = x.visit(val.second.x_, mut_visitor); BOOST_TEST_EQ(count, 0u); } }); @@ -183,15 +162,14 @@ namespace { for (auto const& val : s) { auto r = val.first.x_; BOOST_ASSERT(r >= 0); - auto r2 = val.second.x_; auto const& y = x; - auto count = y.visit(val.first.x_, const_visitor(r, r2)); + auto count = y.visit(val.first.x_, const_visitor); BOOST_TEST_EQ(count, 1u); total_count += count; - count = y.visit(val.second.x_, const_visitor(r, r2)); + count = y.visit(val.second.x_, const_visitor); BOOST_TEST_EQ(count, 0u); } }); @@ -209,14 +187,13 @@ namespace { for (auto const& val : s) { auto r = val.first.x_; BOOST_ASSERT(r >= 0); - auto r2 = val.second.x_; - auto count = x.cvisit(val.first.x_, const_visitor(r, r2)); + auto count = x.cvisit(val.first.x_, const_visitor); BOOST_TEST_EQ(count, 1u); total_count += count; - count = x.cvisit(val.second.x_, const_visitor(r, r2)); + count = x.cvisit(val.second.x_, const_visitor); BOOST_TEST_EQ(count, 0u); } }); @@ -230,6 +207,128 @@ namespace { } } transp_visitor; + struct visit_all_type + { + template + void operator()(std::vector& values, X& x, M const& reference_map) + { + using value_type = typename X::value_type; + + std::atomic total_count{0}; + + auto mut_visitor = [&reference_map](std::atomic& num_visits) { + return [&reference_map, &num_visits](value_type& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second); + ++num_visits; + }; + }; + + auto const_visitor = [&reference_map](std::atomic& num_visits) { + return [&reference_map, &num_visits](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second); + ++num_visits; + }; + }; + + { + thread_runner(values, [&x, &total_count, &mut_visitor](boost::span) { + std::atomic num_visits{0}; + total_count += x.visit_all(mut_visitor(num_visits)); + BOOST_TEST_EQ(x.size(), num_visits); + }); + + BOOST_TEST_EQ(total_count, num_threads * x.size()); + total_count = 0; + } + + { + thread_runner( + values, [&x, &total_count, &const_visitor](boost::span) { + std::atomic num_visits{0}; + auto const& y = x; + total_count += y.visit_all(const_visitor(num_visits)); + BOOST_TEST_EQ(x.size(), num_visits); + }); + + BOOST_TEST_EQ(total_count, num_threads * x.size()); + total_count = 0; + } + + { + thread_runner( + values, [&x, &total_count, &const_visitor](boost::span) { + std::atomic num_visits{0}; + total_count += x.cvisit_all(const_visitor(num_visits)); + BOOST_TEST_EQ(x.size(), num_visits); + }); + + BOOST_TEST_EQ(total_count, num_threads * x.size()); + total_count = 0; + } + } + + } visit_all; + + struct exec_policy_visit_all_type + { + template + void operator()(std::vector& values, X& x, M const& reference_map) + { +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) + using value_type = typename X::value_type; + + auto mut_visitor = [&reference_map](std::atomic& num_visits) { + return [&reference_map, &num_visits](value_type& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second); + ++num_visits; + }; + }; + + auto const_visitor = [&reference_map](std::atomic& num_visits) { + return [&reference_map, &num_visits](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second); + ++num_visits; + }; + }; + + { + thread_runner(values, [&x, &mut_visitor](boost::span) { + std::atomic num_visits{0}; + + x.visit_all(std::execution::par_unseq, mut_visitor(num_visits)); + BOOST_TEST_EQ(x.size(), num_visits); + }); + } + + { + thread_runner(values, [&x, &const_visitor](boost::span) { + std::atomic num_visits{0}; + auto const& y = x; + + y.visit_all(std::execution::par_unseq, const_visitor(num_visits)); + BOOST_TEST_EQ(x.size(), num_visits); + }); + } + + { + thread_runner(values, [&x, &const_visitor](boost::span) { + std::atomic num_visits{0}; + x.cvisit_all(std::execution::par_unseq, const_visitor(num_visits)); + BOOST_TEST_EQ(x.size(), num_visits); + }); + } +#else + (void)values; + (void)x; + (void)reference_map; +#endif + } + } exec_policy_visit_all; + template void visit(X*, G gen, F visitor, test::random_generator rg) { @@ -258,7 +357,7 @@ namespace { std::uint64_t old_copy_assignment = raii::copy_assignment; std::uint64_t old_move_assignment = raii::move_assignment; - visitor(values, x, rg); + visitor(values, x, reference_map); BOOST_TEST_EQ(old_default_constructor, raii::default_constructor); BOOST_TEST_EQ(old_copy_constructor, raii::copy_constructor); @@ -293,7 +392,7 @@ UNORDERED_TEST( visit, ((map)) ((value_type_generator)(init_type_generator)) - ((lvalue_visitor)) + ((lvalue_visitor)(visit_all)(exec_policy_visit_all)) ((default_generator)(sequential)(limited_range))) UNORDERED_TEST( From 8544d9f3c8628c2e6f9d6c3c5c8721396f9cb3c1 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 7 Apr 2023 12:40:06 -0700 Subject: [PATCH 147/327] Clean up test/Jamfile --- test/Jamfile.v2 | 137 +++++++++++++++++------------------------------- 1 file changed, 49 insertions(+), 88 deletions(-) diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 4443ffef..ca00ff4f 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -104,50 +104,51 @@ import ../../config/checks/config : requires ; CPP11 = [ requires cxx11_constexpr cxx11_noexcept cxx11_decltype cxx11_alignas ] ; -rule build_foa ( name ) +local FOA_TESTS = + fwd_set_test + fwd_map_test + compile_set + compile_map + noexcept_tests + incomplete_test + simple_tests + equivalent_keys_tests + constructor_tests + copy_tests + move_tests + post_move_tests + assign_tests + insert_tests + insert_hint_tests + emplace_tests + erase_tests + merge_tests + find_tests + at_tests + load_factor_tests + rehash_tests + equality_tests + swap_tests + transparent_tests + reserve_tests + contains_tests + erase_if + scary_tests + init_type_insert_tests + max_load_tests + extract_tests + node_handle_tests + uses_allocator +; + +for local test in $(FOA_TESTS) { - run unordered/$(name).cpp : : : $(CPP11) BOOST_UNORDERED_FOA_TESTS : foa_$(name) ; + run unordered/$(test).cpp : : : $(CPP11) BOOST_UNORDERED_FOA_TESTS : foa_$(test) ; } -build_foa fwd_set_test ; -build_foa fwd_map_test ; -build_foa compile_set ; -build_foa compile_map ; -build_foa noexcept_tests ; run unordered/link_test_1.cpp unordered/link_test_2.cpp : : : $(CPP11) BOOST_UNORDERED_FOA_TESTS : foa_link_test ; -build_foa incomplete_test ; -build_foa simple_tests ; -build_foa equivalent_keys_tests ; -build_foa constructor_tests ; -build_foa copy_tests ; -build_foa move_tests ; -build_foa post_move_tests ; -build_foa assign_tests ; -build_foa insert_tests ; -build_foa insert_hint_tests ; -build_foa emplace_tests ; -build_foa erase_tests ; -build_foa merge_tests ; -build_foa find_tests ; -build_foa at_tests ; -build_foa load_factor_tests ; -build_foa rehash_tests ; -build_foa equality_tests ; -build_foa swap_tests ; run unordered/scoped_allocator.cpp : : : $(CPP11) msvc-14.0:no BOOST_UNORDERED_FOA_TESTS : foa_scoped_allocator ; -build_foa transparent_tests ; -build_foa reserve_tests ; -build_foa contains_tests ; -build_foa erase_if ; -build_foa scary_tests ; -build_foa init_type_insert_tests ; -build_foa max_load_tests ; -build_foa extract_tests ; -build_foa node_handle_tests ; -build_foa uses_allocator ; - run unordered/hash_is_avalanching_test.cpp ; - run exception/constructor_exception_tests.cpp : : : $(CPP11) BOOST_UNORDERED_FOA_TESTS : foa_constructor_exception_tests ; run exception/copy_exception_tests.cpp : : : $(CPP11) BOOST_UNORDERED_FOA_TESTS : foa_copy_exception_tests ; run exception/assign_exception_tests.cpp : : : $(CPP11) BOOST_UNORDERED_FOA_TESTS : foa_assign_exception_tests ; @@ -159,43 +160,10 @@ run exception/swap_exception_tests.cpp : : : $(CPP11) BOOST_UNORD run exception/merge_exception_tests.cpp : : : $(CPP11) BOOST_UNORDERED_FOA_TESTS : foa_merge_exception_tests ; alias foa_tests : - hash_is_avalanching_test - foa_fwd_set_test - foa_fwd_map_test - foa_compile_set - foa_compile_map - foa_noexcept_tests - foa_incomplete_test - foa_simple_tests - foa_equivalent_keys_tests - foa_constructor_tests - foa_copy_tests - foa_move_tests - foa_post_move_tests - foa_assign_tests - foa_insert_tests - foa_insert_hint_tests - foa_emplace_tests - foa_erase_tests - foa_merge_tests - foa_find_tests - foa_at_tests - foa_load_factor_tests - foa_rehash_tests - foa_equality_tests - foa_swap_tests - foa_transparent_tests - foa_reserve_tests - foa_contains_tests - foa_erase_if - foa_scary_tests - foa_init_type_insert_tests - foa_max_load_tests - foa_extract_tests - foa_node_handle_tests - foa_uses_allocator + foa_$(FOA_TESTS) foa_link_test foa_scoped_allocator + hash_is_avalanching_test foa_constructor_exception_tests foa_copy_exception_tests foa_assign_exception_tests @@ -207,21 +175,14 @@ alias foa_tests : foa_merge_exception_tests ; -rule build_cfoa ( name ) +local CFOA_TESTS = insert_tests erase_tests try_emplace_tests emplace_tests visit_tests ; + +for local test in $(CFOA_TESTS) { - run cfoa/$(name).cpp : : : $(CPP11) : cfoa_$(name) ; + run cfoa/$(test).cpp + : requirements $(CPP11) + : target-name cfoa_$(test) + ; } -build_cfoa insert_tests ; -build_cfoa erase_tests ; -build_cfoa try_emplace_tests ; -build_cfoa emplace_tests ; -build_cfoa visit_tests ; - -alias cfoa_tests : - cfoa_insert_tests - cfoa_erase_tests - cfoa_try_emplace_tests - cfoa_emplace_tests - cfoa_visit_tests -; +alias cfoa_tests : cfoa_$(CFOA_TESTS) ; From 8a4e987030c7fdb03b59e0f4bf82452bf7f8450a Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 9 Apr 2023 20:20:55 +0200 Subject: [PATCH 148/327] made f the last argument in all *_or_[c]visit functions --- .../boost/unordered/concurrent_flat_map.hpp | 80 +++++++++--------- .../unordered/detail/foa/concurrent_table.hpp | 82 +++++++++++++++---- .../detail/foa/tuple_rotate_right.hpp | 47 +++++++++++ 3 files changed, 155 insertions(+), 54 deletions(-) create mode 100644 include/boost/unordered/detail/foa/tuple_rotate_right.hpp diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 2bea917e..40c2fcae 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -362,18 +362,20 @@ namespace boost { return table_.emplace(std::forward(args)...); } - template - bool emplace_or_visit(F f, Args&&... args) + template + bool emplace_or_visit(Arg&& arg, Args&&... args) { - BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) - return table_.emplace_or_visit(f, std::forward(args)...); + //BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + return table_.emplace_or_visit( + std::forward(arg), std::forward(args)...); } - template - bool emplace_or_cvisit(F f, Args&&... args) + template + bool emplace_or_cvisit(Arg&& arg, Args&&... args) { - BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) - return table_.emplace_or_cvisit(f, std::forward(args)...); + //BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.emplace_or_cvisit( + std::forward(arg), std::forward(args)...); } template @@ -396,50 +398,52 @@ namespace boost { std::forward(k), std::forward(args)...); } - template - bool try_emplace_or_visit(key_type const& k, F f, Args&&... args) + template + bool try_emplace_or_visit(key_type const& k, Arg&& arg, Args&&... args) { - BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) - return table_.try_emplace_or_visit(k, f, std::forward(args)...); - } - - template - bool try_emplace_or_cvisit(key_type const& k, F f, Args&&... args) - { - BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) - return table_.try_emplace_or_cvisit(k, f, std::forward(args)...); - } - - template - bool try_emplace_or_visit(key_type&& k, F f, Args&&... args) - { - BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + //BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.try_emplace_or_visit( - std::move(k), f, std::forward(args)...); + k, std::forward(arg), std::forward(args)...); } - template - bool try_emplace_or_cvisit(key_type&& k, F f, Args&&... args) + template + bool try_emplace_or_cvisit(key_type const& k, Arg&& arg, Args&&... args) { - BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + //BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.try_emplace_or_cvisit( - std::move(k), f, std::forward(args)...); + k, std::forward(arg), std::forward(args)...); } - template - bool try_emplace_or_visit(K&& k, F f, Args&&... args) + template + bool try_emplace_or_visit(key_type&& k, Arg&& arg, Args&&... args) { - BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + //BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.try_emplace_or_visit( - std::forward(k), f, std::forward(args)...); + std::move(k), std::forward(arg), std::forward(args)...); } - template - bool try_emplace_or_cvisit(K&& k, F f, Args&&... args) + template + bool try_emplace_or_cvisit(key_type&& k, Arg&& arg, Args&&... args) { - BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + //BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.try_emplace_or_cvisit( - std::forward(k), f, std::forward(args)...); + std::move(k), std::forward(arg), std::forward(args)...); + } + + template + bool try_emplace_or_visit(K&& k, Arg&& arg, Args&&... args) + { + //BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + return table_.try_emplace_or_visit( + std::forward(k), std::forward(arg), std::forward(args)...); + } + + template + bool try_emplace_or_cvisit(K&& k, Arg&& arg, Args&&... args) + { + //BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.try_emplace_or_cvisit( + std::forward(k), std::forward(arg), std::forward(args)...); } size_type erase(key_type const& k) { return table_.erase(k); } diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 2461bbad..4517ae5c 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -17,13 +17,16 @@ #include #include #include +#include #include #include +#include #include #include #include #include #include +#include #include #if !defined(BOOST_UNORDERED_DISABLE_PARALLEL_ALGORITHMS) @@ -445,34 +448,34 @@ public: try_emplace_args_t{},std::forward(x),std::forward(args)...); } - template - BOOST_FORCEINLINE bool try_emplace_or_visit(Key&& x,F&& f,Args&&... args) + template + BOOST_FORCEINLINE bool try_emplace_or_visit(Key&& x,Args&&... args) { - return emplace_or_visit_impl( - group_exclusive{},std::forward(f), + return emplace_or_visit_flast( + group_exclusive{}, try_emplace_args_t{},std::forward(x),std::forward(args)...); } - template - BOOST_FORCEINLINE bool try_emplace_or_cvisit(Key&& x,F&& f,Args&&... args) + template + BOOST_FORCEINLINE bool try_emplace_or_cvisit(Key&& x,Args&&... args) { - return emplace_or_visit_impl( - group_shared{},std::forward(f), + return emplace_or_visit_flast( + group_shared{}, try_emplace_args_t{},std::forward(x),std::forward(args)...); } - template - BOOST_FORCEINLINE bool emplace_or_visit(F&& f,Args&&... args) + template + BOOST_FORCEINLINE bool emplace_or_visit(Args&&... args) { - return construct_and_emplace_or_visit( - group_exclusive{},std::forward(f),std::forward(args)...); + return construct_and_emplace_or_visit_flast( + group_exclusive{},std::forward(args)...); } - template - BOOST_FORCEINLINE bool emplace_or_cvisit(F&& f,Args&&... args) + template + BOOST_FORCEINLINE bool emplace_or_cvisit(Args&&... args) { - return construct_and_emplace_or_visit( - group_shared{},std::forward(f),std::forward(args)...); + return construct_and_emplace_or_visit_flast( + group_shared{},std::forward(args)...); } template @@ -880,6 +883,30 @@ private: group_shared{},[](const value_type&){},std::forward(args)...); } + struct call_construct_and_emplace_or_visit + { + template + BOOST_FORCEINLINE bool operator()( + concurrent_table* this_,Args&&... args)const + { + return this_->construct_and_emplace_or_visit( + std::forward(args)...); + } + }; + + template + BOOST_FORCEINLINE bool construct_and_emplace_or_visit_flast( + GroupAccessMode access_mode,Args&&... args) + { + return mp11::tuple_apply( + call_construct_and_emplace_or_visit{}, + std::tuple_cat( + std::make_tuple(this,access_mode), + tuple_rotate_right(std::forward_as_tuple(std::forward(args)...)) + ) + ); + } + template BOOST_FORCEINLINE bool construct_and_emplace_or_visit( GroupAccessMode access_mode,F&& f,Args&&... args) @@ -914,6 +941,29 @@ private: access_mode,std::forward(f),std::forward(args)...); } + struct call_emplace_or_visit_impl + { + template + BOOST_FORCEINLINE bool operator()( + concurrent_table* this_,Args&&... args)const + { + return this_->emplace_or_visit_impl(std::forward(args)...); + } + }; + + template + BOOST_FORCEINLINE bool emplace_or_visit_flast( + GroupAccessMode access_mode,Args&&... args) + { + return mp11::tuple_apply( + call_emplace_or_visit_impl{}, + std::tuple_cat( + std::make_tuple(this,access_mode), + tuple_rotate_right(std::forward_as_tuple(std::forward(args)...)) + ) + ); + } + template BOOST_FORCEINLINE bool emplace_or_visit_impl( GroupAccessMode access_mode,F&& f,Args&&... args) diff --git a/include/boost/unordered/detail/foa/tuple_rotate_right.hpp b/include/boost/unordered/detail/foa/tuple_rotate_right.hpp new file mode 100644 index 00000000..c63a7fb5 --- /dev/null +++ b/include/boost/unordered/detail/foa/tuple_rotate_right.hpp @@ -0,0 +1,47 @@ +/* Copyright 2023 Joaquin M Lopez Munoz. + * 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. + */ + +#ifndef BOOST_UNORDERED_DETAIL_FOA_TUPLE_ROTATE_RIGHT_HPP +#define BOOST_UNORDERED_DETAIL_FOA_TUPLE_ROTATE_RIGHT_HPP + +#include +#include +#include + +namespace boost{ +namespace unordered{ +namespace detail{ +namespace foa{ + +template +auto tuple_rotate_right_aux(mp11::index_sequence,Tuple&& x) + ->std::tuple(std::forward(x)))... + > +{ + return { + std::get<(Is+sizeof...(Is)-1)%sizeof...(Is)>(std::forward(x))...}; +} + +template +auto tuple_rotate_right(Tuple&& x) + ->decltype(tuple_rotate_right_aux( + mp11::make_index_sequence::value>{}, + std::forward(x))) +{ + return tuple_rotate_right_aux( + mp11::make_index_sequence::value>{}, + std::forward(x)); +} + +} /* namespace foa */ +} /* namespace detail */ +} /* namespace unordered */ +} /* namespace boost */ + +#endif From e73d5ecdd10069072de792d4ca58810bd00cf7fb Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 9 Apr 2023 20:41:15 +0200 Subject: [PATCH 149/327] updated tests affected by change in position of f --- test/cfoa/emplace_tests.cpp | 4 ++-- test/cfoa/try_emplace_tests.cpp | 15 ++++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/test/cfoa/emplace_tests.cpp b/test/cfoa/emplace_tests.cpp index 9e62122a..ab6215cf 100644 --- a/test/cfoa/emplace_tests.cpp +++ b/test/cfoa/emplace_tests.cpp @@ -87,11 +87,11 @@ namespace { thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { for (auto& r : s) { bool b = x.emplace_or_visit( + r.first.x_, r.second.x_, [&num_invokes](typename X::value_type& v) { (void)v; ++num_invokes; - }, - r.first.x_, r.second.x_); + }); if (b) { ++num_inserts; diff --git a/test/cfoa/try_emplace_tests.cpp b/test/cfoa/try_emplace_tests.cpp index 20af1a95..bb58e5b8 100644 --- a/test/cfoa/try_emplace_tests.cpp +++ b/test/cfoa/try_emplace_tests.cpp @@ -178,12 +178,11 @@ namespace { thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { for (auto& r : s) { bool b = x.try_emplace_or_visit( - r.first, + r.first, r.second.x_, [&num_invokes](typename X::value_type& v) { (void)v; ++num_invokes; - }, - r.second.x_); + }); if (b) { ++num_inserts; @@ -249,12 +248,11 @@ namespace { thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { for (auto& r : s) { bool b = x.try_emplace_or_visit( - std::move(r.first), + std::move(r.first), r.second.x_, [&num_invokes](typename X::value_type& v) { (void)v; ++num_invokes; - }, - r.second.x_); + }); if (b) { ++num_inserts; @@ -314,12 +312,11 @@ namespace { thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { for (auto& r : s) { bool b = x.try_emplace_or_visit( - r.first.x_, + r.first.x_, r.second.x_, [&num_invokes](typename X::value_type& v) { (void)v; ++num_invokes; - }, - r.second.x_); + }); if (b) { ++num_inserts; From c54151769f65e0699d8c56c981e89220aa86c7c0 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 9 Apr 2023 21:56:12 +0200 Subject: [PATCH 150/327] updated insert_or_assign impls to f-last interface --- include/boost/unordered/concurrent_flat_map.hpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 40c2fcae..043c34f3 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -255,15 +255,15 @@ namespace boost { template bool insert_or_assign(key_type const& k, M&& obj) { return table_.try_emplace_or_visit( - k, [&](value_type& m) { m.second = std::forward(obj); }, - std::forward(obj)); + k, std::forward(obj), + [&](value_type& m) { m.second = std::forward(obj); }); } template bool insert_or_assign(key_type&& k, M&& obj) { return table_.try_emplace_or_visit( - std::move(k), [&](value_type& m) { m.second = std::forward(obj); }, - std::forward(obj)); + std::move(k), std::forward(obj), + [&](value_type& m) { m.second = std::forward(obj); }); } template @@ -272,9 +272,8 @@ namespace boost { insert_or_assign(K&& k, M&& obj) { return table_.try_emplace_or_visit( - std::forward(k), - [&](value_type& m) { m.second = std::forward(obj); }, - std::forward(obj)); + std::forward(k), std::forward(obj), + [&](value_type& m) { m.second = std::forward(obj); }); } template bool insert_or_visit(value_type const& obj, F f) @@ -494,4 +493,4 @@ namespace boost { #undef BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE #undef BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE -#endif // BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP \ No newline at end of file +#endif // BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP From 81a47ca88d54ec1dbb77250f8322e606791b818c Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 10 Apr 2023 09:05:19 +0200 Subject: [PATCH 151/327] updated more tests affected by change in position of f --- test/cfoa/emplace_tests.cpp | 4 ++-- test/cfoa/try_emplace_tests.cpp | 15 ++++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/test/cfoa/emplace_tests.cpp b/test/cfoa/emplace_tests.cpp index ab6215cf..ba8ac70d 100644 --- a/test/cfoa/emplace_tests.cpp +++ b/test/cfoa/emplace_tests.cpp @@ -55,11 +55,11 @@ namespace { thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { for (auto& r : s) { bool b = x.emplace_or_cvisit( + r.first.x_, r.second.x_, [&num_invokes](typename X::value_type const& v) { (void)v; ++num_invokes; - }, - r.first.x_, r.second.x_); + }); if (b) { ++num_inserts; diff --git a/test/cfoa/try_emplace_tests.cpp b/test/cfoa/try_emplace_tests.cpp index bb58e5b8..374c5f02 100644 --- a/test/cfoa/try_emplace_tests.cpp +++ b/test/cfoa/try_emplace_tests.cpp @@ -144,12 +144,11 @@ namespace { thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { for (auto& r : s) { bool b = x.try_emplace_or_cvisit( - r.first, + r.first, r.second.x_, [&num_invokes](typename X::value_type const& v) { (void)v; ++num_invokes; - }, - r.second.x_); + }); if (b) { ++num_inserts; @@ -211,12 +210,11 @@ namespace { thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { for (auto& r : s) { bool b = x.try_emplace_or_cvisit( - std::move(r.first), + std::move(r.first), r.second.x_, [&num_invokes](typename X::value_type const& v) { (void)v; ++num_invokes; - }, - r.second.x_); + }); if (b) { ++num_inserts; @@ -283,12 +281,11 @@ namespace { thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { for (auto& r : s) { bool b = x.try_emplace_or_cvisit( - r.first.x_, + r.first.x_, r.second.x_, [&num_invokes](typename X::value_type const& v) { (void)v; ++num_invokes; - }, - r.second.x_); + }); if (b) { ++num_inserts; From 8ea1cbbd643aab78434bc8c599c27c77c6aba8d7 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 10 Apr 2023 10:17:05 +0200 Subject: [PATCH 152/327] s/BOOST_ASSERT/BOOST_TEST to avoid unused var warnings --- test/cfoa/visit_tests.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/cfoa/visit_tests.cpp b/test/cfoa/visit_tests.cpp index ef526546..3dacfd57 100644 --- a/test/cfoa/visit_tests.cpp +++ b/test/cfoa/visit_tests.cpp @@ -41,7 +41,7 @@ namespace { values, [&x, &mut_visitor, &total_count](boost::span s) { for (auto const& val : s) { auto r = val.first.x_; - BOOST_ASSERT(r >= 0); + BOOST_TEST(r >= 0); auto count = x.visit(val.first, mut_visitor); BOOST_TEST_EQ(count, 1u); @@ -64,7 +64,7 @@ namespace { values, [&x, &const_visitor, &total_count](boost::span s) { for (auto const& val : s) { auto r = val.first.x_; - BOOST_ASSERT(r >= 0); + BOOST_TEST(r >= 0); auto const& y = x; auto count = y.visit(val.first, const_visitor); @@ -89,7 +89,7 @@ namespace { values, [&x, &const_visitor, &total_count](boost::span s) { for (auto const& val : s) { auto r = val.first.x_; - BOOST_ASSERT(r >= 0); + BOOST_TEST(r >= 0); auto count = x.cvisit(val.first, const_visitor); @@ -137,7 +137,7 @@ namespace { values, [&x, &mut_visitor, &total_count](boost::span s) { for (auto const& val : s) { auto r = val.first.x_; - BOOST_ASSERT(r >= 0); + BOOST_TEST(r >= 0); auto count = x.visit(val.first.x_, mut_visitor); @@ -161,7 +161,7 @@ namespace { values, [&x, &const_visitor, &total_count](boost::span s) { for (auto const& val : s) { auto r = val.first.x_; - BOOST_ASSERT(r >= 0); + BOOST_TEST(r >= 0); auto const& y = x; auto count = y.visit(val.first.x_, const_visitor); @@ -186,7 +186,7 @@ namespace { values, [&x, &const_visitor, &total_count](boost::span s) { for (auto const& val : s) { auto r = val.first.x_; - BOOST_ASSERT(r >= 0); + BOOST_TEST(r >= 0); auto count = x.cvisit(val.first.x_, const_visitor); From 00c6a93a560d99c8405453b37fb814603a1e8187 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 10 Apr 2023 11:03:11 +0200 Subject: [PATCH 153/327] restored invocability static asserts --- .../boost/unordered/concurrent_flat_map.hpp | 48 ++++++++++++++----- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 043c34f3..10bfbba5 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -18,26 +18,48 @@ #include #include -#include #include #include +#include +#include #include #include +#include #include #define BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) \ - static_assert(boost::callable_traits::is_invocable::value, \ + static_assert(boost::unordered::detail::is_invocable::value, \ "The provided Callable must be invocable with `value_type&`"); #define BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) \ static_assert( \ - boost::callable_traits::is_invocable::value, \ + boost::unordered::detail::is_invocable::value, \ "The provided Callable must be invocable with `value_type const&`"); +#define BOOST_UNORDERED_COMMA , + +#define BOOST_UNORDERED_LAST_ARG(Arg, Args) \ +mp11::mp_back> + +#define BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args) \ +BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(BOOST_UNORDERED_LAST_ARG(Arg, Args)) + +#define BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args) \ +BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE( \ + BOOST_UNORDERED_LAST_ARG(Arg, Args)) + namespace boost { namespace unordered { namespace detail { + template + struct is_invocable: + std::is_constructible< + std::function, + std::reference_wrapper::type> + > + {}; + template struct concurrent_map_types { using key_type = Key; @@ -364,7 +386,7 @@ namespace boost { template bool emplace_or_visit(Arg&& arg, Args&&... args) { - //BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...) return table_.emplace_or_visit( std::forward(arg), std::forward(args)...); } @@ -372,7 +394,7 @@ namespace boost { template bool emplace_or_cvisit(Arg&& arg, Args&&... args) { - //BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...) return table_.emplace_or_cvisit( std::forward(arg), std::forward(args)...); } @@ -400,7 +422,7 @@ namespace boost { template bool try_emplace_or_visit(key_type const& k, Arg&& arg, Args&&... args) { - //BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...) return table_.try_emplace_or_visit( k, std::forward(arg), std::forward(args)...); } @@ -408,7 +430,7 @@ namespace boost { template bool try_emplace_or_cvisit(key_type const& k, Arg&& arg, Args&&... args) { - //BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...) return table_.try_emplace_or_cvisit( k, std::forward(arg), std::forward(args)...); } @@ -416,7 +438,7 @@ namespace boost { template bool try_emplace_or_visit(key_type&& k, Arg&& arg, Args&&... args) { - //BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...) return table_.try_emplace_or_visit( std::move(k), std::forward(arg), std::forward(args)...); } @@ -424,7 +446,7 @@ namespace boost { template bool try_emplace_or_cvisit(key_type&& k, Arg&& arg, Args&&... args) { - //BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...) return table_.try_emplace_or_cvisit( std::move(k), std::forward(arg), std::forward(args)...); } @@ -432,7 +454,7 @@ namespace boost { template bool try_emplace_or_visit(K&& k, Arg&& arg, Args&&... args) { - //BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...) return table_.try_emplace_or_visit( std::forward(k), std::forward(arg), std::forward(args)...); } @@ -440,7 +462,7 @@ namespace boost { template bool try_emplace_or_cvisit(K&& k, Arg&& arg, Args&&... args) { - //BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...) return table_.try_emplace_or_cvisit( std::forward(k), std::forward(arg), std::forward(args)...); } @@ -492,5 +514,9 @@ namespace boost { #undef BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE #undef BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE +#undef BOOST_UNORDERED_COMMA +#undef BOOST_UNORDERED_LAST_ARG +#undef BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE +#undef BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE #endif // BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP From 2226a7238d310198060ec3f6fea23793480f155b Mon Sep 17 00:00:00 2001 From: joaquintides Date: Tue, 11 Apr 2023 16:32:47 +0200 Subject: [PATCH 154/327] adjusted arg constness in erase and erase_if --- include/boost/unordered/detail/foa/concurrent_table.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 4517ae5c..d51f5279 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -537,13 +537,13 @@ public: } template - BOOST_FORCEINLINE std::size_t erase(Key&& x) + BOOST_FORCEINLINE std::size_t erase(const Key& x) { - return erase_if(std::forward(x),[](const value_type&){return true;}); + return erase_if(x,[](const value_type&){return true;}); } template - BOOST_FORCEINLINE auto erase_if(Key&& x,F&& f)->typename std::enable_if< + BOOST_FORCEINLINE auto erase_if(const Key& x,F&& f)->typename std::enable_if< !is_execution_policy::value,std::size_t>::type { auto lck=shared_access(); From b771278813406a50934ed91773724a440ad0f98e Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 12 Apr 2023 12:22:38 -0700 Subject: [PATCH 155/327] Fix copy assignment warning in gcc --- .../unordered/detail/foa/concurrent_table.hpp | 8 +++++ include/boost/unordered/detail/foa/core.hpp | 32 +++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index d51f5279..db09b9ca 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -173,7 +173,15 @@ struct atomic_integral void operator|=(Integral m){n.fetch_or(m,std::memory_order_relaxed);} void operator&=(Integral m){n.fetch_and(m,std::memory_order_relaxed);} + atomic_integral& operator=(atomic_integral const& rhs) { + if(this!=&rhs){ + n.store(rhs.n.load(std::memory_order_relaxed),std::memory_order_relaxed); + } + return *this; + } + std::atomic n; + }; /* Group-level concurrency protection. It provides a rw mutex plus an diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index b7c546c1..0c26699b 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1773,9 +1773,7 @@ private: { if(arrays.elements){ copy_elements_array_from(x); - std::memcpy( - arrays.groups,x.arrays.groups, - (arrays.groups_size_mask+1)*sizeof(group_type)); + copy_groups_array_from(x); size_=std::size_t(x.size_); } } @@ -1833,6 +1831,34 @@ private: BOOST_CATCH_END } + void copy_groups_array_from(const table_core& x) { + copy_groups_array_from(x, std::integral_constant::value +#else + std::is_trivially_copyable::value +#endif + >{} + ); + } + + void copy_groups_array_from( + const table_core& x, std::true_type /* -> memcpy */) + { + std::memcpy( + arrays.groups,x.arrays.groups, + (arrays.groups_size_mask+1)*sizeof(group_type)); + } + + void copy_groups_array_from( + const table_core& x, std::false_type /* -> manual */) + { + for(std::size_t i=0;i Date: Wed, 12 Apr 2023 12:22:59 -0700 Subject: [PATCH 156/327] Begin work on constructors --- .../boost/unordered/concurrent_flat_map.hpp | 77 ++++-- test/Jamfile.v2 | 9 +- test/cfoa/constructor_tests.cpp | 256 ++++++++++++++++++ test/cfoa/helpers.hpp | 71 +++++ 4 files changed, 388 insertions(+), 25 deletions(-) create mode 100644 test/cfoa/constructor_tests.cpp diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 10bfbba5..952cb873 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -39,26 +39,26 @@ #define BOOST_UNORDERED_COMMA , -#define BOOST_UNORDERED_LAST_ARG(Arg, Args) \ -mp11::mp_back> +#define BOOST_UNORDERED_LAST_ARG(Arg, Args) \ + mp11::mp_back > -#define BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args) \ -BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(BOOST_UNORDERED_LAST_ARG(Arg, Args)) +#define BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args) \ + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(BOOST_UNORDERED_LAST_ARG(Arg, Args)) -#define BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args) \ -BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE( \ - BOOST_UNORDERED_LAST_ARG(Arg, Args)) +#define BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args) \ + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE( \ + BOOST_UNORDERED_LAST_ARG(Arg, Args)) namespace boost { namespace unordered { namespace detail { + template - struct is_invocable: - std::is_constructible< - std::function, - std::reference_wrapper::type> - > - {}; + struct is_invocable + : std::is_constructible, + std::reference_wrapper::type> > + { + }; template struct concurrent_map_types { @@ -142,7 +142,11 @@ namespace boost { using const_pointer = typename boost::allocator_const_pointer::type; - concurrent_flat_map() : concurrent_flat_map(0) {} + concurrent_flat_map() + : concurrent_flat_map(detail::foa::default_bucket_count) + { + } + explicit concurrent_flat_map(size_type n, const hasher& hf = hasher(), const key_equal& eql = key_equal(), const allocator_type& a = allocator_type()) @@ -150,6 +154,23 @@ namespace boost { { } + template + concurrent_flat_map(InputIterator f, InputIterator l, + size_type n = detail::foa::default_bucket_count, + const hasher& hf = hasher(), const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()) + : table_(n, hf, eql, a) + { + this->insert(f, l); + } + + concurrent_flat_map(concurrent_flat_map const& rhs) + : table_(rhs.table_, + boost::allocator_select_on_container_copy_construction( + rhs.get_allocator())) + { + } + /// Capacity /// @@ -276,15 +297,13 @@ namespace boost { template bool insert_or_assign(key_type const& k, M&& obj) { - return table_.try_emplace_or_visit( - k, std::forward(obj), + return table_.try_emplace_or_visit(k, std::forward(obj), [&](value_type& m) { m.second = std::forward(obj); }); } template bool insert_or_assign(key_type&& k, M&& obj) { - return table_.try_emplace_or_visit( - std::move(k), std::forward(obj), + return table_.try_emplace_or_visit(std::move(k), std::forward(obj), [&](value_type& m) { m.second = std::forward(obj); }); } @@ -293,8 +312,8 @@ namespace boost { detail::are_transparent::value, bool>::type insert_or_assign(K&& k, M&& obj) { - return table_.try_emplace_or_visit( - std::forward(k), std::forward(obj), + return table_.try_emplace_or_visit(std::forward(k), + std::forward(obj), [&](value_type& m) { m.second = std::forward(obj); }); } @@ -455,16 +474,16 @@ namespace boost { bool try_emplace_or_visit(K&& k, Arg&& arg, Args&&... args) { BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...) - return table_.try_emplace_or_visit( - std::forward(k), std::forward(arg), std::forward(args)...); + return table_.try_emplace_or_visit(std::forward(k), + std::forward(arg), std::forward(args)...); } template bool try_emplace_or_cvisit(K&& k, Arg&& arg, Args&&... args) { BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...) - return table_.try_emplace_or_cvisit( - std::forward(k), std::forward(arg), std::forward(args)...); + return table_.try_emplace_or_cvisit(std::forward(k), + std::forward(arg), std::forward(args)...); } size_type erase(key_type const& k) { return table_.erase(k); } @@ -508,6 +527,16 @@ namespace boost { /// void rehash(size_type n) { table_.rehash(n); } void reserve(size_type n) { table_.reserve(n); } + + /// Observers + /// + allocator_type get_allocator() const noexcept + { + return table_.get_allocator(); + } + + hasher hash_function() const { return table_.hash_function(); } + key_equal key_eq() const { return table_.key_eq(); } }; } // namespace unordered } // namespace boost diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index ca00ff4f..604eedf5 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -175,7 +175,14 @@ alias foa_tests : foa_merge_exception_tests ; -local CFOA_TESTS = insert_tests erase_tests try_emplace_tests emplace_tests visit_tests ; +local CFOA_TESTS = + insert_tests + erase_tests + try_emplace_tests + emplace_tests + visit_tests + constructor_tests +; for local test in $(CFOA_TESTS) { diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp new file mode 100644 index 00000000..246a641f --- /dev/null +++ b/test/cfoa/constructor_tests.cpp @@ -0,0 +1,256 @@ +// 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(4122023); + +using test::default_generator; +using test::limited_range; +using test::sequential; + +using hasher = stateful_hash; +using key_equal = stateful_key_equal; +using allocator_type = std::allocator >; + +using map_type = boost::unordered::concurrent_flat_map; + +UNORDERED_AUTO_TEST (default_constructor) { + boost::unordered::concurrent_flat_map x; + BOOST_TEST(x.empty()); + BOOST_TEST_EQ(x.size(), 0u); +} + +UNORDERED_AUTO_TEST (bucket_count_with_hasher_key_equal_and_allocator) { + raii::reset_counts(); + { + map_type x(0); + + BOOST_TEST(x.empty()); + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + } + + { + map_type x(0, hasher(1)); + + BOOST_TEST(x.empty()); + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + } + + { + map_type x(0, hasher(1), key_equal(2)); + + BOOST_TEST(x.empty()); + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + } + + { + map_type x(0, hasher(1), key_equal(2), allocator_type{}); + + BOOST_TEST(x.empty()); + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + BOOST_TEST(x.get_allocator() == allocator_type{}); + } + raii::reset_counts(); +} + +namespace { + template void from_iterator_range(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + map_type x(values.begin(), values.end()); + + test_matches_reference(x, reference_map); + BOOST_TEST_GT(x.size(), 0u); + BOOST_TEST_LE(x.size(), values.size()); + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + BOOST_TEST(x.get_allocator() == allocator_type{}); + if (rg == sequential) { + BOOST_TEST_EQ(x.size(), values.size()); + } + raii::reset_counts(); + } + + { + map_type x(values.begin(), values.end(), 0); + + test_matches_reference(x, reference_map); + BOOST_TEST_GT(x.size(), 0u); + BOOST_TEST_LE(x.size(), values.size()); + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + BOOST_TEST(x.get_allocator() == allocator_type{}); + if (rg == sequential) { + BOOST_TEST_EQ(x.size(), values.size()); + } + raii::reset_counts(); + } + + { + map_type x(values.begin(), values.end(), 0, hasher(1)); + + test_matches_reference(x, reference_map); + BOOST_TEST_GT(x.size(), 0u); + BOOST_TEST_LE(x.size(), values.size()); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + BOOST_TEST(x.get_allocator() == allocator_type{}); + if (rg == sequential) { + BOOST_TEST_EQ(x.size(), values.size()); + } + raii::reset_counts(); + } + + { + map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2)); + + test_matches_reference(x, reference_map); + BOOST_TEST_GT(x.size(), 0u); + BOOST_TEST_LE(x.size(), values.size()); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + BOOST_TEST(x.get_allocator() == allocator_type{}); + if (rg == sequential) { + BOOST_TEST_EQ(x.size(), values.size()); + } + raii::reset_counts(); + } + + { + map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2), + allocator_type{}); + + test_matches_reference(x, reference_map); + BOOST_TEST_GT(x.size(), 0u); + BOOST_TEST_LE(x.size(), values.size()); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + BOOST_TEST(x.get_allocator() == allocator_type{}); + if (rg == sequential) { + BOOST_TEST_EQ(x.size(), values.size()); + } + raii::reset_counts(); + } + } + + template void copy_constructor(G gen, test::random_generator rg) + { + { + map_type x(0, hasher(1), key_equal(2), allocator_type{}); + map_type y(x); + + BOOST_TEST_EQ(y.size(), x.size()); + BOOST_TEST_EQ(y.hash_function(), x.hash_function()); + BOOST_TEST_EQ(y.key_eq(), x.key_eq()); + BOOST_TEST(y.get_allocator() == x.get_allocator()); + } + + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2), + allocator_type{}); + + thread_runner( + values, [&x, &reference_map]( + boost::span s) { + (void)s; + map_type y(x); + + test_matches_reference(x, reference_map); + test_matches_reference(y, reference_map); + BOOST_TEST_EQ(y.size(), x.size()); + BOOST_TEST_EQ(y.hash_function(), x.hash_function()); + BOOST_TEST_EQ(y.key_eq(), x.key_eq()); + BOOST_TEST(y.get_allocator() == x.get_allocator()); + }); + } + } + + template + void copy_constructor_with_insertion(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + map_type x(0, hasher(1), key_equal(2), allocator_type{}); + + auto f = [&x, &values] { + std::this_thread::sleep_for(std::chrono::milliseconds(75)); + for (auto const& val : values) { + x.insert(val); + } + }; + + std::thread t1(f); + std::thread t2(f); + + thread_runner( + values, [&x, &reference_map, &values, rg]( + boost::span s) { + (void)s; + map_type y(x); + BOOST_TEST_GT(y.size(), 0u); + BOOST_TEST_LE(y.size(), values.size()); + BOOST_TEST_EQ(y.hash_function(), x.hash_function()); + BOOST_TEST_EQ(y.key_eq(), x.key_eq()); + BOOST_TEST(y.get_allocator() == x.get_allocator()); + + x.visit_all([&reference_map, rg]( + typename map_type::value_type const& val) { + BOOST_TEST(reference_map.contains(val.first)); + if (rg == sequential) { + BOOST_TEST_EQ(val.second, reference_map.find(val.first)->second); + } + }); + }); + + t1.join(); + t2.join(); + } + } + +} // namespace + +// clang-format off +UNORDERED_TEST( + from_iterator_range, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + copy_constructor, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + copy_constructor_with_insertion, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) +// clang-format on + +RUN_TESTS() diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index 8fb4fdd3..01f15ccf 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -36,6 +36,67 @@ struct transp_key_equal } }; +struct stateful_hash +{ + int x_ = -1; + + stateful_hash() = default; + stateful_hash(stateful_hash const&) = default; + stateful_hash(stateful_hash&& rhs) noexcept + { + auto tmp = x_; + x_ = rhs.x_; + rhs.x_ = tmp; + } + + stateful_hash(int const x) : x_{x} {} + + template std::size_t operator()(T const& t) const noexcept + { + std::size_t h = static_cast(x_); + boost::hash_combine(h, t); + return h; + } + + bool operator==(stateful_hash const& rhs) const { return x_ == rhs.x_; } + + friend std::ostream& operator<<(std::ostream& os, stateful_hash const& rhs) + { + os << "{ x_: " << rhs.x_ << " }"; + return os; + } +}; + +struct stateful_key_equal +{ + int x_ = -1; + + stateful_key_equal() = default; + stateful_key_equal(stateful_key_equal const&) = default; + stateful_key_equal(stateful_key_equal&& rhs) noexcept + { + auto tmp = x_; + x_ = rhs.x_; + rhs.x_ = tmp; + } + + stateful_key_equal(int const x) : x_{x} {} + + template bool operator()(T const& t, U const& u) const + { + return t == u; + } + + bool operator==(stateful_key_equal const& rhs) const { return x_ == rhs.x_; } + + friend std::ostream& operator<<( + std::ostream& os, stateful_key_equal const& rhs) + { + os << "{ x_: " << rhs.x_ << " }"; + return os; + } +}; + struct raii { static std::atomic default_constructor; @@ -226,4 +287,14 @@ template void thread_runner(std::vector& values, F f) } } +template +void test_matches_reference(X const& x, Y const& reference_map) +{ + using value_type = typename X::value_type; + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second); + })); +} + #endif // BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP \ No newline at end of file From 0898219edc0c4dea745a1996ac7a994e67199f95 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 12 Apr 2023 12:23:16 -0700 Subject: [PATCH 157/327] Test empty visitation --- test/cfoa/visit_tests.cpp | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/cfoa/visit_tests.cpp b/test/cfoa/visit_tests.cpp index 3dacfd57..1f18562a 100644 --- a/test/cfoa/visit_tests.cpp +++ b/test/cfoa/visit_tests.cpp @@ -376,6 +376,51 @@ namespace { raii::destructor); } + template + void empty_visit(X*, G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + raii::reset_counts(); + + { + X x; + + std::uint64_t old_default_constructor = raii::default_constructor; + std::uint64_t old_copy_constructor = raii::copy_constructor; + std::uint64_t old_move_constructor = raii::move_constructor; + std::uint64_t old_copy_assignment = raii::copy_assignment; + std::uint64_t old_move_assignment = raii::move_assignment; + + { + thread_runner( + values, [&x](boost::span s) { + std::atomic num_visits{0}; + + x.visit_all( + [&num_visits](typename X::value_type const&) { ++num_visits; }); + BOOST_TEST_EQ(num_visits, 0u); + + for (auto const& val : s) { + auto count = x.visit(val.first, + [&num_visits](typename X::value_type const&) { ++num_visits; }); + BOOST_TEST_EQ(count, 0u); + } + }); + } + + BOOST_TEST_EQ(old_default_constructor, raii::default_constructor); + BOOST_TEST_EQ(old_copy_constructor, raii::copy_constructor); + BOOST_TEST_EQ(old_move_constructor, raii::move_constructor); + BOOST_TEST_EQ(old_copy_assignment, raii::copy_assignment); + BOOST_TEST_EQ(old_move_assignment, raii::move_assignment); + } + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_EQ(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::destructor, 0u); + } + boost::unordered::concurrent_flat_map* map; boost::unordered::concurrent_flat_map* transp_map; @@ -402,6 +447,13 @@ UNORDERED_TEST( ((transp_visitor)) ((default_generator)(sequential)(limited_range))) +UNORDERED_TEST( + empty_visit, + ((map)(transp_map)) + ((value_type_generator)(init_type_generator)) + ((default_generator)(sequential)(limited_range)) +) + // clang-format on RUN_TESTS() From ecd4a82ed522e4b5ccbd8a0bd2ed1f44c03da14d Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 12 Apr 2023 13:58:01 -0700 Subject: [PATCH 158/327] Add test for select_on_container_copy_construction Allocator requirement --- test/cfoa/constructor_tests.cpp | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index 246a641f..1ddb09b8 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -12,6 +12,38 @@ using test::default_generator; using test::limited_range; using test::sequential; +template struct soccc_allocator +{ + int x_ = -1; + + using value_type = T; + + soccc_allocator() = default; + soccc_allocator(soccc_allocator const&) = default; + soccc_allocator(soccc_allocator&&) = default; + + soccc_allocator(int const x) : x_{x} {} + + template soccc_allocator(soccc_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); } + + soccc_allocator select_on_container_copy_construction() const + { + return {x_ + 1}; + } + + bool operator==(soccc_allocator const& rhs) const { return x_ == rhs.x_; } + bool operator!=(soccc_allocator const& rhs) const { return x_ != rhs.x_; } +}; + using hasher = stateful_hash; using key_equal = stateful_key_equal; using allocator_type = std::allocator >; @@ -66,6 +98,20 @@ UNORDERED_AUTO_TEST (bucket_count_with_hasher_key_equal_and_allocator) { raii::reset_counts(); } +UNORDERED_AUTO_TEST (soccc) { + boost::unordered::concurrent_flat_map > > + x; + + boost::unordered::concurrent_flat_map > > + y(x); + + BOOST_TEST_EQ(y.hash_function(), x.hash_function()); + BOOST_TEST_EQ(y.key_eq(), x.key_eq()); + BOOST_TEST(y.get_allocator() != x.get_allocator()); +} + namespace { template void from_iterator_range(G gen, test::random_generator rg) { From d8bc38f154325f81f05c1fc17bc3605706946525 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 12 Apr 2023 14:41:36 -0700 Subject: [PATCH 159/327] Clean up tag dispatch for group_type copy assignment --- include/boost/unordered/detail/foa/concurrent_table.hpp | 4 +--- include/boost/unordered/detail/foa/core.hpp | 7 +------ 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index db09b9ca..04168d82 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -174,9 +174,7 @@ struct atomic_integral void operator&=(Integral m){n.fetch_and(m,std::memory_order_relaxed);} atomic_integral& operator=(atomic_integral const& rhs) { - if(this!=&rhs){ - n.store(rhs.n.load(std::memory_order_relaxed),std::memory_order_relaxed); - } + n.store(rhs.n.load(std::memory_order_relaxed),std::memory_order_relaxed); return *this; } diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 0c26699b..eff1ccb6 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1833,12 +1833,7 @@ private: void copy_groups_array_from(const table_core& x) { copy_groups_array_from(x, std::integral_constant::value -#else - std::is_trivially_copyable::value -#endif + std::is_trivially_copy_assignable::value >{} ); } From ab84a922cf88954853b48a8bb5125ef0bd4eb23c Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 13 Apr 2023 13:00:29 +0200 Subject: [PATCH 160/327] added workaround for lack of std::is_trivially_copy_assignable in GCC<5.0 --- include/boost/unordered/detail/foa/core.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index eff1ccb6..cc66393a 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -1833,7 +1834,12 @@ private: void copy_groups_array_from(const table_core& x) { copy_groups_array_from(x, std::integral_constant::value +#else std::is_trivially_copy_assignable::value +#endif >{} ); } From 0e5ef25a69d5e67ca838e039bb11f2d55930c2b2 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 13 Apr 2023 19:31:20 +0200 Subject: [PATCH 161/327] added workaround for GCC<=5 having problems with return {...} syntax --- include/boost/unordered/detail/foa/tuple_rotate_right.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/detail/foa/tuple_rotate_right.hpp b/include/boost/unordered/detail/foa/tuple_rotate_right.hpp index c63a7fb5..2774cbbb 100644 --- a/include/boost/unordered/detail/foa/tuple_rotate_right.hpp +++ b/include/boost/unordered/detail/foa/tuple_rotate_right.hpp @@ -24,8 +24,12 @@ auto tuple_rotate_right_aux(mp11::index_sequence,Tuple&& x) std::get<(Is+sizeof...(Is)-1)%sizeof...(Is)>(std::forward(x)))... > { - return { - std::get<(Is+sizeof...(Is)-1)%sizeof...(Is)>(std::forward(x))...}; + return std::tuple(std::forward(x)))... + > + { + std::get<(Is+sizeof...(Is)-1)%sizeof...(Is)>(std::forward(x))... + }; } template From 6bf0e93a41d8dbb58d95f33f6e457212030a5e0b Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 13 Apr 2023 13:30:56 -0700 Subject: [PATCH 162/327] Remove size check as the insertion thread may not have started by the time the check happens --- test/cfoa/constructor_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index 1ddb09b8..bc5875eb 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -260,7 +260,7 @@ namespace { boost::span s) { (void)s; map_type y(x); - BOOST_TEST_GT(y.size(), 0u); + BOOST_TEST_LE(y.size(), values.size()); BOOST_TEST_EQ(y.hash_function(), x.hash_function()); BOOST_TEST_EQ(y.key_eq(), x.key_eq()); From 1723358298ca0a3bbad9ff914d5e8eba160a4b3c Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 14 Apr 2023 10:48:26 +0200 Subject: [PATCH 163/327] refactored to deICE Clang<=3.8 and for clarity --- .../detail/foa/tuple_rotate_right.hpp | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/include/boost/unordered/detail/foa/tuple_rotate_right.hpp b/include/boost/unordered/detail/foa/tuple_rotate_right.hpp index 2774cbbb..c95077b2 100644 --- a/include/boost/unordered/detail/foa/tuple_rotate_right.hpp +++ b/include/boost/unordered/detail/foa/tuple_rotate_right.hpp @@ -9,6 +9,7 @@ #ifndef BOOST_UNORDERED_DETAIL_FOA_TUPLE_ROTATE_RIGHT_HPP #define BOOST_UNORDERED_DETAIL_FOA_TUPLE_ROTATE_RIGHT_HPP +#include #include #include #include @@ -18,28 +19,28 @@ namespace unordered{ namespace detail{ namespace foa{ +template +using tuple_rotate_right_return_type=mp11::mp_rotate_right_c< + typename std::remove_cv::type>::type, + 1 +>; + template -auto tuple_rotate_right_aux(mp11::index_sequence,Tuple&& x) - ->std::tuple(std::forward(x)))... - > +tuple_rotate_right_return_type +tuple_rotate_right_aux(mp11::index_sequence,Tuple&& x) { - return std::tuple(std::forward(x)))... - > - { - std::get<(Is+sizeof...(Is)-1)%sizeof...(Is)>(std::forward(x))... - }; + return tuple_rotate_right_return_type{ + std::get<(Is+sizeof...(Is)-1)%sizeof...(Is)>(std::forward(x))...}; } template -auto tuple_rotate_right(Tuple&& x) - ->decltype(tuple_rotate_right_aux( - mp11::make_index_sequence::value>{}, - std::forward(x))) +tuple_rotate_right_return_type tuple_rotate_right(Tuple&& x) { + using RawTuple=typename std::remove_cv< + typename std::remove_reference::type>::type; + return tuple_rotate_right_aux( - mp11::make_index_sequence::value>{}, + mp11::make_index_sequence::value>{}, std::forward(x)); } From b1a52d0236d234c944b49d2e6fe3c60c92e15d5c Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 15 Apr 2023 09:51:01 +0200 Subject: [PATCH 164/327] explicitly declared guards' copy ctors as it prevents VS from doing spurious copies (!) --- include/boost/unordered/detail/foa/concurrent_table.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 04168d82..922d81d8 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -106,6 +106,9 @@ public: shared_lock(Mutex& m_)noexcept:m{m_}{m.lock_shared();} ~shared_lock()noexcept{if(owns)m.unlock_shared();} + /* not used but VS in pre-C++17 mode needs to see it for RVO */ + shared_lock(const shared_lock&); + void lock(){BOOST_ASSERT(!owns);m.lock_shared();owns=true;} void unlock(){BOOST_ASSERT(owns);m.unlock_shared();owns=false;} @@ -125,6 +128,9 @@ public: lock_guard(Mutex& m_)noexcept:m{m_}{m.lock();} ~lock_guard()noexcept{m.unlock();} + /* not used but VS in pre-C++17 mode needs to see it for RVO */ + lock_guard(const lock_guard&); + private: Mutex &m; }; From 9a0e5e9ea8250dbba08cc0e7f638e386813ddf0d Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 15 Apr 2023 12:13:40 +0200 Subject: [PATCH 165/327] dispensed with std::array --- include/boost/unordered/detail/foa/concurrent_table.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 922d81d8..4f73f44c 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -11,7 +11,6 @@ #ifndef BOOST_UNORDERED_DETAIL_FOA_CONCURRENT_TABLE_HPP #define BOOST_UNORDERED_DETAIL_FOA_CONCURRENT_TABLE_HPP -#include #include #include #include @@ -94,7 +93,7 @@ public: void unlock()noexcept{for(auto n=N;n>0;)mutexes[--n].unlock();} private: - std::array mutexes; + Mutex mutexes[N]; }; /* std::shared_lock is C++14 */ From a9ad06139e84e190095e07391bca0d79d0305659 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 18 Apr 2023 12:00:08 -0700 Subject: [PATCH 166/327] Add move constructor --- .../boost/unordered/concurrent_flat_map.hpp | 5 + test/cfoa/constructor_tests.cpp | 123 ++++++++++++++++++ 2 files changed, 128 insertions(+) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 952cb873..3085e767 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -171,6 +171,11 @@ namespace boost { { } + concurrent_flat_map(concurrent_flat_map&& rhs) + : table_(std::move(rhs.table_)) + { + } + /// Capacity /// diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index bc5875eb..d3caf019 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -51,6 +51,8 @@ using allocator_type = std::allocator >; using map_type = boost::unordered::concurrent_flat_map; +using value_type = typename map_type::value_type; + UNORDERED_AUTO_TEST (default_constructor) { boost::unordered::concurrent_flat_map x; BOOST_TEST(x.empty()); @@ -280,6 +282,117 @@ namespace { } } + template void move_constructor(G gen, test::random_generator rg) + { + { + map_type x(0, hasher(1), key_equal(2), allocator_type{}); + auto const old_size = x.size(); + + map_type y(std::move(x)); + + BOOST_TEST_EQ(y.size(), old_size); + BOOST_TEST_EQ(y.hash_function(), hasher(1)); + BOOST_TEST_EQ(y.key_eq(), key_equal(2)); + + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + + BOOST_TEST(y.get_allocator() == x.get_allocator()); + } + + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2), + allocator_type{}); + + std::atomic_int num_transfers{0}; + + thread_runner( + values, [&x, &reference_map, &num_transfers]( + boost::span s) { + (void)s; + + auto const old_size = x.size(); + map_type y(std::move(x)); + + if (!y.empty()) { + ++num_transfers; + + test_matches_reference(y, reference_map); + BOOST_TEST_EQ(y.size(), old_size); + BOOST_TEST_EQ(y.hash_function(), hasher(1)); + BOOST_TEST_EQ(y.key_eq(), key_equal(2)); + } else { + BOOST_TEST_EQ(y.size(), 0u); + BOOST_TEST_EQ(y.hash_function(), hasher()); + BOOST_TEST_EQ(y.key_eq(), key_equal()); + } + + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + + BOOST_TEST(y.get_allocator() == x.get_allocator()); + }); + + BOOST_TEST_EQ(num_transfers, 1u); + } + } + + template + void move_constructor_with_insertion(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + map_type x(0, hasher(1), key_equal(2), allocator_type{}); + + auto f = [&x, &values] { + std::this_thread::sleep_for(std::chrono::milliseconds(95)); + for (auto const& val : values) { + x.insert(val); + } + }; + + std::atomic_int num_transfers{0}; + + std::thread t1(f); + std::thread t2(f); + + thread_runner( + values, [&x, &reference_map, &num_transfers, rg]( + boost::span s) { + (void)s; + + map_type y(std::move(x)); + + if (!y.empty()) { + ++num_transfers; + y.cvisit_all([&reference_map, rg](value_type const& val) { + BOOST_TEST(reference_map.contains(val.first)); + if (rg == sequential) { + BOOST_TEST_EQ( + val.second, reference_map.find(val.first)->second); + } + }); + } + }); + + t1.join(); + t2.join(); + + BOOST_TEST_GE(num_transfers, 1u); + } + } + } // namespace // clang-format off @@ -297,6 +410,16 @@ UNORDERED_TEST( copy_constructor_with_insertion, ((value_type_generator)) ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + move_constructor, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + move_constructor_with_insertion, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) // clang-format on RUN_TESTS() From 68c018fda69d3bae685b815a64203ff58e010272 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 18 Apr 2023 12:30:34 -0700 Subject: [PATCH 167/327] Clean up usage of decltype to help msvc-14.3 --- test/cfoa/visit_tests.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/test/cfoa/visit_tests.cpp b/test/cfoa/visit_tests.cpp index 1f18562a..4c22782c 100644 --- a/test/cfoa/visit_tests.cpp +++ b/test/cfoa/visit_tests.cpp @@ -380,6 +380,9 @@ namespace { void empty_visit(X*, G gen, test::random_generator rg) { auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + using values_type = decltype(values); + using span_value_type = typename values_type::value_type; + raii::reset_counts(); { @@ -392,20 +395,19 @@ namespace { std::uint64_t old_move_assignment = raii::move_assignment; { - thread_runner( - values, [&x](boost::span s) { - std::atomic num_visits{0}; + thread_runner(values, [&x](boost::span s) { + std::atomic num_visits{0}; - x.visit_all( + x.visit_all( + [&num_visits](typename X::value_type const&) { ++num_visits; }); + BOOST_TEST_EQ(num_visits, 0u); + + for (auto const& val : s) { + auto count = x.visit(val.first, [&num_visits](typename X::value_type const&) { ++num_visits; }); - BOOST_TEST_EQ(num_visits, 0u); - - for (auto const& val : s) { - auto count = x.visit(val.first, - [&num_visits](typename X::value_type const&) { ++num_visits; }); - BOOST_TEST_EQ(count, 0u); - } - }); + BOOST_TEST_EQ(count, 0u); + } + }); } BOOST_TEST_EQ(old_default_constructor, raii::default_constructor); From 3aff995ae030d54709a423639b2f7df04bf59b3f Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 18 Apr 2023 13:38:24 -0700 Subject: [PATCH 168/327] Clean up constructor_tests --- test/cfoa/constructor_tests.cpp | 12 ++++++------ test/cfoa/helpers.hpp | 3 +++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index d3caf019..f5572c08 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -222,7 +222,7 @@ namespace { thread_runner( values, [&x, &reference_map]( - boost::span s) { + boost::span > s) { (void)s; map_type y(x); @@ -259,7 +259,7 @@ namespace { thread_runner( values, [&x, &reference_map, &values, rg]( - boost::span s) { + boost::span > s) { (void)s; map_type y(x); @@ -314,7 +314,7 @@ namespace { thread_runner( values, [&x, &reference_map, &num_transfers]( - boost::span s) { + boost::span > s) { (void)s; auto const old_size = x.size(); @@ -356,20 +356,20 @@ namespace { map_type x(0, hasher(1), key_equal(2), allocator_type{}); auto f = [&x, &values] { - std::this_thread::sleep_for(std::chrono::milliseconds(95)); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); for (auto const& val : values) { x.insert(val); } }; - std::atomic_int num_transfers{0}; + std::atomic_uint num_transfers{0}; std::thread t1(f); std::thread t2(f); thread_runner( values, [&x, &reference_map, &num_transfers, rg]( - boost::span s) { + boost::span > s) { (void)s; map_type y(std::move(x)); diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index 01f15ccf..0e2a3832 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -297,4 +297,7 @@ void test_matches_reference(X const& x, Y const& reference_map) })); } +template +using span_value_type = typename T::value_type; + #endif // BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP \ No newline at end of file From 910b8de6977de23215a0582316040782c898fa20 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 18 Apr 2023 15:11:53 -0700 Subject: [PATCH 169/327] Add iterator range + allocator constructor, continue to clean up constructor_tests --- .../boost/unordered/concurrent_flat_map.hpp | 6 +++ test/cfoa/constructor_tests.cpp | 53 ++++++++++++++++--- test/cfoa/helpers.hpp | 28 +++++++++- 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 3085e767..f87d3967 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -176,6 +176,12 @@ namespace boost { { } + template + concurrent_flat_map(InputIterator f, InputIterator l, allocator_type a) + : concurrent_flat_map(f, l, 0, hasher(), key_equal(), a) + { + } + /// Capacity /// diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index f5572c08..00d60072 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -97,10 +97,11 @@ UNORDERED_AUTO_TEST (bucket_count_with_hasher_key_equal_and_allocator) { BOOST_TEST_EQ(x.key_eq(), key_equal(2)); BOOST_TEST(x.get_allocator() == allocator_type{}); } - raii::reset_counts(); } UNORDERED_AUTO_TEST (soccc) { + raii::reset_counts(); + boost::unordered::concurrent_flat_map > > x; @@ -134,7 +135,6 @@ namespace { if (rg == sequential) { BOOST_TEST_EQ(x.size(), values.size()); } - raii::reset_counts(); } { @@ -149,7 +149,6 @@ namespace { if (rg == sequential) { BOOST_TEST_EQ(x.size(), values.size()); } - raii::reset_counts(); } { @@ -164,7 +163,6 @@ namespace { if (rg == sequential) { BOOST_TEST_EQ(x.size(), values.size()); } - raii::reset_counts(); } { @@ -179,7 +177,6 @@ namespace { if (rg == sequential) { BOOST_TEST_EQ(x.size(), values.size()); } - raii::reset_counts(); } { @@ -195,8 +192,9 @@ namespace { if (rg == sequential) { BOOST_TEST_EQ(x.size(), values.size()); } - raii::reset_counts(); } + + check_raii_counts(); } template void copy_constructor(G gen, test::random_generator rg) @@ -234,6 +232,8 @@ namespace { BOOST_TEST(y.get_allocator() == x.get_allocator()); }); } + + check_raii_counts(); } template @@ -280,6 +280,8 @@ namespace { t1.join(); t2.join(); } + + check_raii_counts(); } template void move_constructor(G gen, test::random_generator rg) @@ -342,6 +344,8 @@ namespace { BOOST_TEST_EQ(num_transfers, 1u); } + + check_raii_counts(); } template @@ -391,6 +395,38 @@ namespace { BOOST_TEST_GE(num_transfers, 1u); } + + check_raii_counts(); + } + + template + void iterator_range_with_allocator(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + + raii::reset_counts(); + + { + allocator_type a; + map_type x(values.begin(), values.end(), a); + + BOOST_TEST_GT(x.size(), 0u); + BOOST_TEST_LE(x.size(), values.size()); + if (rg == sequential) { + BOOST_TEST_EQ(x.size(), values.size()); + } + + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + + BOOST_TEST(x.get_allocator() == a); + + test_fuzzy_matches_reference(x, reference_map, rg); + } + + check_raii_counts(); } } // namespace @@ -420,6 +456,11 @@ UNORDERED_TEST( move_constructor_with_insertion, ((value_type_generator)) ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + iterator_range_with_allocator, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) // clang-format on RUN_TESTS() diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index 0e2a3832..02f22d86 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -297,7 +297,31 @@ void test_matches_reference(X const& x, Y const& reference_map) })); } -template -using span_value_type = typename T::value_type; +template +void test_fuzzy_matches_reference( + X const& x, Y const& reference_map, test::random_generator rg) +{ + using value_type = typename X::value_type; + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + if (rg == test::sequential) { + BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second); + } + })); +} + +template using span_value_type = typename T::value_type; + +void check_raii_counts() +{ + BOOST_TEST_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); + + BOOST_TEST_EQ( + raii::default_constructor + raii::copy_constructor + raii::move_constructor, + raii::destructor); +} #endif // BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP \ No newline at end of file From fb403bc233b81a31cb6d84c6f41733c609371b97 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 18 Apr 2023 15:36:12 -0700 Subject: [PATCH 170/327] Add explicit allocator constructor --- include/boost/unordered/concurrent_flat_map.hpp | 5 +++++ test/cfoa/constructor_tests.cpp | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index f87d3967..a7547b55 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -182,6 +182,11 @@ namespace boost { { } + explicit concurrent_flat_map(allocator_type a) + : table_(detail::foa::default_bucket_count, hasher(), key_equal(), a) + { + } + /// Capacity /// diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index 00d60072..e47cf1c1 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -312,7 +312,7 @@ namespace { map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2), allocator_type{}); - std::atomic_int num_transfers{0}; + std::atomic_uint num_transfers{0}; thread_runner( values, [&x, &reference_map, &num_transfers]( @@ -429,6 +429,21 @@ namespace { check_raii_counts(); } + UNORDERED_AUTO_TEST (explicit_allocator) { + raii::reset_counts(); + + { + allocator_type a; + map_type x(a); + + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + + BOOST_TEST(x.get_allocator() == a); + } + } + } // namespace // clang-format off From 37edc392a550357551a3b24c5aa3f6dc1eb252c9 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 19 Apr 2023 09:35:38 -0700 Subject: [PATCH 171/327] Add allocator-aware copy constructor --- .../boost/unordered/concurrent_flat_map.hpp | 5 ++++ test/cfoa/constructor_tests.cpp | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index a7547b55..47509962 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -187,6 +187,11 @@ namespace boost { { } + concurrent_flat_map(concurrent_flat_map const& rhs, allocator_type a) + : table_(rhs.table_, a) + { + } + /// Capacity /// diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index e47cf1c1..2d4fa8fd 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -212,6 +212,7 @@ namespace { auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); auto reference_map = boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); { @@ -234,6 +235,30 @@ namespace { } check_raii_counts(); + + raii::reset_counts(); + + { + allocator_type a; + + map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2), a); + + thread_runner( + values, [&x, &reference_map, a]( + boost::span > s) { + (void)s; + map_type y(x, a); + + test_matches_reference(x, reference_map); + test_matches_reference(y, reference_map); + BOOST_TEST_EQ(y.size(), x.size()); + BOOST_TEST_EQ(y.hash_function(), x.hash_function()); + BOOST_TEST_EQ(y.key_eq(), x.key_eq()); + BOOST_TEST(y.get_allocator() == x.get_allocator()); + }); + } + + check_raii_counts(); } template From b3c0368ab54bd638c660d60c4ee8097bc480c1c2 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 19 Apr 2023 09:35:47 -0700 Subject: [PATCH 172/327] Clean up typedef names to avoid conflicts --- test/cfoa/constructor_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index 2d4fa8fd..0fb5614b 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -51,7 +51,7 @@ using allocator_type = std::allocator >; using map_type = boost::unordered::concurrent_flat_map; -using value_type = typename map_type::value_type; +using map_value_type = typename map_type::value_type; UNORDERED_AUTO_TEST (default_constructor) { boost::unordered::concurrent_flat_map x; @@ -405,7 +405,7 @@ namespace { if (!y.empty()) { ++num_transfers; - y.cvisit_all([&reference_map, rg](value_type const& val) { + y.cvisit_all([&reference_map, rg](map_value_type const& val) { BOOST_TEST(reference_map.contains(val.first)); if (rg == sequential) { BOOST_TEST_EQ( From 8bd07e17c38b62369efbe9e720f28dff1371b688 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 19 Apr 2023 10:52:41 -0700 Subject: [PATCH 173/327] Add forced inlining to keep msvc benchmarks on par with raw concurrent_table --- .../boost/unordered/concurrent_flat_map.hpp | 165 +++++++++++------- 1 file changed, 106 insertions(+), 59 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 47509962..dffad274 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -202,26 +202,29 @@ namespace boost { return size() == 0; } - template std::size_t visit(key_type const& k, F f) + template + BOOST_FORCEINLINE std::size_t visit(key_type const& k, F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.visit(k, f); } - template std::size_t visit(key_type const& k, F f) const + template + BOOST_FORCEINLINE std::size_t visit(key_type const& k, F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.visit(k, f); } - template std::size_t cvisit(key_type const& k, F f) const + template + BOOST_FORCEINLINE std::size_t cvisit(key_type const& k, F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.visit(k, f); } template - typename std::enable_if< + BOOST_FORCEINLINE typename std::enable_if< detail::are_transparent::value, std::size_t>::type visit(K&& k, F f) { @@ -230,7 +233,7 @@ namespace boost { } template - typename std::enable_if< + BOOST_FORCEINLINE typename std::enable_if< detail::are_transparent::value, std::size_t>::type visit(K&& k, F f) const { @@ -239,7 +242,7 @@ namespace boost { } template - typename std::enable_if< + BOOST_FORCEINLINE typename std::enable_if< detail::are_transparent::value, std::size_t>::type cvisit(K&& k, F f) const { @@ -247,19 +250,19 @@ namespace boost { return table_.visit(std::forward(k), f); } - template std::size_t visit_all(F f) + template BOOST_FORCEINLINE std::size_t visit_all(F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.visit_all(f); } - template std::size_t visit_all(F f) const + template BOOST_FORCEINLINE std::size_t visit_all(F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.visit_all(f); } - template std::size_t cvisit_all(F f) const + template BOOST_FORCEINLINE std::size_t cvisit_all(F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.cvisit_all(f); @@ -267,27 +270,30 @@ namespace boost { #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) template - typename std::enable_if::value, - void>::type - visit_all(ExecPolicy p, F f) + BOOST_FORCEINLINE + typename std::enable_if::value, + void>::type + visit_all(ExecPolicy p, F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) table_.visit_all(p, f); } template - typename std::enable_if::value, - void>::type - visit_all(ExecPolicy p, F f) const + BOOST_FORCEINLINE + typename std::enable_if::value, + void>::type + visit_all(ExecPolicy p, F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) table_.visit_all(p, f); } template - typename std::enable_if::value, - void>::type - cvisit_all(ExecPolicy p, F f) const + BOOST_FORCEINLINE + typename std::enable_if::value, + void>::type + cvisit_all(ExecPolicy p, F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) table_.cvisit_all(p, f); @@ -297,39 +303,53 @@ namespace boost { /// Modifiers /// - bool insert(value_type const& obj) { return table_.insert(obj); } - bool insert(value_type&& obj) { return table_.insert(std::move(obj)); } + BOOST_FORCEINLINE bool insert(value_type const& obj) + { + return table_.insert(obj); + } + BOOST_FORCEINLINE bool insert(value_type&& obj) + { + return table_.insert(std::move(obj)); + } - bool insert(init_type const& obj) { return table_.insert(obj); } - bool insert(init_type&& obj) { return table_.insert(std::move(obj)); } + BOOST_FORCEINLINE bool insert(init_type const& obj) + { + return table_.insert(obj); + } + BOOST_FORCEINLINE bool insert(init_type&& obj) + { + return table_.insert(std::move(obj)); + } template - void insert(InputIterator begin, InputIterator end) + BOOST_FORCEINLINE void insert(InputIterator begin, InputIterator end) { for (auto pos = begin; pos != end; ++pos) { table_.insert(*pos); } } - void insert(std::initializer_list ilist) + BOOST_FORCEINLINE void insert(std::initializer_list ilist) { this->insert(ilist.begin(), ilist.end()); } - template bool insert_or_assign(key_type const& k, M&& obj) + template + BOOST_FORCEINLINE bool insert_or_assign(key_type const& k, M&& obj) { return table_.try_emplace_or_visit(k, std::forward(obj), [&](value_type& m) { m.second = std::forward(obj); }); } - template bool insert_or_assign(key_type&& k, M&& obj) + template + BOOST_FORCEINLINE bool insert_or_assign(key_type&& k, M&& obj) { return table_.try_emplace_or_visit(std::move(k), std::forward(obj), [&](value_type& m) { m.second = std::forward(obj); }); } template - typename std::enable_if< + BOOST_FORCEINLINE typename std::enable_if< detail::are_transparent::value, bool>::type insert_or_assign(K&& k, M&& obj) { @@ -338,32 +358,37 @@ namespace boost { [&](value_type& m) { m.second = std::forward(obj); }); } - template bool insert_or_visit(value_type const& obj, F f) + template + BOOST_FORCEINLINE bool insert_or_visit(value_type const& obj, F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.insert_or_visit(obj, f); } - template bool insert_or_visit(value_type&& obj, F f) + template + BOOST_FORCEINLINE bool insert_or_visit(value_type&& obj, F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.insert_or_visit(std::move(obj), f); } - template bool insert_or_visit(init_type const& obj, F f) + template + BOOST_FORCEINLINE bool insert_or_visit(init_type const& obj, F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.insert_or_visit(obj, f); } - template bool insert_or_visit(init_type&& obj, F f) + template + BOOST_FORCEINLINE bool insert_or_visit(init_type&& obj, F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.insert_or_visit(std::move(obj), f); } template - void insert_or_visit(InputIterator first, InputIterator last, F f) + BOOST_FORCEINLINE void insert_or_visit( + InputIterator first, InputIterator last, F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) for (; first != last; ++first) { @@ -372,38 +397,44 @@ namespace boost { } template - void insert_or_visit(std::initializer_list ilist, F f) + BOOST_FORCEINLINE void insert_or_visit( + std::initializer_list ilist, F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) this->insert_or_visit(ilist.begin(), ilist.end(), f); } - template bool insert_or_cvisit(value_type const& obj, F f) + template + BOOST_FORCEINLINE bool insert_or_cvisit(value_type const& obj, F f) { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.insert_or_cvisit(obj, f); } - template bool insert_or_cvisit(value_type&& obj, F f) + template + BOOST_FORCEINLINE bool insert_or_cvisit(value_type&& obj, F f) { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.insert_or_cvisit(std::move(obj), f); } - template bool insert_or_cvisit(init_type const& obj, F f) + template + BOOST_FORCEINLINE bool insert_or_cvisit(init_type const& obj, F f) { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.insert_or_cvisit(obj, f); } - template bool insert_or_cvisit(init_type&& obj, F f) + template + BOOST_FORCEINLINE bool insert_or_cvisit(init_type&& obj, F f) { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.insert_or_cvisit(std::move(obj), f); } template - void insert_or_cvisit(InputIterator first, InputIterator last, F f) + BOOST_FORCEINLINE void insert_or_cvisit( + InputIterator first, InputIterator last, F f) { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) for (; first != last; ++first) { @@ -412,19 +443,20 @@ namespace boost { } template - void insert_or_cvisit(std::initializer_list ilist, F f) + BOOST_FORCEINLINE void insert_or_cvisit( + std::initializer_list ilist, F f) { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) this->insert_or_visit(ilist.begin(), ilist.end(), f); } - template bool emplace(Args&&... args) + template BOOST_FORCEINLINE bool emplace(Args&&... args) { return table_.emplace(std::forward(args)...); } template - bool emplace_or_visit(Arg&& arg, Args&&... args) + BOOST_FORCEINLINE bool emplace_or_visit(Arg&& arg, Args&&... args) { BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...) return table_.emplace_or_visit( @@ -432,7 +464,7 @@ namespace boost { } template - bool emplace_or_cvisit(Arg&& arg, Args&&... args) + BOOST_FORCEINLINE bool emplace_or_cvisit(Arg&& arg, Args&&... args) { BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...) return table_.emplace_or_cvisit( @@ -440,18 +472,19 @@ namespace boost { } template - bool try_emplace(key_type const& k, Args&&... args) + BOOST_FORCEINLINE bool try_emplace(key_type const& k, Args&&... args) { return table_.try_emplace(k, std::forward(args)...); } - template bool try_emplace(key_type&& k, Args&&... args) + template + BOOST_FORCEINLINE bool try_emplace(key_type&& k, Args&&... args) { return table_.try_emplace(std::move(k), std::forward(args)...); } template - typename std::enable_if< + BOOST_FORCEINLINE typename std::enable_if< detail::are_transparent::value, bool>::type try_emplace(K&& k, Args&&... args) { @@ -460,7 +493,8 @@ namespace boost { } template - bool try_emplace_or_visit(key_type const& k, Arg&& arg, Args&&... args) + BOOST_FORCEINLINE bool try_emplace_or_visit( + key_type const& k, Arg&& arg, Args&&... args) { BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...) return table_.try_emplace_or_visit( @@ -468,7 +502,8 @@ namespace boost { } template - bool try_emplace_or_cvisit(key_type const& k, Arg&& arg, Args&&... args) + BOOST_FORCEINLINE bool try_emplace_or_cvisit( + key_type const& k, Arg&& arg, Args&&... args) { BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...) return table_.try_emplace_or_cvisit( @@ -476,7 +511,8 @@ namespace boost { } template - bool try_emplace_or_visit(key_type&& k, Arg&& arg, Args&&... args) + BOOST_FORCEINLINE bool try_emplace_or_visit( + key_type&& k, Arg&& arg, Args&&... args) { BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...) return table_.try_emplace_or_visit( @@ -484,7 +520,8 @@ namespace boost { } template - bool try_emplace_or_cvisit(key_type&& k, Arg&& arg, Args&&... args) + BOOST_FORCEINLINE bool try_emplace_or_cvisit( + key_type&& k, Arg&& arg, Args&&... args) { BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...) return table_.try_emplace_or_cvisit( @@ -492,7 +529,8 @@ namespace boost { } template - bool try_emplace_or_visit(K&& k, Arg&& arg, Args&&... args) + BOOST_FORCEINLINE bool try_emplace_or_visit( + K&& k, Arg&& arg, Args&&... args) { BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...) return table_.try_emplace_or_visit(std::forward(k), @@ -500,30 +538,35 @@ namespace boost { } template - bool try_emplace_or_cvisit(K&& k, Arg&& arg, Args&&... args) + BOOST_FORCEINLINE bool try_emplace_or_cvisit( + K&& k, Arg&& arg, Args&&... args) { BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...) return table_.try_emplace_or_cvisit(std::forward(k), std::forward(arg), std::forward(args)...); } - size_type erase(key_type const& k) { return table_.erase(k); } + BOOST_FORCEINLINE size_type erase(key_type const& k) + { + return table_.erase(k); + } template - typename std::enable_if< + BOOST_FORCEINLINE typename std::enable_if< detail::are_transparent::value, size_type>::type erase(K&& k) { return table_.erase(std::forward(k)); } - template size_type erase_if(key_type const& k, F f) + template + BOOST_FORCEINLINE size_type erase_if(key_type const& k, F f) { return table_.erase_if(k, f); } template - typename std::enable_if< + BOOST_FORCEINLINE typename std::enable_if< detail::are_transparent::value && !detail::is_execution_policy::value, size_type>::type @@ -534,15 +577,19 @@ namespace boost { #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) template - typename std::enable_if::value, - void>::type - erase_if(ExecPolicy p, F f) + BOOST_FORCEINLINE + typename std::enable_if::value, + void>::type + erase_if(ExecPolicy p, F f) { table_.erase_if(p, f); } #endif - template size_type erase_if(F f) { return table_.erase_if(f); } + template BOOST_FORCEINLINE size_type erase_if(F f) + { + return table_.erase_if(f); + } /// Hash Policy /// From 7812b26d3a44604a34cb82e62ec84ab3387d5e59 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 19 Apr 2023 15:18:53 -0700 Subject: [PATCH 174/327] Add allocator aware move constructors --- .../boost/unordered/concurrent_flat_map.hpp | 5 + include/boost/unordered/detail/foa/core.hpp | 21 ++- test/cfoa/constructor_tests.cpp | 132 +++++++++++++++++- 3 files changed, 154 insertions(+), 4 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index dffad274..62d24b52 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -192,6 +192,11 @@ namespace boost { { } + concurrent_flat_map(concurrent_flat_map&& rhs, allocator_type a) + : table_(std::move(rhs.table_), a) + { + } + /// Capacity /// diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index cc66393a..5e07151b 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1274,12 +1274,27 @@ public: } table_core(table_core&& x,const Allocator& al_): - table_core{0,std::move(x.h()),std::move(x.pred()),al_} + hash_base{empty_init,std::move(x.h())}, + pred_base{empty_init,std::move(x.pred())}, + allocator_base{empty_init,al_},arrays(new_arrays(0)), + ml{initial_max_load()},size_{0} { if(al()==x.al()){ std::swap(arrays,x.arrays); - std::swap(ml,x.ml); - std::swap(size_,x.size_); + + // when SizeImpl is an atomic type, std::swap() can't be used + // as it's not MoveConstructible so we instead opt for this manual version + { + std::size_t tmp{size_}; + size_=static_cast(x.size_); + x.size_=tmp; + } + + { + std::size_t tmp{ml}; + ml=static_cast(x.ml); + x.ml=tmp; + } } else{ reserve(x.size()); diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index 0fb5614b..6e41d951 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -44,9 +44,37 @@ template struct soccc_allocator bool operator!=(soccc_allocator const& rhs) const { return x_ != rhs.x_; } }; +template struct stateful_allocator +{ + int x_ = -1; + + using value_type = T; + + stateful_allocator() = default; + stateful_allocator(stateful_allocator const&) = default; + stateful_allocator(stateful_allocator&&) = default; + + stateful_allocator(int const x) : x_{x} {} + + template + stateful_allocator(stateful_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==(stateful_allocator const& rhs) const { return x_ == rhs.x_; } + bool operator!=(stateful_allocator const& rhs) const { return x_ != rhs.x_; } +}; + using hasher = stateful_hash; using key_equal = stateful_key_equal; -using allocator_type = std::allocator >; +using allocator_type = stateful_allocator >; using map_type = boost::unordered::concurrent_flat_map; @@ -331,6 +359,7 @@ namespace { auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); auto reference_map = boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); { @@ -339,6 +368,8 @@ namespace { std::atomic_uint num_transfers{0}; + auto const old_mc = +raii::move_constructor; + thread_runner( values, [&x, &reference_map, &num_transfers]( boost::span > s) { @@ -368,6 +399,105 @@ namespace { }); BOOST_TEST_EQ(num_transfers, 1u); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + } + + check_raii_counts(); + + // allocator-aware move constructor, unequal allocators + raii::reset_counts(); + + { + map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2), + allocator_type{1}); + + std::atomic_uint num_transfers{0}; + + auto const old_mc = +raii::move_constructor; + auto const old_size = x.size(); + + thread_runner( + values, [&x, &reference_map, &num_transfers, old_size]( + boost::span > s) { + (void)s; + + auto a = allocator_type{2}; + BOOST_TEST(a != x.get_allocator()); + + map_type y(std::move(x), a); + + if (!y.empty()) { + ++num_transfers; + + test_matches_reference(y, reference_map); + BOOST_TEST_EQ(y.size(), old_size); + BOOST_TEST_EQ(y.hash_function(), hasher(1)); + BOOST_TEST_EQ(y.key_eq(), key_equal(2)); + } else { + BOOST_TEST_EQ(y.size(), 0); + BOOST_TEST_EQ(y.hash_function(), hasher()); + BOOST_TEST_EQ(y.key_eq(), key_equal()); + } + + BOOST_TEST_EQ(x.size(), 0); + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + + BOOST_TEST(y.get_allocator() != x.get_allocator()); + BOOST_TEST(y.get_allocator() == a); + }); + + BOOST_TEST_EQ(num_transfers, 1u); + BOOST_TEST_EQ(raii::move_constructor, old_mc + (2 * old_size)); + } + + check_raii_counts(); + + // allocator-aware move constructor, equal allocators + raii::reset_counts(); + + { + map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2), + allocator_type{1}); + + std::atomic_uint num_transfers{0}; + + auto const old_mc = +raii::move_constructor; + auto const old_size = x.size(); + + thread_runner( + values, [&x, &reference_map, &num_transfers, old_size]( + boost::span > s) { + (void)s; + + auto a = allocator_type{1}; + BOOST_TEST(a == x.get_allocator()); + + map_type y(std::move(x), a); + + if (!y.empty()) { + ++num_transfers; + + test_matches_reference(y, reference_map); + BOOST_TEST_EQ(y.size(), old_size); + BOOST_TEST_EQ(y.hash_function(), hasher(1)); + BOOST_TEST_EQ(y.key_eq(), key_equal(2)); + } else { + BOOST_TEST_EQ(y.size(), 0); + BOOST_TEST_EQ(y.hash_function(), hasher()); + BOOST_TEST_EQ(y.key_eq(), key_equal()); + } + + BOOST_TEST_EQ(x.size(), 0); + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + + BOOST_TEST(y.get_allocator() == x.get_allocator()); + BOOST_TEST(y.get_allocator() == a); + }); + + BOOST_TEST_EQ(num_transfers, 1u); + BOOST_TEST_EQ(raii::move_constructor, old_mc); } check_raii_counts(); From a91efeb23729c1b088e65ebbd75e65b0c54d441d Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 20 Apr 2023 17:29:21 +0200 Subject: [PATCH 175/327] refactored size_/ml swap --- include/boost/unordered/detail/foa/core.hpp | 31 ++++++++++----------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 5e07151b..34b4bb3d 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1226,6 +1226,7 @@ public: using alloc_traits=boost::allocator_traits; using element_type=typename type_policy::element_type; using arrays_type=Arrays; + using size_impl_type=SizeImpl; using key_type=typename type_policy::key_type; using init_type=typename type_policy::init_type; @@ -1281,20 +1282,8 @@ public: { if(al()==x.al()){ std::swap(arrays,x.arrays); - - // when SizeImpl is an atomic type, std::swap() can't be used - // as it's not MoveConstructible so we instead opt for this manual version - { - std::size_t tmp{size_}; - size_=static_cast(x.size_); - x.size_=tmp; - } - - { - std::size_t tmp{ml}; - ml=static_cast(x.ml); - x.ml=tmp; - } + swap_size_impl(size_,x.size_); + swap_size_impl(ml,x.ml); } else{ reserve(x.size()); @@ -1708,9 +1697,9 @@ public: } } - arrays_type arrays; - SizeImpl ml; - SizeImpl size_; + arrays_type arrays; + size_impl_type ml; + size_impl_type size_; private: template< @@ -1875,6 +1864,14 @@ private: } } + static inline void swap_size_impl(size_impl_type& x,size_impl_type& y) + { + /* std::swap can't be used on non-assignable atomics */ + std::size_t tmp=x; + x=static_cast(y); + y=tmp; + } + void recover_slot(unsigned char* pc) { /* If this slot potentially caused overflow, we decrease the maximum load so From c704788718177a1ad3c0a9ef55d45f590e46b922 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 20 Apr 2023 09:25:33 -0700 Subject: [PATCH 176/327] Fix flaky test by starting insertion thread earlier --- test/cfoa/constructor_tests.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index 6e41d951..86760863 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -434,12 +434,12 @@ namespace { BOOST_TEST_EQ(y.hash_function(), hasher(1)); BOOST_TEST_EQ(y.key_eq(), key_equal(2)); } else { - BOOST_TEST_EQ(y.size(), 0); + BOOST_TEST_EQ(y.size(), 0u); BOOST_TEST_EQ(y.hash_function(), hasher()); BOOST_TEST_EQ(y.key_eq(), key_equal()); } - BOOST_TEST_EQ(x.size(), 0); + BOOST_TEST_EQ(x.size(), 0u); BOOST_TEST_EQ(x.hash_function(), hasher()); BOOST_TEST_EQ(x.key_eq(), key_equal()); @@ -515,7 +515,7 @@ namespace { map_type x(0, hasher(1), key_equal(2), allocator_type{}); auto f = [&x, &values] { - std::this_thread::sleep_for(std::chrono::milliseconds(50)); + std::this_thread::sleep_for(std::chrono::milliseconds(25)); for (auto const& val : values) { x.insert(val); } From c214fb44a31f59969ee679f2d32440810aea8804 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 20 Apr 2023 18:49:42 +0200 Subject: [PATCH 177/327] completed internal code documentation --- .../unordered/detail/foa/concurrent_table.hpp | 55 ++++++++++++++++++- include/boost/unordered/detail/foa/core.hpp | 2 - include/boost/unordered/detail/foa/table.hpp | 8 ++- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 4f73f44c..ff2a93e4 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -168,7 +168,7 @@ private: Mutex *pm1,*pm2; }; -/* TODO: describe atomic_integral and group_access */ +/* use atomics for group metadata storage */ template struct atomic_integral @@ -184,7 +184,6 @@ struct atomic_integral } std::atomic n; - }; /* Group-level concurrency protection. It provides a rw mutex plus an @@ -283,7 +282,57 @@ struct concurrent_table_arrays:table_arrays group_access *group_accesses; }; -/* TODO: describe foa::concurrent_table. +/* foa::concurrent_table serves as the foundation for end-user concurrent + * hash containers. The TypePolicy parameter can specify flat/node-based + * map-like and set-like containers, though currently we're only providing + * boost::concurrent_flat_map. + * + * The exposed interface (completed by the wrapping containers) is not that + * of a regular container (in fact, it does not model Container as understood + * by the C++ standard): + * + * - Iterators are not provided as they are not suitable for concurrent + * scenarios. + * - As a consequence, composite operations with regular containers + * (like, for instance, looking up and element and modifying it), must + * be provided natively without any intervening iterator/accesor. + * Visitation is a core concept in this design, either on its own (eg. + * visit(k) locates the element with key k *and* accesses it) or as part + * of a native composite operation (eg. try_emplace_or_visit). Visitation + * is constant or mutating depending on whether the used table function is + * const or not. + * - The API provides member functions for all the meaningful composite + * operations of the form "X (and|or) Y", where X, Y are one of the + * primitives FIND, ACCESS, INSERT or ERASE. + * + * Consult boost::unordered_flat_map docs for the full API reference. + * Heterogeneous lookup is suported by default, that is, without checking for + * any ::is_transparent typedefs --this checking is done by the wrapping + * containers. + * + * Thread-safe concurrency is implemented using a two-level lock system: + * + * - The first level is container-wide and implemented with an array + * of rw spinlocks acting as a single rw mutex with very little + * false sharing on read (each thread is assigned a different spinlock + * in the array). At this level, write locking is only used for rehashing + * and container-wide operations (assignment, swap). + * - Each group of slots has an associated rw spinlock. Lookup is + * implemented in a (groupwise) lock-free manner until a reduced hash match + * is found, in which case the relevant group is locked and the slot is + * double-checked for occupancy and compared with the key. + * - Each group has also an associated so-called insertion counter used for + * the following optimistic insertion algorithm: + * - The value of the insertion counter for the initial group in the probe + * sequence is recorded (let's call this value c0). + * - Lookup and search for an available slot (if lookup failed) are + * lock-free. + * - When an available slot is located, it is preemptively occupied (its + * reduced hash value is set) after locking and the insertion counter is + * atomically incremented: if no other thread has incremented the counter + * during the whole operation (which is checked by comparing with c0), + * then we're good to go and complete the insertion, otherwise we roll + * back and start over. */ template diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 34b4bb3d..1552f41e 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1411,8 +1411,6 @@ public: std::size_t size()const noexcept{return size_;} std::size_t max_size()const noexcept{return SIZE_MAX;} - // TODO unify erase? - BOOST_FORCEINLINE void erase(group_type* pg,unsigned int pos,element_type* p)noexcept { diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 5713ccda..1946bd9f 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -27,6 +27,8 @@ namespace unordered{ namespace detail{ namespace foa{ +/* use plain integrals for group metadata storage */ + template struct plain_integral { @@ -211,9 +213,9 @@ private: * - Pointer stability is not kept under rehashing. * - No extract API. * - * try_emplace, erase and find support heterogenous lookup by default, that is, - * without checking for any ::is_transparent typedefs --the checking is done by - * boost::unordered_[flat|node]_[map|set]. + * try_emplace, erase and find support heterogeneous lookup by default, + * that is, without checking for any ::is_transparent typedefs --the + * checking is done by boost::unordered_[flat|node]_[map|set]. */ template From 0e8affcc071af45b82970d963bb138c57e1f8c3e Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 20 Apr 2023 20:40:10 +0200 Subject: [PATCH 178/327] [skip ci] editorial --- .../boost/unordered/detail/foa/concurrent_table.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index ff2a93e4..ad76bf21 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -294,7 +294,7 @@ struct concurrent_table_arrays:table_arrays * - Iterators are not provided as they are not suitable for concurrent * scenarios. * - As a consequence, composite operations with regular containers - * (like, for instance, looking up and element and modifying it), must + * (like, for instance, looking up an element and modifying it), must * be provided natively without any intervening iterator/accesor. * Visitation is a core concept in this design, either on its own (eg. * visit(k) locates the element with key k *and* accesses it) or as part @@ -312,11 +312,11 @@ struct concurrent_table_arrays:table_arrays * * Thread-safe concurrency is implemented using a two-level lock system: * - * - The first level is container-wide and implemented with an array - * of rw spinlocks acting as a single rw mutex with very little - * false sharing on read (each thread is assigned a different spinlock - * in the array). At this level, write locking is only used for rehashing - * and container-wide operations (assignment, swap). + * - A first container-level lock is implemented with an array of + * rw spinlocks acting as a single rw mutex with very little + * cache-coherence traffic on read (each thread is assigned a different + * spinlock in the array). Container-level write locking is only used for + * rehashing and container-wide operations (assignment, swap). * - Each group of slots has an associated rw spinlock. Lookup is * implemented in a (groupwise) lock-free manner until a reduced hash match * is found, in which case the relevant group is locked and the slot is From c304284773227f70d1d0e8747aa9b18b01f82538 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 20 Apr 2023 12:57:00 -0700 Subject: [PATCH 179/327] Update tests to use condition variables for work synchronization instead of sleeps --- test/cfoa/constructor_tests.cpp | 45 +++++++++++++++++++++++++++------ test/cfoa/helpers.hpp | 21 +++++++++++++-- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index 86760863..457d27e3 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -297,11 +297,20 @@ namespace { boost::unordered_flat_map(values.begin(), values.end()); raii::reset_counts(); + std::mutex m; + std::condition_variable cv; + bool ready = false; + { map_type x(0, hasher(1), key_equal(2), allocator_type{}); - auto f = [&x, &values] { - std::this_thread::sleep_for(std::chrono::milliseconds(75)); + auto f = [&x, &values, &m, &cv, &ready] { + { + std::lock_guard guard(m); + ready = true; + } + cv.notify_all(); + for (auto const& val : values) { x.insert(val); } @@ -311,9 +320,15 @@ namespace { std::thread t2(f); thread_runner( - values, [&x, &reference_map, &values, rg]( + values, [&x, &reference_map, &values, rg, &m, &cv, &ready]( boost::span > s) { (void)s; + + { + std::unique_lock lk(m); + cv.wait(lk, [&] { return ready; }); + } + map_type y(x); BOOST_TEST_LE(y.size(), values.size()); @@ -483,12 +498,12 @@ namespace { BOOST_TEST_EQ(y.hash_function(), hasher(1)); BOOST_TEST_EQ(y.key_eq(), key_equal(2)); } else { - BOOST_TEST_EQ(y.size(), 0); + BOOST_TEST_EQ(y.size(), 0u); BOOST_TEST_EQ(y.hash_function(), hasher()); BOOST_TEST_EQ(y.key_eq(), key_equal()); } - BOOST_TEST_EQ(x.size(), 0); + BOOST_TEST_EQ(x.size(), 0u); BOOST_TEST_EQ(x.hash_function(), hasher()); BOOST_TEST_EQ(x.key_eq(), key_equal()); @@ -511,11 +526,20 @@ namespace { boost::unordered_flat_map(values.begin(), values.end()); raii::reset_counts(); + std::mutex m; + std::condition_variable cv; + bool ready = false; + { map_type x(0, hasher(1), key_equal(2), allocator_type{}); - auto f = [&x, &values] { - std::this_thread::sleep_for(std::chrono::milliseconds(25)); + auto f = [&x, &values, &m, &cv, &ready] { + { + std::lock_guard guard(m); + ready = true; + } + cv.notify_all(); + for (auto const& val : values) { x.insert(val); } @@ -527,10 +551,15 @@ namespace { std::thread t2(f); thread_runner( - values, [&x, &reference_map, &num_transfers, rg]( + values, [&x, &reference_map, &num_transfers, rg, &m, &ready, &cv]( boost::span > s) { (void)s; + { + std::unique_lock lk(m); + cv.wait(lk, [&] { return ready; }); + } + map_type y(std::move(x)); if (!y.empty()) { diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index 02f22d86..a830c032 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -9,8 +9,10 @@ #include #include +#include #include #include +#include #include #include @@ -272,16 +274,31 @@ std::vector > split( template void thread_runner(std::vector& values, F f) { + std::mutex m; + std::condition_variable cv; + bool ready = false; + std::vector threads; auto subslices = split(values, num_threads); for (std::size_t i = 0; i < num_threads; ++i) { - threads.emplace_back([&f, &subslices, i] { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + threads.emplace_back([&f, &subslices, i, &m, &cv, &ready] { + { + std::unique_lock lk(m); + cv.wait(lk, [&] { return ready; }); + } + auto s = subslices[i]; f(s); }); } + + { + std::lock_guard guard(m); + ready = true; + } + cv.notify_all(); + for (auto& t : threads) { t.join(); } From e08f9f11a1e44e0f8a7c125fa0855c8d7fcd4ce0 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 20 Apr 2023 12:57:29 -0700 Subject: [PATCH 180/327] Add more constructors --- .../boost/unordered/concurrent_flat_map.hpp | 21 ++++++ test/cfoa/constructor_tests.cpp | 68 +++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 62d24b52..f65e534d 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -197,6 +197,27 @@ namespace boost { { } + concurrent_flat_map(std::initializer_list il, + size_type n = detail::foa::default_bucket_count, + const hasher& hf = hasher(), const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()) + : concurrent_flat_map(n, hf, eql, a) + { + this->insert(il.begin(), il.end()); + } + + concurrent_flat_map(size_type n, const allocator_type& a) + : concurrent_flat_map(n, hasher(), key_equal(), a) + { + } + + concurrent_flat_map( + size_type n, const hasher& hf, const allocator_type& a) + : concurrent_flat_map(n, hf, key_equal(), a) + { + } + + /// Capacity /// diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index 457d27e3..9ecae41c 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -628,6 +628,74 @@ namespace { } } + UNORDERED_AUTO_TEST (initializer_list_with_all_params) { + std::initializer_list ilist{ + map_value_type{raii{0}, raii{0}}, + map_value_type{raii{1}, raii{1}}, + map_value_type{raii{2}, raii{2}}, + map_value_type{raii{3}, raii{3}}, + map_value_type{raii{4}, raii{4}}, + map_value_type{raii{5}, raii{5}}, + map_value_type{raii{6}, raii{6}}, + map_value_type{raii{6}, raii{6}}, + map_value_type{raii{7}, raii{7}}, + map_value_type{raii{8}, raii{8}}, + map_value_type{raii{9}, raii{9}}, + map_value_type{raii{10}, raii{10}}, + map_value_type{raii{9}, raii{9}}, + map_value_type{raii{8}, raii{8}}, + map_value_type{raii{7}, raii{7}}, + map_value_type{raii{6}, raii{6}}, + map_value_type{raii{5}, raii{5}}, + map_value_type{raii{4}, raii{4}}, + map_value_type{raii{3}, raii{3}}, + map_value_type{raii{2}, raii{2}}, + map_value_type{raii{1}, raii{1}}, + map_value_type{raii{0}, raii{0}}, + }; + + raii::reset_counts(); + + map_type x(ilist, 0, hasher(1), key_equal(2), allocator_type(3)); + + BOOST_TEST_EQ(x.size(), 11u); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + BOOST_TEST(x.get_allocator() == allocator_type(3)); + } + + UNORDERED_AUTO_TEST (bucket_count_and_allocator) { + raii::reset_counts(); + + { + map_type x(0, allocator_type(3)); + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + BOOST_TEST(x.get_allocator() == allocator_type(3)); + } + + { + map_type x(4096, allocator_type(3)); + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + BOOST_TEST(x.get_allocator() == allocator_type(3)); + } + } + + UNORDERED_AUTO_TEST (bucket_count_with_hasher_and_allocator) { + raii::reset_counts(); + + { + map_type x(0, hasher(1), allocator_type(3)); + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + BOOST_TEST(x.get_allocator() == allocator_type(3)); + } + } + } // namespace // clang-format off From 5dfed4deec3470ee6a6cd7f30d6001fee1d7e4e8 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 20 Apr 2023 14:34:01 -0700 Subject: [PATCH 181/327] Update thread_runner to block on hand-rolled barrier, add yield points to hash, key_equal --- test/cfoa/helpers.hpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index a830c032..e630cd9c 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -24,6 +24,7 @@ struct transp_hash template std::size_t operator()(T const& t) const noexcept { + std::this_thread::yield(); return boost::hash()(t); } }; @@ -34,6 +35,7 @@ struct transp_key_equal template bool operator()(T const& lhs, U const& rhs) const { + std::this_thread::yield(); return lhs == rhs; } }; @@ -57,6 +59,7 @@ struct stateful_hash { std::size_t h = static_cast(x_); boost::hash_combine(h, t); + std::this_thread::yield(); return h; } @@ -86,6 +89,7 @@ struct stateful_key_equal template bool operator()(T const& t, U const& u) const { + std::this_thread::yield(); return t == u; } @@ -276,16 +280,22 @@ template void thread_runner(std::vector& values, F f) { std::mutex m; std::condition_variable cv; - bool ready = false; + std::size_t c = 0; std::vector threads; auto subslices = split(values, num_threads); for (std::size_t i = 0; i < num_threads; ++i) { - threads.emplace_back([&f, &subslices, i, &m, &cv, &ready] { + threads.emplace_back([&f, &subslices, i, &m, &cv, &c] { { std::unique_lock lk(m); - cv.wait(lk, [&] { return ready; }); + ++c; + if (c == num_threads) { + lk.unlock(); + cv.notify_all(); + } else { + cv.wait(lk, [&] { return c == num_threads; }); + } } auto s = subslices[i]; @@ -293,12 +303,6 @@ template void thread_runner(std::vector& values, F f) }); } - { - std::lock_guard guard(m); - ready = true; - } - cv.notify_all(); - for (auto& t : threads) { t.join(); } From 80d7203d78ff81a0b5e59fa68dfa5efb59edbc96 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 20 Apr 2023 15:35:04 -0700 Subject: [PATCH 182/327] Add more iterator constructor overloads --- .../boost/unordered/concurrent_flat_map.hpp | 13 ++++ test/cfoa/constructor_tests.cpp | 59 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index f65e534d..4bb78f3c 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -217,6 +217,19 @@ namespace boost { { } + template + concurrent_flat_map( + InputIterator f, InputIterator l, size_type n, const allocator_type& a) + : concurrent_flat_map(f, l, n, hasher(), key_equal(), a) + { + } + + template + concurrent_flat_map(InputIterator f, InputIterator l, size_type n, + const hasher& hf, const allocator_type& a) + : concurrent_flat_map(f, l, n, hf, key_equal(), a) + { + } /// Capacity /// diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index 9ecae41c..0d696546 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -524,6 +524,7 @@ namespace { auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); auto reference_map = boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); std::mutex m; @@ -696,6 +697,53 @@ namespace { } } + template + void iterator_range_with_bucket_count_and_allocator( + G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + + raii::reset_counts(); + + { + allocator_type a(3); + map_type x(values.begin(), values.end(), 0, a); + test_fuzzy_matches_reference(x, reference_map, rg); + + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + BOOST_TEST(x.get_allocator() == a); + } + + check_raii_counts(); + } + + template + void iterator_range_with_bucket_count_hasher_and_allocator( + G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + + raii::reset_counts(); + + { + allocator_type a(3); + hasher hf(1); + map_type x(values.begin(), values.end(), 0, hf, a); + test_fuzzy_matches_reference(x, reference_map, rg); + + BOOST_TEST_EQ(x.hash_function(), hf); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + BOOST_TEST(x.get_allocator() == a); + } + + check_raii_counts(); + } + } // namespace // clang-format off @@ -728,6 +776,17 @@ UNORDERED_TEST( iterator_range_with_allocator, ((value_type_generator)) ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + iterator_range_with_bucket_count_and_allocator, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + iterator_range_with_bucket_count_hasher_and_allocator, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) + // clang-format on RUN_TESTS() From 7f7e577e779a9cf1d92a542031b71a3907ba77c9 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 21 Apr 2023 09:13:28 +0200 Subject: [PATCH 183/327] polished description --- .../unordered/detail/foa/concurrent_table.hpp | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index ad76bf21..1769e242 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -304,6 +304,8 @@ struct concurrent_table_arrays:table_arrays * - The API provides member functions for all the meaningful composite * operations of the form "X (and|or) Y", where X, Y are one of the * primitives FIND, ACCESS, INSERT or ERASE. + * - Parallel versions of [c]visit_all(f) and erase_if(f) are provided based + * on C++17 stdlib parallel algorithms. * * Consult boost::unordered_flat_map docs for the full API reference. * Heterogeneous lookup is suported by default, that is, without checking for @@ -316,23 +318,25 @@ struct concurrent_table_arrays:table_arrays * rw spinlocks acting as a single rw mutex with very little * cache-coherence traffic on read (each thread is assigned a different * spinlock in the array). Container-level write locking is only used for - * rehashing and container-wide operations (assignment, swap). - * - Each group of slots has an associated rw spinlock. Lookup is - * implemented in a (groupwise) lock-free manner until a reduced hash match - * is found, in which case the relevant group is locked and the slot is - * double-checked for occupancy and compared with the key. + * rehashing and other container-wide operations (assignment, swap, etc.) + * - Each group of slots has an associated rw spinlock. A thread holds + * at most one group lock at any given time. Lookup is implemented in + * a (groupwise) lock-free manner until a reduced hash match is found, in + * which case the relevant group is locked and the slot is double-checked + * for occupancy and compared with the key. * - Each group has also an associated so-called insertion counter used for * the following optimistic insertion algorithm: * - The value of the insertion counter for the initial group in the probe - * sequence is recorded (let's call this value c0). - * - Lookup and search for an available slot (if lookup failed) are - * lock-free. + * sequence is locally recorded (let's call this value c0). + * - Lookup is as described above. If lookup finds no equivalent element, + * search for an available slot for insertion successively locks/unlocks + * each group in the probing sequence. * - When an available slot is located, it is preemptively occupied (its - * reduced hash value is set) after locking and the insertion counter is - * atomically incremented: if no other thread has incremented the counter - * during the whole operation (which is checked by comparing with c0), - * then we're good to go and complete the insertion, otherwise we roll - * back and start over. + * reduced hash value is set) and the insertion counter is atomically + * incremented: if no other thread has incremented the counter during the + * whole operation (which is checked by comparing with c0), then we're + * good to go and complete the insertion, otherwise we roll back and start + * over. */ template From 88d4d64edf879707e2a93f46a0c26bac992f4f28 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 21 Apr 2023 10:33:21 -0700 Subject: [PATCH 184/327] Add last remaining constructors --- .../boost/unordered/concurrent_flat_map.hpp | 21 +++++++ test/cfoa/constructor_tests.cpp | 59 +++++++++++++++++-- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 4bb78f3c..56a90de6 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -231,6 +231,27 @@ namespace boost { { } + concurrent_flat_map( + std::initializer_list il, const allocator_type& a) + : concurrent_flat_map( + il, detail::foa::default_bucket_count, hasher(), key_equal(), a) + { + } + + concurrent_flat_map(std::initializer_list il, size_type n, + const allocator_type& a) + : concurrent_flat_map(il, n, hasher(), key_equal(), a) + { + } + + concurrent_flat_map(std::initializer_list il, size_type n, + const hasher& hf, const allocator_type& a) + : concurrent_flat_map(il, n, hf, key_equal(), a) + { + } + + ~concurrent_flat_map() = default; + /// Capacity /// diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index 0d696546..4d92d6b5 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -655,14 +655,61 @@ namespace { map_value_type{raii{0}, raii{0}}, }; - raii::reset_counts(); + { + raii::reset_counts(); - map_type x(ilist, 0, hasher(1), key_equal(2), allocator_type(3)); + map_type x(ilist, 0, hasher(1), key_equal(2), allocator_type(3)); - BOOST_TEST_EQ(x.size(), 11u); - BOOST_TEST_EQ(x.hash_function(), hasher(1)); - BOOST_TEST_EQ(x.key_eq(), key_equal(2)); - BOOST_TEST(x.get_allocator() == allocator_type(3)); + BOOST_TEST_EQ(x.size(), 11u); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + BOOST_TEST(x.get_allocator() == allocator_type(3)); + + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + check_raii_counts(); + + { + raii::reset_counts(); + + map_type x(ilist, allocator_type(3)); + + BOOST_TEST_EQ(x.size(), 11u); + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + BOOST_TEST(x.get_allocator() == allocator_type(3)); + + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + check_raii_counts(); + + { + raii::reset_counts(); + + map_type x(ilist, 0, allocator_type(3)); + + BOOST_TEST_EQ(x.size(), 11u); + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + BOOST_TEST(x.get_allocator() == allocator_type(3)); + + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + check_raii_counts(); + + { + raii::reset_counts(); + + map_type x(ilist, 0, hasher(1), allocator_type(3)); + + BOOST_TEST_EQ(x.size(), 11u); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + BOOST_TEST(x.get_allocator() == allocator_type(3)); + + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + check_raii_counts(); } UNORDERED_AUTO_TEST (bucket_count_and_allocator) { From 2cf72093b1b8c52ef14435ced23eca01baf311fe Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 21 Apr 2023 10:44:06 -0700 Subject: [PATCH 185/327] Update test to spin until the container is non-empty --- test/cfoa/constructor_tests.cpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index 4d92d6b5..39fbc76a 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -534,22 +534,25 @@ namespace { { map_type x(0, hasher(1), key_equal(2), allocator_type{}); - auto f = [&x, &values, &m, &cv, &ready] { + std::atomic_uint num_transfers{0}; + + std::thread t1([&x, &values] { + for (auto const& val : values) { + x.insert(val); + } + }); + + std::thread t2([&x, &m, &cv, &ready] { + while (x.empty()) { + std::this_thread::yield(); + } + { std::lock_guard guard(m); ready = true; } cv.notify_all(); - - for (auto const& val : values) { - x.insert(val); - } - }; - - std::atomic_uint num_transfers{0}; - - std::thread t1(f); - std::thread t2(f); + }); thread_runner( values, [&x, &reference_map, &num_transfers, rg, &m, &ready, &cv]( From 1e92d9f54510bc1fdda0c880a142db29cb46913b Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 21 Apr 2023 13:33:18 -0700 Subject: [PATCH 186/327] Refactor stateful_allocator to test helpers --- test/cfoa/constructor_tests.cpp | 28 ---------------------------- test/cfoa/helpers.hpp | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index 39fbc76a..1256fa7e 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -44,34 +44,6 @@ template struct soccc_allocator bool operator!=(soccc_allocator const& rhs) const { return x_ != rhs.x_; } }; -template struct stateful_allocator -{ - int x_ = -1; - - using value_type = T; - - stateful_allocator() = default; - stateful_allocator(stateful_allocator const&) = default; - stateful_allocator(stateful_allocator&&) = default; - - stateful_allocator(int const x) : x_{x} {} - - template - stateful_allocator(stateful_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==(stateful_allocator const& rhs) const { return x_ == rhs.x_; } - bool operator!=(stateful_allocator const& rhs) const { return x_ != rhs.x_; } -}; - using hasher = stateful_hash; using key_equal = stateful_key_equal; using allocator_type = stateful_allocator >; diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index e630cd9c..8323393f 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -103,6 +103,34 @@ struct stateful_key_equal } }; +template struct stateful_allocator +{ + int x_ = -1; + + using value_type = T; + + stateful_allocator() = default; + stateful_allocator(stateful_allocator const&) = default; + stateful_allocator(stateful_allocator&&) = default; + + stateful_allocator(int const x) : x_{x} {} + + template + stateful_allocator(stateful_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==(stateful_allocator const& rhs) const { return x_ == rhs.x_; } + bool operator!=(stateful_allocator const& rhs) const { return x_ != rhs.x_; } +}; + struct raii { static std::atomic default_constructor; From 26ab9ff584e7ffd28f38dfb0bbab37b89e634e5d Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 21 Apr 2023 15:56:22 -0700 Subject: [PATCH 187/327] Add prototype copy assignment operator --- .../boost/unordered/concurrent_flat_map.hpp | 6 + test/Jamfile.v2 | 1 + test/cfoa/assign_tests.cpp | 147 ++++++++++++++++++ test/cfoa/helpers.hpp | 14 ++ 4 files changed, 168 insertions(+) create mode 100644 test/cfoa/assign_tests.cpp diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 56a90de6..74d97b94 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -252,6 +252,12 @@ namespace boost { ~concurrent_flat_map() = default; + concurrent_flat_map& operator=(concurrent_flat_map const& rhs) + { + table_ = rhs.table_; + return *this; + } + /// Capacity /// diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 604eedf5..bb5ea89a 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -182,6 +182,7 @@ local CFOA_TESTS = emplace_tests visit_tests constructor_tests + assign_tests ; for local test in $(CFOA_TESTS) diff --git a/test/cfoa/assign_tests.cpp b/test/cfoa/assign_tests.cpp new file mode 100644 index 00000000..f7c3000e --- /dev/null +++ b/test/cfoa/assign_tests.cpp @@ -0,0 +1,147 @@ +// 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{2762556623}; + +using test::default_generator; +using test::limited_range; +using test::sequential; + +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; + +namespace { + template void copy_assign(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + + // to test: + // self-assign + // propagation + // + + // lhs empty, rhs empty + { + raii::reset_counts(); + + map_type x(0, hasher(1), key_equal(2), allocator_type(3)); + map_type y; + + BOOST_TEST(x.empty()); + BOOST_TEST(y.empty()); + y = x; + + BOOST_TEST_EQ(raii::destructor, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + + BOOST_TEST_EQ(x.hash_function(), y.hash_function()); + BOOST_TEST_EQ(x.key_eq(), y.key_eq()); + BOOST_TEST(x.get_allocator() != y.get_allocator()); + } + + // lhs non-empty, rhs empty + { + raii::reset_counts(); + + map_type x(0, hasher(1), key_equal(2), allocator_type(3)); + + map_type y(values.begin(), values.end(), values.size()); + + auto const old_cc = +raii::copy_constructor; + auto const old_size = y.size(); + + BOOST_TEST(x.empty()); + BOOST_TEST(!y.empty()); + y = x; + + BOOST_TEST_EQ(raii::destructor, 2 * old_size); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + + BOOST_TEST_EQ(x.hash_function(), y.hash_function()); + BOOST_TEST_EQ(x.key_eq(), y.key_eq()); + BOOST_TEST(x.get_allocator() != y.get_allocator()); + } + check_raii_counts(); + + // lhs empty, rhs non-empty + { + raii::reset_counts(); + + map_type x(values.begin(), values.end(), values.size(), hasher(1), + key_equal(2), allocator_type(3)); + + map_type y; + auto const old_cc = +raii::copy_constructor; + + BOOST_TEST(!x.empty()); + BOOST_TEST(y.empty()); + y = x; + + BOOST_TEST_EQ(raii::destructor, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_constructor, old_cc + (2 * x.size())); + + BOOST_TEST_EQ(x.hash_function(), y.hash_function()); + BOOST_TEST_EQ(x.key_eq(), y.key_eq()); + BOOST_TEST(x.get_allocator() != y.get_allocator()); + + test_matches_reference(y, reference_map); + } + check_raii_counts(); + + // lhs non-empty, rhs non-empty + { + raii::reset_counts(); + + map_type x(values.begin(), values.end(), values.size(), hasher(1), + key_equal(2), allocator_type(3)); + + map_type y(values.begin(), values.end(), values.size()); + + auto const old_size = y.size(); + auto const old_cc = +raii::copy_constructor; + + BOOST_TEST(!x.empty()); + BOOST_TEST(!y.empty()); + y = x; + + BOOST_TEST_EQ(raii::destructor, 2 * old_size); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_constructor, old_cc + (2 * x.size())); + + BOOST_TEST_EQ(x.hash_function(), y.hash_function()); + BOOST_TEST_EQ(x.key_eq(), y.key_eq()); + BOOST_TEST(x.get_allocator() != y.get_allocator()); + } + check_raii_counts(); + } + +} // namespace + +// clang-format off +UNORDERED_TEST( + copy_assign, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) +// clang-format on + +RUN_TESTS() diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index 8323393f..b5fa1046 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -70,6 +70,13 @@ struct stateful_hash os << "{ x_: " << rhs.x_ << " }"; return os; } + + friend void swap(stateful_hash& lhs, stateful_hash& rhs) noexcept + { + if (&lhs != &rhs) { + std::swap(lhs.x_, rhs.x_); + } + } }; struct stateful_key_equal @@ -101,6 +108,13 @@ struct stateful_key_equal os << "{ x_: " << rhs.x_ << " }"; return os; } + + friend void swap(stateful_key_equal& lhs, stateful_key_equal& rhs) noexcept + { + if (&lhs != &rhs) { + std::swap(lhs.x_, rhs.x_); + } + } }; template struct stateful_allocator From e49fef5f9a685c76962a024cc54b38ef8fe279c6 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 23 Apr 2023 17:27:50 +0200 Subject: [PATCH 188/327] commented out all tests except those for cfoa --- test/Jamfile.v2 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index bb5ea89a..dd6baf71 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -33,6 +33,7 @@ project msvc:on ; +#| run unordered/prime_fmod_tests.cpp ; run unordered/fwd_set_test.cpp ; run unordered/fwd_map_test.cpp ; @@ -99,11 +100,13 @@ run exception/less_tests.cpp ; run unordered/narrow_cast_tests.cpp ; run quick.cpp ; +|# import ../../config/checks/config : requires ; CPP11 = [ requires cxx11_constexpr cxx11_noexcept cxx11_decltype cxx11_alignas ] ; +#| local FOA_TESTS = fwd_set_test fwd_map_test @@ -174,6 +177,7 @@ alias foa_tests : foa_swap_exception_tests foa_merge_exception_tests ; +|# local CFOA_TESTS = insert_tests From ac216a93c880601debe4fee671a623c08be556ea Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 24 Apr 2023 13:29:07 -0700 Subject: [PATCH 189/327] Add tests back in --- test/Jamfile.v2 | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index dd6baf71..bb5ea89a 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -33,7 +33,6 @@ project msvc:on ; -#| run unordered/prime_fmod_tests.cpp ; run unordered/fwd_set_test.cpp ; run unordered/fwd_map_test.cpp ; @@ -100,13 +99,11 @@ run exception/less_tests.cpp ; run unordered/narrow_cast_tests.cpp ; run quick.cpp ; -|# import ../../config/checks/config : requires ; CPP11 = [ requires cxx11_constexpr cxx11_noexcept cxx11_decltype cxx11_alignas ] ; -#| local FOA_TESTS = fwd_set_test fwd_map_test @@ -177,7 +174,6 @@ alias foa_tests : foa_swap_exception_tests foa_merge_exception_tests ; -|# local CFOA_TESTS = insert_tests From e9c6a0fef5dab24a5690988604aaf4de77c1c416 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 24 Apr 2023 13:29:35 -0700 Subject: [PATCH 190/327] Add polyfill implementation of `std::latch` --- test/Jamfile.v2 | 1 + test/cfoa/helpers.hpp | 23 +++--- test/cfoa/latch.hpp | 87 +++++++++++++++++++++ test/cfoa/latch_tests.cpp | 154 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 251 insertions(+), 14 deletions(-) create mode 100644 test/cfoa/latch.hpp create mode 100644 test/cfoa/latch_tests.cpp diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index bb5ea89a..d6038bdf 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -176,6 +176,7 @@ alias foa_tests : ; local CFOA_TESTS = + latch_tests insert_tests erase_tests try_emplace_tests diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index b5fa1046..7ff48334 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -1,6 +1,12 @@ +// 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) + #ifndef BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP #define BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP +#include "latch.hpp" + #include "../helpers/generators.hpp" #include "../helpers/test.hpp" @@ -320,25 +326,14 @@ std::vector > split( template void thread_runner(std::vector& values, F f) { - std::mutex m; - std::condition_variable cv; - std::size_t c = 0; + boost::latch latch(num_threads); std::vector threads; auto subslices = split(values, num_threads); for (std::size_t i = 0; i < num_threads; ++i) { - threads.emplace_back([&f, &subslices, i, &m, &cv, &c] { - { - std::unique_lock lk(m); - ++c; - if (c == num_threads) { - lk.unlock(); - cv.notify_all(); - } else { - cv.wait(lk, [&] { return c == num_threads; }); - } - } + threads.emplace_back([&f, &subslices, i, &latch] { + latch.arrive_and_wait(); auto s = subslices[i]; f(s); diff --git a/test/cfoa/latch.hpp b/test/cfoa/latch.hpp new file mode 100644 index 00000000..bee42119 --- /dev/null +++ b/test/cfoa/latch.hpp @@ -0,0 +1,87 @@ +// 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) + +#ifndef BOOST_UNORDERED_TEST_CFOA_LATCH_HPP +#define BOOST_UNORDERED_TEST_CFOA_LATCH_HPP + +#include + +#include +#include +#include +#include + +namespace boost { + class latch + { + private: + std::ptrdiff_t n_; + mutable std::mutex m_; + mutable std::condition_variable cv_; + + public: + explicit latch(std::ptrdiff_t expected) : n_{expected}, m_{}, cv_{} + { + BOOST_ASSERT(n_ >= 0); + BOOST_ASSERT(n_ <= max()); + } + + latch(latch const&) = delete; + latch& operator=(latch const&) = delete; + + ~latch() = default; + + void count_down(std::ptrdiff_t n = 1) + { + std::unique_lock lk(m_); + count_down_and_notify(lk, n); + } + + bool try_wait() const noexcept + { + std::unique_lock lk(m_); + return is_ready(); + } + + void wait() const + { + std::unique_lock lk(m_); + wait_impl(lk); + } + + void arrive_and_wait(std::ptrdiff_t n = 1) + { + std::unique_lock lk(m_); + bool should_wait = count_down_and_notify(lk, n); + if (should_wait) { + wait_impl(lk); + } + } + + static constexpr std::ptrdiff_t max() noexcept { return INT_MAX; } + + private: + bool is_ready() const { return n_ == 0; } + + bool count_down_and_notify( + std::unique_lock& lk, std::ptrdiff_t n) + { + n_ -= n; + if (n_ == 0) { + lk.unlock(); + cv_.notify_all(); + return false; + } + + return true; + } + + void wait_impl(std::unique_lock& lk) const + { + cv_.wait(lk, [this] { return this->is_ready(); }); + } + }; +} // namespace boost + +#endif // BOOST_UNORDERED_TEST_CFOA_LATCH_HPP diff --git a/test/cfoa/latch_tests.cpp b/test/cfoa/latch_tests.cpp new file mode 100644 index 00000000..8bad302a --- /dev/null +++ b/test/cfoa/latch_tests.cpp @@ -0,0 +1,154 @@ +// 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) + +#define BOOST_ENABLE_ASSERT_HANDLER + +#include "latch.hpp" + +#include + +#include +#include + +struct exception +{ +}; + +namespace boost { + void assertion_failed( + char const* expr, char const* function, char const* file, long line) + { + (void)expr; + (void)function; + (void)file; + (void)line; + throw exception{}; + } +} // namespace boost + +namespace { + void test_max() { BOOST_TEST_EQ(boost::latch::max(), INT_MAX); } + + void test_constructor() + { + { + auto const f = [] { + boost::latch l(-1); + (void)l; + }; + BOOST_TEST_THROWS(f(), exception); + } + + { + std::ptrdiff_t n = 0; + + boost::latch l(n); + BOOST_TEST(l.try_wait()); + } + + { + std::ptrdiff_t n = 16; + + boost::latch l(n); + BOOST_TEST_NOT(l.try_wait()); + + l.count_down(16); + BOOST_TEST(l.try_wait()); + } + +#if PTRDIFF_MAX > INT_MAX + { + auto const f = [] { + std::ptrdiff_t n = INT_MAX; + n += 10; + boost::latch l(n); + (void)l; + }; + BOOST_TEST_THROWS(f(), exception); + } +#endif + } + + void test_count_down_and_wait() + { + constexpr std::ptrdiff_t n = 1024; + + boost::latch l(2 * n); + + bool bs[] = {false, false}; + + std::thread t1([&] { + l.wait(); + BOOST_TEST(bs[0]); + BOOST_TEST(bs[1]); + }); + + std::thread t2([&] { + for (int i = 0; i < n; ++i) { + if (i == (n - 1)) { + bs[0] = true; + } else { + BOOST_TEST_NOT(l.try_wait()); + } + + l.count_down(1); + } + }); + + for (int i = 0; i < n; ++i) { + if (i == (n - 1)) { + bs[1] = true; + } else { + BOOST_TEST_NOT(l.try_wait()); + } + + l.count_down(1); + } + + t1.join(); + t2.join(); + + BOOST_TEST(l.try_wait()); + } + + void test_arrive_and_wait() + { + constexpr std::ptrdiff_t n = 16; + + boost::latch l(2 * n); + + int xs[n] = {0}; + + std::vector threads; + for (int i = 0; i < n; ++i) { + threads.emplace_back([&l, &xs, i] { + for (int j = 0; j < n; ++j) { + BOOST_TEST_EQ(xs[j], 0); + } + + l.arrive_and_wait(2); + + xs[i] = 1; + }); + } + + for (auto& t : threads) { + t.join(); + } + + for (int i = 0; i < n; ++i) { + BOOST_TEST_EQ(xs[i], 1); + } + } +} // namespace + +int main() +{ + test_max(); + test_constructor(); + test_count_down_and_wait(); + test_arrive_and_wait(); + + return boost::report_errors(); +} From 4c117ab20a5f6bcf21eb03d4a89c388ef5efdcd0 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Tue, 25 Apr 2023 15:53:18 +0200 Subject: [PATCH 191/327] made merge blocking --- .../unordered/detail/foa/concurrent_table.hpp | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 1769e242..0d7272eb 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -674,14 +674,11 @@ public: template void merge(concurrent_table& x) { - // TODO: consider grabbing shared access on *this at this level - // TODO: can deadlock if x1.merge(x2) while x2.merge(x1) - auto lck=x.shared_access(); - x.for_all_elements( - group_exclusive{}, + auto lck=exclusive_access(*this,x); + x.super::for_all_elements( /* super::for_all_elements -> unprotected */ [&,this](group_type* pg,unsigned int n,element_type* p){ erase_on_exit e{x,pg,n,p}; - if(!emplace_impl(type_policy::move(*p)))e.rollback(); + if(!unprotected_emplace(type_policy::move(*p)))e.rollback(); }); } @@ -1043,6 +1040,18 @@ private: } } + template + BOOST_FORCEINLINE bool unprotected_emplace(Args&&... args) + { + // TODO: could be made more efficient (nonconcurrent scenario) + for(;;){ + int res=unprotected_norehash_emplace_or_visit( + group_shared{},[](const value_type&){},std::forward(args)...); + if(BOOST_LIKELY(res>=0))return res!=0; + unprotected_rehash_if_full(); + } + } + struct reserve_size { reserve_size(concurrent_table& x_):x{x_} @@ -1129,6 +1138,11 @@ private: void rehash_if_full() { auto lck=exclusive_access(); + unprotected_rehash_if_full(); + } + + void unprotected_rehash_if_full() + { if(this->size_==this->ml)this->unchecked_rehash_for_growth(); } From a7c15e86fc2a467f5973b3e97430839ff10f00dd Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 25 Apr 2023 12:18:15 -0700 Subject: [PATCH 192/327] Update num_threads to use the concurrent hint from the stdlib --- test/cfoa/helpers.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index 7ff48334..a9c27121 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -21,8 +21,10 @@ #include #include #include +#include -constexpr std::size_t const num_threads = 16; +static std::size_t const num_threads = + std::max(2u, std::thread::hardware_concurrency()); struct transp_hash { @@ -326,7 +328,7 @@ std::vector > split( template void thread_runner(std::vector& values, F f) { - boost::latch latch(num_threads); + boost::latch latch(static_cast(num_threads)); std::vector threads; auto subslices = split(values, num_threads); From 2b612ed1206d95af9a28ebe45c5dbdc49e9daa7b Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 25 Apr 2023 13:14:08 -0700 Subject: [PATCH 193/327] Flesh out assign_tests --- test/cfoa/assign_tests.cpp | 232 +++++++++++++++++++++++++++++-------- 1 file changed, 183 insertions(+), 49 deletions(-) diff --git a/test/cfoa/assign_tests.cpp b/test/cfoa/assign_tests.cpp index f7c3000e..d11bd4d4 100644 --- a/test/cfoa/assign_tests.cpp +++ b/test/cfoa/assign_tests.cpp @@ -6,6 +6,12 @@ #include +#if defined(__clang__) && defined(__has_warning) +#if __has_warning("-Wself-assign-overloaded") +#pragma clang diagnostic ignored "-Wself-assign-overloaded" +#endif +#endif + test::seed_t initialize_seed{2762556623}; using test::default_generator; @@ -21,6 +27,43 @@ using map_type = boost::unordered::concurrent_flat_map struct pocca_allocator +{ + using propagate_on_container_copy_assignment = std::true_type; + + int x_ = -1; + + using value_type = T; + + pocca_allocator() = default; + pocca_allocator(pocca_allocator const&) = default; + pocca_allocator(pocca_allocator&&) = default; + + pocca_allocator(int const x) : x_{x} {} + + pocca_allocator& operator=(pocca_allocator const& rhs) + { + if (this != &rhs) { + x_ = rhs.x_; + } + return *this; + } + + template pocca_allocator(pocca_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==(pocca_allocator const& rhs) const { return x_ == rhs.x_; } + bool operator!=(pocca_allocator const& rhs) const { return x_ != rhs.x_; } +}; + namespace { template void copy_assign(G gen, test::random_generator rg) { @@ -28,30 +71,31 @@ namespace { auto reference_map = boost::unordered_flat_map(values.begin(), values.end()); - // to test: - // self-assign - // propagation - // - // lhs empty, rhs empty { raii::reset_counts(); map_type x(0, hasher(1), key_equal(2), allocator_type(3)); - map_type y; - BOOST_TEST(x.empty()); - BOOST_TEST(y.empty()); - y = x; + thread_runner(values, [&x](boost::span s) { + (void)s; + + map_type y; + + BOOST_TEST(x.empty()); + BOOST_TEST(y.empty()); + + y = x; + + BOOST_TEST_EQ(x.hash_function(), y.hash_function()); + BOOST_TEST_EQ(x.key_eq(), y.key_eq()); + BOOST_TEST(x.get_allocator() != y.get_allocator()); + }); BOOST_TEST_EQ(raii::destructor, 0u); BOOST_TEST_EQ(raii::copy_assignment, 0u); BOOST_TEST_EQ(raii::move_assignment, 0u); BOOST_TEST_EQ(raii::copy_constructor, 0u); - - BOOST_TEST_EQ(x.hash_function(), y.hash_function()); - BOOST_TEST_EQ(x.key_eq(), y.key_eq()); - BOOST_TEST(x.get_allocator() != y.get_allocator()); } // lhs non-empty, rhs empty @@ -60,23 +104,28 @@ namespace { map_type x(0, hasher(1), key_equal(2), allocator_type(3)); - map_type y(values.begin(), values.end(), values.size()); + auto const old_size = reference_map.size(); - auto const old_cc = +raii::copy_constructor; - auto const old_size = y.size(); + thread_runner(values, [&x, &values](boost::span s) { + (void)s; - BOOST_TEST(x.empty()); - BOOST_TEST(!y.empty()); - y = x; + map_type y(values.begin(), values.end(), values.size()); - BOOST_TEST_EQ(raii::destructor, 2 * old_size); + BOOST_TEST(x.empty()); + BOOST_TEST(!y.empty()); + + y = x; + + BOOST_TEST_EQ(x.hash_function(), y.hash_function()); + BOOST_TEST_EQ(x.key_eq(), y.key_eq()); + BOOST_TEST(x.get_allocator() != y.get_allocator()); + }); + + BOOST_TEST_EQ(raii::destructor, num_threads * (2 * old_size)); BOOST_TEST_EQ(raii::copy_assignment, 0u); BOOST_TEST_EQ(raii::move_assignment, 0u); - BOOST_TEST_EQ(raii::copy_constructor, old_cc); - - BOOST_TEST_EQ(x.hash_function(), y.hash_function()); - BOOST_TEST_EQ(x.key_eq(), y.key_eq()); - BOOST_TEST(x.get_allocator() != y.get_allocator()); + BOOST_TEST_EQ( + raii::copy_constructor, num_threads * 2 * reference_map.size()); } check_raii_counts(); @@ -87,23 +136,31 @@ namespace { map_type x(values.begin(), values.end(), values.size(), hasher(1), key_equal(2), allocator_type(3)); - map_type y; auto const old_cc = +raii::copy_constructor; - BOOST_TEST(!x.empty()); - BOOST_TEST(y.empty()); - y = x; + thread_runner( + values, [&x, &reference_map](boost::span s) { + (void)s; - BOOST_TEST_EQ(raii::destructor, 0u); + map_type y; + + BOOST_TEST(!x.empty()); + BOOST_TEST(y.empty()); + + y = x; + + BOOST_TEST_EQ(x.hash_function(), y.hash_function()); + BOOST_TEST_EQ(x.key_eq(), y.key_eq()); + BOOST_TEST(x.get_allocator() != y.get_allocator()); + + test_matches_reference(y, reference_map); + }); + + BOOST_TEST_EQ(raii::destructor, num_threads * 2 * x.size()); BOOST_TEST_EQ(raii::copy_assignment, 0u); BOOST_TEST_EQ(raii::move_assignment, 0u); - BOOST_TEST_EQ(raii::copy_constructor, old_cc + (2 * x.size())); - - BOOST_TEST_EQ(x.hash_function(), y.hash_function()); - BOOST_TEST_EQ(x.key_eq(), y.key_eq()); - BOOST_TEST(x.get_allocator() != y.get_allocator()); - - test_matches_reference(y, reference_map); + BOOST_TEST_EQ( + raii::copy_constructor, old_cc + (num_threads * 2 * x.size())); } check_raii_counts(); @@ -114,27 +171,104 @@ namespace { map_type x(values.begin(), values.end(), values.size(), hasher(1), key_equal(2), allocator_type(3)); - map_type y(values.begin(), values.end(), values.size()); - - auto const old_size = y.size(); + auto const old_size = x.size(); auto const old_cc = +raii::copy_constructor; - BOOST_TEST(!x.empty()); - BOOST_TEST(!y.empty()); - y = x; + thread_runner(values, [&x, &values](boost::span s) { + (void)s; - BOOST_TEST_EQ(raii::destructor, 2 * old_size); + map_type y(values.begin(), values.end(), values.size()); + + BOOST_TEST(!x.empty()); + BOOST_TEST(!y.empty()); + + y = x; + + BOOST_TEST_EQ(x.hash_function(), y.hash_function()); + BOOST_TEST_EQ(x.key_eq(), y.key_eq()); + BOOST_TEST(x.get_allocator() != y.get_allocator()); + }); + + BOOST_TEST_EQ(raii::destructor, 2 * num_threads * 2 * old_size); BOOST_TEST_EQ(raii::copy_assignment, 0u); BOOST_TEST_EQ(raii::move_assignment, 0u); - BOOST_TEST_EQ(raii::copy_constructor, old_cc + (2 * x.size())); + BOOST_TEST_EQ( + raii::copy_constructor, old_cc + (2 * num_threads * 2 * x.size())); + } + check_raii_counts(); - BOOST_TEST_EQ(x.hash_function(), y.hash_function()); - BOOST_TEST_EQ(x.key_eq(), y.key_eq()); - BOOST_TEST(x.get_allocator() != y.get_allocator()); + // self-assign + { + raii::reset_counts(); + + map_type x(values.begin(), values.end(), values.size(), hasher(1), + key_equal(2), allocator_type(3)); + + auto const old_cc = +raii::copy_constructor; + + thread_runner( + values, [&x, &reference_map](boost::span s) { + (void)s; + + BOOST_TEST(!x.empty()); + + x = x; + + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + BOOST_TEST(x.get_allocator() == allocator_type(3)); + + test_matches_reference(x, reference_map); + }); + + BOOST_TEST_EQ(raii::destructor, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + } + check_raii_counts(); + + // propagation + { + using pocca_allocator_type = + pocca_allocator >; + + using pocca_map_type = boost::unordered::concurrent_flat_map; + + raii::reset_counts(); + + pocca_map_type x(values.begin(), values.end(), values.size(), hasher(1), + key_equal(2), pocca_allocator_type(3)); + + auto const old_size = x.size(); + auto const old_cc = +raii::copy_constructor; + + thread_runner(values, [&x, &values](boost::span s) { + (void)s; + + pocca_map_type y(values.begin(), values.end(), values.size()); + + BOOST_TEST(!x.empty()); + BOOST_TEST(!y.empty()); + + BOOST_TEST(x.get_allocator() != y.get_allocator()); + + y = x; + + BOOST_TEST_EQ(x.hash_function(), y.hash_function()); + BOOST_TEST_EQ(x.key_eq(), y.key_eq()); + BOOST_TEST(x.get_allocator() == y.get_allocator()); + }); + + BOOST_TEST_EQ(raii::destructor, 2 * num_threads * 2 * old_size); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ( + raii::copy_constructor, old_cc + (2 * num_threads * 2 * x.size())); } check_raii_counts(); } - } // namespace // clang-format off From 0bc4f2c4b951d033fc287a1104b777daeca78068 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Wed, 26 Apr 2023 13:39:38 +0200 Subject: [PATCH 194/327] refactored foa::concurrent_table::merge internals --- .../unordered/detail/foa/concurrent_table.hpp | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 0d7272eb..d5e877cc 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -1043,13 +1043,21 @@ private: template BOOST_FORCEINLINE bool unprotected_emplace(Args&&... args) { + const auto &k=this->key_from(std::forward(args)...); + auto hash=this->hash_for(k); + auto pos0=this->position_for(hash); + // TODO: could be made more efficient (nonconcurrent scenario) - for(;;){ - int res=unprotected_norehash_emplace_or_visit( - group_shared{},[](const value_type&){},std::forward(args)...); - if(BOOST_LIKELY(res>=0))return res!=0; - unprotected_rehash_if_full(); + if(unprotected_visit( + group_shared{},k,pos0,hash,[](const value_type&){}))return false; + + if(BOOST_LIKELY(this->size_ml)){ + this->unchecked_emplace_at(pos0,hash,std::forward(args)...); } + else{ + this->unchecked_emplace_with_rehash(hash,std::forward(args)...); + } + return true; } struct reserve_size @@ -1098,9 +1106,9 @@ private: unprotected_norehash_emplace_or_visit( GroupAccessMode access_mode,F&& f,Args&&... args) { - const auto &k=this->key_from(std::forward(args)...); - auto hash=this->hash_for(k); - auto pos0=this->position_for(hash); + const auto &k=this->key_from(std::forward(args)...); + auto hash=this->hash_for(k); + auto pos0=this->position_for(hash); for(;;){ startover: From d7acb7e8b8e382cac5f48acfa5401345e5c24e40 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 25 Apr 2023 13:41:36 -0700 Subject: [PATCH 195/327] Fix capturing in latch_tests --- test/cfoa/latch_tests.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/cfoa/latch_tests.cpp b/test/cfoa/latch_tests.cpp index 8bad302a..0c7e9ece 100644 --- a/test/cfoa/latch_tests.cpp +++ b/test/cfoa/latch_tests.cpp @@ -114,7 +114,7 @@ namespace { void test_arrive_and_wait() { - constexpr std::ptrdiff_t n = 16; + std::ptrdiff_t const n = 16; boost::latch l(2 * n); @@ -122,7 +122,8 @@ namespace { std::vector threads; for (int i = 0; i < n; ++i) { - threads.emplace_back([&l, &xs, i] { + threads.emplace_back([&l, &xs, i, n] { + (void)n; for (int j = 0; j < n; ++j) { BOOST_TEST_EQ(xs[j], 0); } From 212c6a1e4d251ee4d9a0be375951b55de9290b6a Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 26 Apr 2023 12:53:49 -0700 Subject: [PATCH 196/327] Add prototype of move assignment --- .../boost/unordered/concurrent_flat_map.hpp | 9 + include/boost/unordered/detail/foa/core.hpp | 4 +- test/cfoa/assign_tests.cpp | 213 ++++++++++++++++++ 3 files changed, 224 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 74d97b94..7f38d261 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -258,6 +258,15 @@ namespace boost { return *this; } + concurrent_flat_map& operator=(concurrent_flat_map&& rhs) + noexcept(std::allocator_traits::is_always_equal::value || + std::allocator_traits< + Allocator>::propagate_on_container_move_assignment::value) + { + table_ = std::move(rhs.table_); + return *this; + } + /// Capacity /// diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 1552f41e..6769d179 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1381,8 +1381,8 @@ public: reserve(0); move_assign_if(al(),x.al()); swap(arrays,x.arrays); - swap(ml,x.ml); - swap(size_,x.size_); + swap_size_impl(ml,x.ml); + swap_size_impl(size_,x.size_); } else{ /* noshrink: favor memory reuse over tightness */ diff --git a/test/cfoa/assign_tests.cpp b/test/cfoa/assign_tests.cpp index d11bd4d4..d527eb46 100644 --- a/test/cfoa/assign_tests.cpp +++ b/test/cfoa/assign_tests.cpp @@ -119,6 +119,8 @@ namespace { BOOST_TEST_EQ(x.hash_function(), y.hash_function()); BOOST_TEST_EQ(x.key_eq(), y.key_eq()); BOOST_TEST(x.get_allocator() != y.get_allocator()); + + BOOST_TEST(y.empty()); }); BOOST_TEST_EQ(raii::destructor, num_threads * (2 * old_size)); @@ -269,6 +271,212 @@ namespace { } check_raii_counts(); } + + template void move_assign(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + + // move assignment has more complex requirements than copying + // equal allocators: + // lhs empty, rhs non-empty + // lhs non-empty, rhs empty + // lhs non-empty, rhs non-empty + // + // unequal allocators: + // lhs non-empty, rhs non-empty + // + // pocma + // self move-assign + + // lhs empty, rhs empty + { + raii::reset_counts(); + + map_type x(0, hasher(1), key_equal(2), allocator_type(3)); + + std::atomic num_transfers{0}; + + thread_runner( + values, [&x, &num_transfers](boost::span s) { + (void)s; + + map_type y(0, hasher(2), key_equal(1), allocator_type(3)); + + BOOST_TEST(x.empty()); + BOOST_TEST(y.empty()); + BOOST_TEST(x.get_allocator() == y.get_allocator()); + + y = std::move(x); + if (y.hash_function() == hasher(1)) { + ++num_transfers; + BOOST_TEST_EQ(y.key_eq(), key_equal(2)); + } else { + BOOST_TEST_EQ(y.hash_function(), hasher(2)); + BOOST_TEST_EQ(y.key_eq(), key_equal(1)); + } + + BOOST_TEST_EQ(x.hash_function(), hasher(2)); + BOOST_TEST_EQ(x.key_eq(), key_equal(1)); + BOOST_TEST(x.get_allocator() == y.get_allocator()); + }); + + BOOST_TEST_EQ(num_transfers, 1u); + + BOOST_TEST_EQ(raii::destructor, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + } + + // lhs non-empty, rhs empty + { + raii::reset_counts(); + + map_type x(0, hasher(1), key_equal(2), allocator_type(3)); + + std::atomic num_transfers{0}; + + thread_runner( + values, [&x, &values, &num_transfers](boost::span s) { + (void)s; + + map_type y(values.begin(), values.end(), values.size(), hasher(2), + key_equal(1), allocator_type(3)); + + BOOST_TEST(x.empty()); + BOOST_TEST(!y.empty()); + BOOST_TEST(x.get_allocator() == y.get_allocator()); + + y = std::move(x); + if (y.hash_function() == hasher(1)) { + ++num_transfers; + BOOST_TEST_EQ(y.key_eq(), key_equal(2)); + } else { + BOOST_TEST_EQ(y.hash_function(), hasher(2)); + BOOST_TEST_EQ(y.key_eq(), key_equal(1)); + } + + BOOST_TEST_EQ(x.hash_function(), hasher(2)); + BOOST_TEST_EQ(x.key_eq(), key_equal(1)); + BOOST_TEST(x.get_allocator() == y.get_allocator()); + + BOOST_TEST(y.empty()); + }); + + BOOST_TEST_EQ(num_transfers, 1u); + + BOOST_TEST_EQ(raii::destructor, num_threads * 2 * reference_map.size()); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ( + raii::copy_constructor, num_threads * 2 * reference_map.size()); + } + check_raii_counts(); + + // lhs empty, rhs non-empty + { + raii::reset_counts(); + + map_type x(values.begin(), values.end(), values.size(), hasher(1), + key_equal(2), allocator_type(3)); + + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + std::atomic num_transfers{0}; + + thread_runner(values, + [&x, &reference_map, &num_transfers](boost::span s) { + (void)s; + + map_type y(allocator_type(3)); + + BOOST_TEST(y.empty()); + BOOST_TEST(x.get_allocator() == y.get_allocator()); + + y = std::move(x); + if (!y.empty()) { + ++num_transfers; + test_matches_reference(y, reference_map); + + BOOST_TEST_EQ(y.hash_function(), hasher(1)); + BOOST_TEST_EQ(y.key_eq(), key_equal(2)); + } else { + BOOST_TEST_EQ(y.hash_function(), hasher()); + BOOST_TEST_EQ(y.key_eq(), key_equal()); + } + + BOOST_TEST(x.empty()); + + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + BOOST_TEST(x.get_allocator() == y.get_allocator()); + }); + + BOOST_TEST_EQ(num_transfers, 1u); + + BOOST_TEST_EQ(raii::destructor, 2 * reference_map.size()); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + } + check_raii_counts(); + + // lhs non-empty, rhs non-empty + { + raii::reset_counts(); + + map_type x(values.begin(), values.end(), values.size(), hasher(1), + key_equal(2), allocator_type(3)); + + auto const old_size = x.size(); + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + std::atomic num_transfers{0}; + + thread_runner(values, [&x, &values, &num_transfers, &reference_map]( + boost::span s) { + (void)s; + + map_type y(values.begin(), values.end(), values.size(), hasher(2), + key_equal(1), allocator_type(3)); + + BOOST_TEST(!y.empty()); + BOOST_TEST(x.get_allocator() == y.get_allocator()); + + y = std::move(x); + if (y.hash_function() == hasher(1)) { + ++num_transfers; + test_matches_reference(y, reference_map); + + BOOST_TEST_EQ(y.key_eq(), key_equal(2)); + } else { + BOOST_TEST_EQ(y.hash_function(), hasher(2)); + BOOST_TEST_EQ(y.key_eq(), key_equal(1)); + } + + BOOST_TEST(x.empty()); + + BOOST_TEST_EQ(x.hash_function(), hasher(2)); + BOOST_TEST_EQ(x.key_eq(), key_equal(1)); + BOOST_TEST(x.get_allocator() == y.get_allocator()); + }); + + BOOST_TEST_EQ(num_transfers, 1u); + + BOOST_TEST_EQ( + raii::destructor, 2 * old_size + num_threads * 2 * old_size); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + BOOST_TEST_EQ(raii::copy_constructor, + old_cc + (num_threads * 2 * reference_map.size())); + } + check_raii_counts(); + } } // namespace // clang-format off @@ -276,6 +484,11 @@ UNORDERED_TEST( copy_assign, ((value_type_generator)) ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + move_assign, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) // clang-format on RUN_TESTS() From 7833a8359dab598368cea09dbd425faedc4d08e8 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 26 Apr 2023 13:58:20 -0700 Subject: [PATCH 197/327] Use Core's allocator access to handle allocator_traits not having uniform support in early C++11 compilers --- include/boost/unordered/concurrent_flat_map.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 7f38d261..98dc2bd5 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -259,9 +259,9 @@ namespace boost { } concurrent_flat_map& operator=(concurrent_flat_map&& rhs) - noexcept(std::allocator_traits::is_always_equal::value || - std::allocator_traits< - Allocator>::propagate_on_container_move_assignment::value) + noexcept(boost::allocator_is_always_equal::type::value || + boost::allocator_propagate_on_container_move_assignment< + Allocator>::type::value) { table_ = std::move(rhs.table_); return *this; From 0959df18960db166ab3f8ca10128df807a3769d0 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 27 Apr 2023 09:30:49 -0700 Subject: [PATCH 198/327] Flesh out move assignment tests --- test/cfoa/assign_tests.cpp | 194 +++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) diff --git a/test/cfoa/assign_tests.cpp b/test/cfoa/assign_tests.cpp index d527eb46..8734433f 100644 --- a/test/cfoa/assign_tests.cpp +++ b/test/cfoa/assign_tests.cpp @@ -7,11 +7,17 @@ #include #if defined(__clang__) && defined(__has_warning) + #if __has_warning("-Wself-assign-overloaded") #pragma clang diagnostic ignored "-Wself-assign-overloaded" #endif + +#if __has_warning("-Wself-move") +#pragma clang diagnostic ignored "-Wself-move" #endif +#endif /* defined(__clang__) && defined(__has_warning) */ + test::seed_t initialize_seed{2762556623}; using test::default_generator; @@ -64,6 +70,43 @@ template struct pocca_allocator bool operator!=(pocca_allocator const& rhs) const { return x_ != rhs.x_; } }; +template struct pocma_allocator +{ + using propagate_on_container_move_assignment = std::true_type; + + int x_ = -1; + + using value_type = T; + + pocma_allocator() = default; + pocma_allocator(pocma_allocator const&) = default; + pocma_allocator(pocma_allocator&&) = default; + + pocma_allocator(int const x) : x_{x} {} + + pocma_allocator& operator=(pocma_allocator const& rhs) + { + if (this != &rhs) { + x_ = rhs.x_; + } + return *this; + } + + template pocma_allocator(pocma_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==(pocma_allocator const& rhs) const { return x_ == rhs.x_; } + bool operator!=(pocma_allocator const& rhs) const { return x_ != rhs.x_; } +}; + namespace { template void copy_assign(G gen, test::random_generator rg) { @@ -476,6 +519,157 @@ namespace { old_cc + (num_threads * 2 * reference_map.size())); } check_raii_counts(); + + // lhs non-empty, rhs non-empty, unequal allocators, no propagation + { + raii::reset_counts(); + + map_type x(values.begin(), values.end(), values.size(), hasher(1), + key_equal(2), allocator_type(3)); + + auto const old_size = x.size(); + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + std::atomic num_transfers{0}; + + thread_runner(values, [&x, &values, &num_transfers, &reference_map]( + boost::span s) { + (void)s; + + map_type y(values.begin(), values.end(), values.size(), hasher(2), + key_equal(1), allocator_type(13)); + + BOOST_TEST( + !boost::allocator_is_always_equal::type::value); + + BOOST_TEST(!boost::allocator_propagate_on_container_move_assignment< + allocator_type>::type::value); + + BOOST_TEST(!y.empty()); + BOOST_TEST(x.get_allocator() != y.get_allocator()); + + y = std::move(x); + if (y.hash_function() == hasher(1)) { + ++num_transfers; + test_matches_reference(y, reference_map); + + BOOST_TEST_EQ(y.key_eq(), key_equal(2)); + } else { + BOOST_TEST_EQ(y.hash_function(), hasher(2)); + BOOST_TEST_EQ(y.key_eq(), key_equal(1)); + } + + BOOST_TEST(x.empty()); + + BOOST_TEST_EQ(x.hash_function(), hasher(2)); + BOOST_TEST_EQ(x.key_eq(), key_equal(1)); + BOOST_TEST(x.get_allocator() != y.get_allocator()); + }); + + BOOST_TEST_EQ(num_transfers, 1u); + + BOOST_TEST_EQ( + raii::destructor, 2 * 2 * old_size + num_threads * 2 * old_size); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::move_constructor, old_mc + 2 * old_size); + BOOST_TEST_EQ(raii::copy_constructor, + old_cc + (num_threads * 2 * reference_map.size())); + } + check_raii_counts(); + + // lhs non-empty, rhs non-empty, pocma + { + raii::reset_counts(); + + using pocma_allocator_type = + pocma_allocator >; + + using pocma_map_type = boost::unordered::concurrent_flat_map; + + pocma_map_type x(values.begin(), values.end(), values.size(), hasher(1), + key_equal(2), pocma_allocator_type(3)); + + auto const old_size = x.size(); + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + std::atomic num_transfers{0}; + + thread_runner(values, [&x, &values, &num_transfers, &reference_map]( + boost::span s) { + (void)s; + + pocma_map_type y(values.begin(), values.end(), values.size(), hasher(2), + key_equal(1), pocma_allocator_type(13)); + + BOOST_TEST(!y.empty()); + BOOST_TEST(x.get_allocator() != y.get_allocator()); + + y = std::move(x); + if (y.hash_function() == hasher(1)) { + ++num_transfers; + test_matches_reference(y, reference_map); + + BOOST_TEST_EQ(y.key_eq(), key_equal(2)); + } else { + BOOST_TEST_EQ(y.hash_function(), hasher(2)); + BOOST_TEST_EQ(y.key_eq(), key_equal(1)); + } + + BOOST_TEST(x.empty()); + + BOOST_TEST_EQ(x.hash_function(), hasher(2)); + BOOST_TEST_EQ(x.key_eq(), key_equal(1)); + BOOST_TEST(x.get_allocator() == y.get_allocator()); + }); + + BOOST_TEST_EQ(num_transfers, 1u); + + BOOST_TEST_EQ( + raii::destructor, 2 * old_size + num_threads * 2 * old_size); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + BOOST_TEST_EQ(raii::copy_constructor, + old_cc + (num_threads * 2 * reference_map.size())); + } + check_raii_counts(); + + // self-assign + { + raii::reset_counts(); + + map_type x(values.begin(), values.end(), values.size(), hasher(1), + key_equal(2), allocator_type(3)); + + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + thread_runner( + values, [&x, &reference_map](boost::span s) { + (void)s; + + x = std::move(x); + + BOOST_TEST(!x.empty()); + + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + BOOST_TEST(x.get_allocator() == allocator_type(3)); + + test_matches_reference(x, reference_map); + }); + + BOOST_TEST_EQ(raii::destructor, 0); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + } + check_raii_counts(); } } // namespace From 187fd3e71ecbd34976ceb353b1179ad94b20a4b0 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 27 Apr 2023 12:00:42 -0700 Subject: [PATCH 199/327] Implement initializer_list assignment --- .../boost/unordered/concurrent_flat_map.hpp | 6 ++ .../unordered/detail/foa/concurrent_table.hpp | 12 +++ test/cfoa/assign_tests.cpp | 79 +++++++++++++++++-- 3 files changed, 91 insertions(+), 6 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 98dc2bd5..12778f64 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -267,6 +267,12 @@ namespace boost { return *this; } + concurrent_flat_map& operator=(std::initializer_list ilist) + { + table_ = ilist; + return *this; + } + /// Capacity /// diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index d5e877cc..fe314034 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -410,6 +410,18 @@ public: return *this; } + concurrent_table& operator=(std::initializer_list il) { + auto lck=exclusive_access(); + super::clear(); + if (super::capacity()unprotected_emplace(v); + } + return *this; + } + allocator_type get_allocator()const noexcept { auto lck=shared_access(); diff --git a/test/cfoa/assign_tests.cpp b/test/cfoa/assign_tests.cpp index 8734433f..92ba0bbf 100644 --- a/test/cfoa/assign_tests.cpp +++ b/test/cfoa/assign_tests.cpp @@ -317,6 +317,26 @@ namespace { template void move_assign(G gen, test::random_generator rg) { + using pocma_allocator_type = pocma_allocator >; + + using pocma_map_type = boost::unordered::concurrent_flat_map; + + BOOST_STATIC_ASSERT( + std::is_nothrow_move_assignable, std::equal_to, + std::allocator > > >::value); + + BOOST_STATIC_ASSERT( + std::is_nothrow_move_assignable, std::equal_to, + pocma_allocator > > >::value); + + BOOST_STATIC_ASSERT( + !std::is_nothrow_move_assignable, std::equal_to, + stateful_allocator > > >::value); + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); auto reference_map = boost::unordered_flat_map(values.begin(), values.end()); @@ -583,12 +603,6 @@ namespace { { raii::reset_counts(); - using pocma_allocator_type = - pocma_allocator >; - - using pocma_map_type = boost::unordered::concurrent_flat_map; - pocma_map_type x(values.begin(), values.end(), values.size(), hasher(1), key_equal(2), pocma_allocator_type(3)); @@ -671,6 +685,59 @@ namespace { } check_raii_counts(); } + + UNORDERED_AUTO_TEST (initializer_list_assignment) { + std::initializer_list values{ + map_value_type{raii{0}, raii{0}}, + map_value_type{raii{1}, raii{1}}, + map_value_type{raii{2}, raii{2}}, + map_value_type{raii{3}, raii{3}}, + map_value_type{raii{4}, raii{4}}, + map_value_type{raii{5}, raii{5}}, + map_value_type{raii{6}, raii{6}}, + map_value_type{raii{6}, raii{6}}, + map_value_type{raii{7}, raii{7}}, + map_value_type{raii{8}, raii{8}}, + map_value_type{raii{9}, raii{9}}, + map_value_type{raii{10}, raii{10}}, + map_value_type{raii{9}, raii{9}}, + map_value_type{raii{8}, raii{8}}, + map_value_type{raii{7}, raii{7}}, + map_value_type{raii{6}, raii{6}}, + map_value_type{raii{5}, raii{5}}, + map_value_type{raii{4}, raii{4}}, + map_value_type{raii{3}, raii{3}}, + map_value_type{raii{2}, raii{2}}, + map_value_type{raii{1}, raii{1}}, + map_value_type{raii{0}, raii{0}}, + }; + + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + auto v = std::vector(values.begin(), values.end()); + + { + raii::reset_counts(); + map_type x(0, hasher(1), key_equal(2), allocator_type(3)); + + thread_runner(v, [&x, &values](boost::span s) { + (void)s; + x = values; + }); + + test_matches_reference(x, reference_map); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + BOOST_TEST(x.get_allocator() == allocator_type(3)); + + BOOST_TEST_EQ(raii::copy_constructor, num_threads * 2 * x.size()); + BOOST_TEST_EQ(raii::destructor, (num_threads - 1) * 2 * x.size()); + BOOST_TEST_EQ(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + check_raii_counts(); + } } // namespace // clang-format off From 135c9586afa62cd10342d508fffebbb6088011d1 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 27 Apr 2023 15:23:21 -0700 Subject: [PATCH 200/327] Add fuzzy test mixing copy-assignment with insertion --- test/cfoa/assign_tests.cpp | 68 +++++++++++++++++++++++++++++++++++++- test/cfoa/helpers.hpp | 14 +++++++- 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/test/cfoa/assign_tests.cpp b/test/cfoa/assign_tests.cpp index 92ba0bbf..e3f5b39f 100644 --- a/test/cfoa/assign_tests.cpp +++ b/test/cfoa/assign_tests.cpp @@ -677,7 +677,7 @@ namespace { test_matches_reference(x, reference_map); }); - BOOST_TEST_EQ(raii::destructor, 0); + BOOST_TEST_EQ(raii::destructor, 0u); BOOST_TEST_EQ(raii::copy_assignment, 0u); BOOST_TEST_EQ(raii::move_assignment, 0u); BOOST_TEST_EQ(raii::move_constructor, old_mc); @@ -738,6 +738,67 @@ namespace { } check_raii_counts(); } + + template void insert_and_assign(G gen, test::random_generator rg) + { + + std::thread t1, t2, t3; + + boost::latch start_latch(2), end_latch(2); + + auto v1 = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto v2 = v1; + shuffle_values(v2); + + auto reference_map = + boost::unordered_flat_map(v1.begin(), v1.end()); + + raii::reset_counts(); + { + map_type map1(v1.size(), hasher(1), key_equal(2), allocator_type(3)); + map_type map2(v2.size(), hasher(1), key_equal(2), allocator_type(3)); + + t1 = std::thread([&v1, &map1, &start_latch, &end_latch] { + start_latch.arrive_and_wait(); + for (auto const& v : v1) { + map1.insert(v); + } + end_latch.arrive_and_wait(); + }); + + t2 = std::thread([&v2, &map2, &end_latch, &start_latch] { + start_latch.arrive_and_wait(); + for (auto const& v : v2) { + map2.insert(v); + } + end_latch.arrive_and_wait(); + }); + + std::atomic num_assignments{0}; + t3 = std::thread([&map1, &map2, &end_latch, &num_assignments] { + while (map1.empty() && map2.empty()) { + } + + while (!end_latch.try_wait()) { + map1 = map2; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + map2 = map1; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + ++num_assignments; + } + }); + + t1.join(); + t2.join(); + t3.join(); + + BOOST_TEST_GT(num_assignments, 0u); + + test_fuzzy_matches_reference(map1, reference_map, rg); + test_fuzzy_matches_reference(map2, reference_map, rg); + } + check_raii_counts(); + } } // namespace // clang-format off @@ -750,6 +811,11 @@ UNORDERED_TEST( move_assign, ((value_type_generator)) ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + insert_and_assign, + ((init_type_generator)) + ((default_generator)(sequential)(limited_range))) // clang-format on RUN_TESTS() diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index a9c27121..aa5a86a4 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -14,14 +14,16 @@ #include #include +#include #include #include #include #include #include +#include #include -#include #include +#include static std::size_t const num_threads = std::max(2u, std::thread::hardware_concurrency()); @@ -239,6 +241,8 @@ struct raii copy_assignment = 0; move_assignment = 0; } + + friend void swap(raii& lhs, raii& rhs) { std::swap(lhs.x_, rhs.x_); } }; std::atomic raii::default_constructor{0}; @@ -384,4 +388,12 @@ void check_raii_counts() raii::destructor); } +template void shuffle_values(std::vector v) +{ + std::random_device rd; + std::mt19937 g(rd()); + + std::shuffle(v.begin(), v.end(), g); +} + #endif // BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP \ No newline at end of file From 081932221f9c33542f58a140e589be9891315242 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 28 Apr 2023 13:42:28 -0700 Subject: [PATCH 201/327] Attempt to fix flaky assign_tests --- test/cfoa/assign_tests.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/cfoa/assign_tests.cpp b/test/cfoa/assign_tests.cpp index e3f5b39f..3936572d 100644 --- a/test/cfoa/assign_tests.cpp +++ b/test/cfoa/assign_tests.cpp @@ -777,15 +777,16 @@ namespace { std::atomic num_assignments{0}; t3 = std::thread([&map1, &map2, &end_latch, &num_assignments] { while (map1.empty() && map2.empty()) { + std::this_thread::sleep_for(std::chrono::microseconds(10)); } - while (!end_latch.try_wait()) { + do { map1 = map2; std::this_thread::sleep_for(std::chrono::milliseconds(100)); map2 = map1; std::this_thread::sleep_for(std::chrono::milliseconds(100)); ++num_assignments; - } + } while (!end_latch.try_wait()); }); t1.join(); From bee9a3cb1aa1326fafec218d895399e566ffb92d Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 28 Apr 2023 14:10:58 -0700 Subject: [PATCH 202/327] Split Drone jobs to help with CI timeouts and load --- .drone.jsonnet | 93 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 17 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index b153482e..4e568245 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -165,9 +165,15 @@ local windows_pipeline(name, image, environment, arch = "amd64") = ), linux_pipeline( - "Linux 20.04 GCC 9* 32/64", + "Linux 20.04 GCC 9* 32/64 (03,11,14)", "cppalliance/droneubuntu2004:1", - { TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14,17,2a', ADDRMD: '32,64' }, + { TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14', ADDRMD: '32,64' }, + ), + + linux_pipeline( + "Linux 20.04 GCC 9* 32/64 (17,2a)", + "cppalliance/droneubuntu2004:1", + { TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '17,2a', ADDRMD: '32,64' }, ), linux_pipeline( @@ -178,36 +184,70 @@ local windows_pipeline(name, image, environment, arch = "amd64") = ), linux_pipeline( - "Linux 20.04 GCC 9* S390x", + "Linux 20.04 GCC 9* S390x (03,11,14)", "cppalliance/droneubuntu2004:multiarch", - { TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14,17,2a' }, + { TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14' }, arch="s390x", ), linux_pipeline( - "Linux 20.04 GCC 10 32/64", + "Linux 20.04 GCC 9* S390x (17,2a)", + "cppalliance/droneubuntu2004:multiarch", + { TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '17,2a' }, + arch="s390x", + ), + + linux_pipeline( + "Linux 20.04 GCC 10 32/64 (03,11,14)", "cppalliance/droneubuntu2004:1", - { TOOLSET: 'gcc', COMPILER: 'g++-10', CXXSTD: '03,11,14,17,20', ADDRMD: '32,64' }, + { TOOLSET: 'gcc', COMPILER: 'g++-10', CXXSTD: '03,11,14', ADDRMD: '32,64' }, "g++-10-multilib", ), linux_pipeline( - "Linux 22.04 GCC 11* 32/64", + "Linux 20.04 GCC 10 32/64 (17,20)", + "cppalliance/droneubuntu2004:1", + { TOOLSET: 'gcc', COMPILER: 'g++-10', CXXSTD: '17,20', ADDRMD: '32,64' }, + "g++-10-multilib", + ), + + linux_pipeline( + "Linux 22.04 GCC 11* 32/64 (03,11,14)", "cppalliance/droneubuntu2204:1", - { TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14,17,2a', ADDRMD: '32,64' }, + { TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14', ADDRMD: '32,64' }, + ), + + linux_pipeline( + "Linux 22.04 GCC 11* 32/64 (17,2a)", + "cppalliance/droneubuntu2204:1", + { TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '17,2a', ADDRMD: '32,64' }, ), linux_pipeline( "Linux 22.04 GCC 12 32 ASAN (03,11,14)", "cppalliance/droneubuntu2204:1", - { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '03,11,14', ADDRMD: '32' } + asan, + { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '03,11', ADDRMD: '32' } + asan, "g++-12-multilib", ), linux_pipeline( - "Linux 22.04 GCC 12 32 ASAN (17,20,2b)", + "Linux 22.04 GCC 12 32 ASAN (14)", "cppalliance/droneubuntu2204:1", - { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17,20,2b', ADDRMD: '32' } + asan, + { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '14', ADDRMD: '32' } + asan, + "g++-12-multilib", + ), + + linux_pipeline( + "Linux 22.04 GCC 12 32 ASAN (17,20)", + "cppalliance/droneubuntu2204:1", + { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17,20', ADDRMD: '32' } + asan, + "g++-12-multilib", + ), + + linux_pipeline( + "Linux 22.04 GCC 12 32 ASAN (2b)", + "cppalliance/droneubuntu2204:1", + { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '2b', ADDRMD: '32' } + asan, "g++-12-multilib", ), @@ -219,9 +259,16 @@ local windows_pipeline(name, image, environment, arch = "amd64") = ), linux_pipeline( - "Linux 22.04 GCC 12 64 ASAN (17,20,2b)", + "Linux 22.04 GCC 12 64 ASAN (17,20)", "cppalliance/droneubuntu2204:1", - { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17,20,2b', ADDRMD: '64' } + asan, + { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17,20', ADDRMD: '64' } + asan, + "g++-12-multilib", + ), + + linux_pipeline( + "Linux 22.04 GCC 12 64 ASAN (2b)", + "cppalliance/droneubuntu2204:1", + { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '2b', ADDRMD: '64' } + asan, "g++-12-multilib", ), @@ -345,9 +392,16 @@ local windows_pipeline(name, image, environment, arch = "amd64") = ), linux_pipeline( - "Linux 22.04 Clang 14 ASAN", + "Linux 22.04 Clang 14 ASAN (03,11,14)", "cppalliance/droneubuntu2204:1", - { TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '03,11,14,17,20' } + asan, + { TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '03,11,14' } + asan, + "clang-14", + ), + + linux_pipeline( + "Linux 22.04 Clang 14 ASAN (17,20)", + "cppalliance/droneubuntu2204:1", + { TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '17,20' } + asan, "clang-14", ), @@ -367,8 +421,13 @@ local windows_pipeline(name, image, environment, arch = "amd64") = ), macos_pipeline( - "MacOS 10.15 Xcode 12.2 UBSAN", - { TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '03,11,14,1z' } + ubsan, + "MacOS 10.15 Xcode 12.2 UBSAN (03,11)", + { TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '03,11' } + ubsan, + ), + + macos_pipeline( + "MacOS 10.15 Xcode 12.2 UBSAN (14,1z)", + { TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '14,1z' } + ubsan, ), macos_pipeline( From 615ce1e9b69e5040dc46ed2a6d40be7897a1d6fd Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 29 Apr 2023 11:35:11 +0200 Subject: [PATCH 203/327] refactored unprotected_rehash_if_full out --- include/boost/unordered/detail/foa/concurrent_table.hpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index fe314034..b72b4910 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -1158,11 +1158,6 @@ private: void rehash_if_full() { auto lck=exclusive_access(); - unprotected_rehash_if_full(); - } - - void unprotected_rehash_if_full() - { if(this->size_==this->ml)this->unchecked_rehash_for_growth(); } From c52ad849eacd7c3e087065839f314d8e6cdfc9cd Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 1 May 2023 11:04:50 -0700 Subject: [PATCH 204/327] Add policy check that excludes unsequenced policies It's technically UB for the callable in an unsequenced policy to acquire a lock so we add static_assert()s to catch potential user error. --- include/boost/unordered/concurrent_flat_map.hpp | 13 +++++++++++++ test/cfoa/erase_tests.cpp | 2 +- test/cfoa/visit_tests.cpp | 6 +++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 12778f64..c3cf5aeb 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -37,6 +37,14 @@ boost::unordered::detail::is_invocable::value, \ "The provided Callable must be invocable with `value_type const&`"); +#define BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(P) \ + static_assert(!std::is_base_of::value, \ + "ExecPolicy must be sequenced."); \ + static_assert( \ + !std::is_base_of::value, \ + "ExecPolicy must be sequenced."); + #define BOOST_UNORDERED_COMMA , #define BOOST_UNORDERED_LAST_ARG(Arg, Args) \ @@ -357,6 +365,7 @@ namespace boost { visit_all(ExecPolicy p, F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) table_.visit_all(p, f); } @@ -367,6 +376,7 @@ namespace boost { visit_all(ExecPolicy p, F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) table_.visit_all(p, f); } @@ -377,6 +387,7 @@ namespace boost { cvisit_all(ExecPolicy p, F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) table_.cvisit_all(p, f); } #endif @@ -663,6 +674,8 @@ namespace boost { void>::type erase_if(ExecPolicy p, F f) { + BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) + BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) table_.erase_if(p, f); } #endif diff --git a/test/cfoa/erase_tests.cpp b/test/cfoa/erase_tests.cpp index 2066e07e..3bffb8a2 100644 --- a/test/cfoa/erase_tests.cpp +++ b/test/cfoa/erase_tests.cpp @@ -281,7 +281,7 @@ namespace { thread_runner(values, [&num_invokes, &x, threshold](boost::span s) { (void)s; x.erase_if( - std::execution::par_unseq, [&num_invokes, threshold](value_type& v) { + std::execution::par, [&num_invokes, threshold](value_type& v) { ++num_invokes; return v.second.x_ > threshold; }); diff --git a/test/cfoa/visit_tests.cpp b/test/cfoa/visit_tests.cpp index 4c22782c..d49be2df 100644 --- a/test/cfoa/visit_tests.cpp +++ b/test/cfoa/visit_tests.cpp @@ -299,7 +299,7 @@ namespace { thread_runner(values, [&x, &mut_visitor](boost::span) { std::atomic num_visits{0}; - x.visit_all(std::execution::par_unseq, mut_visitor(num_visits)); + x.visit_all(std::execution::par, mut_visitor(num_visits)); BOOST_TEST_EQ(x.size(), num_visits); }); } @@ -309,7 +309,7 @@ namespace { std::atomic num_visits{0}; auto const& y = x; - y.visit_all(std::execution::par_unseq, const_visitor(num_visits)); + y.visit_all(std::execution::par, const_visitor(num_visits)); BOOST_TEST_EQ(x.size(), num_visits); }); } @@ -317,7 +317,7 @@ namespace { { thread_runner(values, [&x, &const_visitor](boost::span) { std::atomic num_visits{0}; - x.cvisit_all(std::execution::par_unseq, const_visitor(num_visits)); + x.cvisit_all(std::execution::par, const_visitor(num_visits)); BOOST_TEST_EQ(x.size(), num_visits); }); } From 011b7a5969abcca525d0fdd3d421908043485f89 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 1 May 2023 11:58:45 -0700 Subject: [PATCH 205/327] Add initial impl of clear --- .../boost/unordered/concurrent_flat_map.hpp | 2 + test/Jamfile.v2 | 1 + test/cfoa/clear_tests.cpp | 110 ++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 test/cfoa/clear_tests.cpp diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index c3cf5aeb..16809138 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -685,6 +685,8 @@ namespace boost { return table_.erase_if(f); } + void clear() noexcept { table_.clear(); } + /// Hash Policy /// void rehash(size_type n) { table_.rehash(n); } diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index d6038bdf..f3f6feb5 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -184,6 +184,7 @@ local CFOA_TESTS = visit_tests constructor_tests assign_tests + clear_tests ; for local test in $(CFOA_TESTS) diff --git a/test/cfoa/clear_tests.cpp b/test/cfoa/clear_tests.cpp new file mode 100644 index 00000000..12c0fc59 --- /dev/null +++ b/test/cfoa/clear_tests.cpp @@ -0,0 +1,110 @@ +// 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{674140082}; + +using test::default_generator; +using test::limited_range; +using test::sequential; + +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; + +namespace { + template void clear_tests(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + + raii::reset_counts(); + + map_type x(values.begin(), values.end(), values.size(), hasher(1), + key_equal(2), allocator_type(3)); + + BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size()); + BOOST_TEST_EQ(raii::destructor, 0u); + + thread_runner(values, [&x](boost::span s) { + (void)s; + x.clear(); + }); + + BOOST_TEST(x.empty()); + + check_raii_counts(); + } + + template void insert_and_clear(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + + raii::reset_counts(); + + std::thread t1, t2; + + { + map_type x(0, hasher(1), key_equal(2), allocator_type(3)); + + std::mutex m; + std::condition_variable cv; + + std::atomic done{false}; + + t1 = std::thread([&x, &values, &cv, &done] { + for (auto i = 0u; i < values.size(); ++i) { + x.insert(values[i]); + if (i % 100 == 0) { + cv.notify_all(); + } + } + done = true; + }); + + t2 = std::thread([&x, &m, &cv, &done] { + while (!done) { + { + std::unique_lock lk(m); + cv.wait(lk, [] { return true; }); + } + x.clear(); + } + }); + + t1.join(); + t2.join(); + + BOOST_TEST_LT(x.size(), reference_map.size()); + if (!x.empty()) { + test_fuzzy_matches_reference(x, reference_map, rg); + } + } + + check_raii_counts(); + } + +} // namespace + +// clang-format off +UNORDERED_TEST( + clear_tests, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST(insert_and_clear, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) +// clang-format on + +RUN_TESTS() From 40c4d456f384c2b4bc8d47eb21206261a42b0acc Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 1 May 2023 15:21:25 -0700 Subject: [PATCH 206/327] Clean up for CI --- include/boost/unordered/concurrent_flat_map.hpp | 12 +++++++++++- test/cfoa/clear_tests.cpp | 1 - 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 16809138..ffc15f9c 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -37,6 +37,8 @@ boost::unordered::detail::is_invocable::value, \ "The provided Callable must be invocable with `value_type const&`"); +#if BOOST_CXX_VERSION >= 202002L + #define BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(P) \ static_assert(!std::is_base_of::value, \ @@ -45,6 +47,14 @@ !std::is_base_of::value, \ "ExecPolicy must be sequenced."); +#else + +#define BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(P) \ + static_assert(!std::is_base_of::value, \ + "ExecPolicy must be sequenced."); +#endif + #define BOOST_UNORDERED_COMMA , #define BOOST_UNORDERED_LAST_ARG(Arg, Args) \ @@ -674,7 +684,6 @@ namespace boost { void>::type erase_if(ExecPolicy p, F f) { - BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) table_.erase_if(p, f); } @@ -707,6 +716,7 @@ namespace boost { #undef BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE #undef BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE +#undef BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY #undef BOOST_UNORDERED_COMMA #undef BOOST_UNORDERED_LAST_ARG #undef BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE diff --git a/test/cfoa/clear_tests.cpp b/test/cfoa/clear_tests.cpp index 12c0fc59..a3f1e846 100644 --- a/test/cfoa/clear_tests.cpp +++ b/test/cfoa/clear_tests.cpp @@ -85,7 +85,6 @@ namespace { t1.join(); t2.join(); - BOOST_TEST_LT(x.size(), reference_map.size()); if (!x.empty()) { test_fuzzy_matches_reference(x, reference_map, rg); } From 2ea0dbf30e20d21629f8698a1290bcc8bcfd41f3 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 2 May 2023 13:44:27 -0700 Subject: [PATCH 207/327] 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() From a9bf367d6eab33617c3d6019d5b07c330cd5da64 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 2 May 2023 15:44:21 -0700 Subject: [PATCH 208/327] Test if being the kind of program that calls yield() pays dividends for flaky CI failures --- test/cfoa/swap_tests.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/cfoa/swap_tests.cpp b/test/cfoa/swap_tests.cpp index 40180168..9fae85cd 100644 --- a/test/cfoa/swap_tests.cpp +++ b/test/cfoa/swap_tests.cpp @@ -191,6 +191,7 @@ namespace { if (idx % 100 == 0) { cv.notify_all(); } + std::this_thread::yield(); } done1 = true; @@ -201,6 +202,7 @@ namespace { for (auto const& val : vals2) { x2.insert(val); + std::this_thread::yield(); } done2 = true; @@ -214,6 +216,7 @@ namespace { } x1.swap(x2); ++num_swaps; + std::this_thread::yield(); } while (!done1 && !done2); }); From cc4cfc7ef23414f2dd39f0ed09f32526aea04089 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 2 May 2023 15:57:16 -0700 Subject: [PATCH 209/327] Fix bug in swap_test iteration logic that caused early termination of the swap loop --- test/cfoa/swap_tests.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/cfoa/swap_tests.cpp b/test/cfoa/swap_tests.cpp index 9fae85cd..b5c2bd08 100644 --- a/test/cfoa/swap_tests.cpp +++ b/test/cfoa/swap_tests.cpp @@ -191,7 +191,6 @@ namespace { if (idx % 100 == 0) { cv.notify_all(); } - std::this_thread::yield(); } done1 = true; @@ -202,7 +201,6 @@ namespace { for (auto const& val : vals2) { x2.insert(val); - std::this_thread::yield(); } done2 = true; @@ -216,8 +214,7 @@ namespace { } x1.swap(x2); ++num_swaps; - std::this_thread::yield(); - } while (!done1 && !done2); + } while (!done1 || !done2); }); t1.join(); From dfb4f2a28a098b4b25e1e0301c747eba47619d32 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Wed, 3 May 2023 17:53:13 +0200 Subject: [PATCH 210/327] added reference for boost::concurrent_flat_map --- doc/unordered/concurrent_flat_map.adoc | 1371 ++++++++++++++++++++++++ doc/unordered/ref.adoc | 1 + 2 files changed, 1372 insertions(+) create mode 100644 doc/unordered/concurrent_flat_map.adoc diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc new file mode 100644 index 00000000..7adb2323 --- /dev/null +++ b/doc/unordered/concurrent_flat_map.adoc @@ -0,0 +1,1371 @@ +[#concurrent_flat_map] +== Class template concurrent_flat_map + +:idprefix: concurrent_flat_map_ + +`boost::concurrent_flat_map` — A hash table that associates unique keys with another value and +allows for concurrent element insertion, erasure, lookup and access +without external synchronization mechanisms. + +Even though it acts as a container, `boost::concurrent_flat_map` +does not model the standard C++ https://en.cppreference.com/w/cpp/named_req/Container[Container^] concept. +In particular, iterators and associated operations (`begin`, `end`, etc.) are not provided. +Element access and modification are done through user-provided _visitation functions_ that are passed +to `concurrent_flat_map` operations where they are executed internally in a controlled fashion. +Such visitation-based API allows for low-contention concurrent usage scenarios. + +The internal data structure of `boost::concurrent_flat_map` is similar to that of +`boost::unordered_flat_map`. As a result of its using open-addressing techniques, +`value_type` must be move-constructible and pointer stability is not kept under rehashing. + +=== Synopsis + +[listing,subs="+macros,+quotes"] +----- +// #include + +namespace boost { + template, + class Pred = std::equal_to, + class Allocator = std::allocator>> + class concurrent_flat_map { + public: + // types + using key_type = Key; + using mapped_type = T; + using value_type = std::pair; + using init_type = std::pair< + typename std::remove_const::type, + typename std::remove_const::type + >; + using hasher = Hash; + using key_equal = Pred; + using allocator_type = Allocator; + using pointer = typename std::allocator_traits::pointer; + using const_pointer = typename std::allocator_traits::const_pointer; + using reference = value_type&; + using const_reference = const value_type&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + // construct/copy/destroy + xref:#concurrent_flat_map_default_constructor[concurrent_flat_map](); + explicit xref:#concurrent_flat_map_bucket_count_constructor[concurrent_flat_map](size_type n, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); + template + xref:#concurrent_flat_map_iterator_range_constructor[concurrent_flat_map](InputIterator f, InputIterator l, + size_type n = _implementation-defined_, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); + xref:#concurrent_flat_map_copy_constructor[concurrent_flat_map](const concurrent_flat_map& other); + xref:#concurrent_flat_map_move_constructor[concurrent_flat_map](concurrent_flat_map&& other); + template + xref:#concurrent_flat_map_iterator_range_constructor_with_allocator[concurrent_flat_map](InputIterator f, InputIterator l,const allocator_type& a); + explicit xref:#concurrent_flat_map_allocator_constructor[concurrent_flat_map](const Allocator& a); + xref:#concurrent_flat_map_copy_constructor_with_allocator[concurrent_flat_map](const concurrent_flat_map& other, const Allocator& a); + xref:#concurrent_flat_map_move_constructor_with_allocator[concurrent_flat_map](concurrent_flat_map&& other, const Allocator& a); + xref:#concurrent_flat_map_initializer_list_constructor[concurrent_flat_map](std::initializer_list il, + size_type n = _implementation-defined_ + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); + xref:#concurrent_flat_map_bucket_count_constructor_with_allocator[concurrent_flat_map](size_type n, const allocator_type& a); + xref:#concurrent_flat_map_bucket_count_constructor_with_hasher_and_allocator[concurrent_flat_map](size_type n, const hasher& hf, const allocator_type& a); + template + xref:#concurrent_flat_map_iterator_range_constructor_with_bucket_count_and_allocator[concurrent_flat_map](InputIterator f, InputIterator l, size_type n, + const allocator_type& a); + template + xref:#concurrent_flat_map_iterator_range_constructor_with_bucket_count_and_hasher[concurrent_flat_map](InputIterator f, InputIterator l, size_type n, const hasher& hf, + const allocator_type& a); + xref:#concurrent_flat_map_initializer_list_constructor_with_allocator[concurrent_flat_map](std::initializer_list il, const allocator_type& a); + xref:#concurrent_flat_map_initializer_list_constructor_with_bucket_count_and_allocator[concurrent_flat_map](std::initializer_list il, size_type n, + const allocator_type& a); + xref:#concurrent_flat_map_initializer_list_constructor_with_bucket_count_and_hasher_and_allocator[concurrent_flat_map](std::initializer_list il, size_type n, const hasher& hf, + const allocator_type& a); + xref:#concurrent_flat_map_destructor[~concurrent_flat_map](); + concurrent_flat_map& xref:#concurrent_flat_map_copy_assignment[operator++=++](const concurrent_flat_map& other); + concurrent_flat_map& xref:#concurrent_flat_map_move_assignment[operator++=++](concurrent_flat_map&& other) + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_move_assignment::value); + concurrent_flat_map& xref:#concurrent_flat_map_initializer_list_assignment[operator++=++](std::initializer_list); + allocator_type xref:#concurrent_flat_map_get_allocator[get_allocator]() const noexcept; + + + // visitation + template std::size_t xref:#concurrent_flat_map_cvisit[visit](const key_type& k, F f); + template std::size_t xref:#concurrent_flat_map_cvisit[visit](const key_type& k, F f) const; + template std::size_t xref:#concurrent_flat_map_cvisit[cvisit](const key_type& k, F f) const; + template std::size_t xref:#concurrent_flat_map_cvisit[visit](const K& k, F f); + template std::size_t xref:#concurrent_flat_map_cvisit[visit](const K& k, F f) const; + template std::size_t xref:#concurrent_flat_map_cvisit[cvisit](const K& k, F f) const; + + template std::size_t xref:#concurrent_flat_map_cvisit_all[visit_all](F f); + template std::size_t xref:#concurrent_flat_map_cvisit_all[visit_all](F f) const; + template std::size_t xref:#concurrent_flat_map_cvisit_all[cvisit_all](F f) const; + template + void std::size_t xref:#concurrent_flat_map_parallel_cvisit_all[visit_all](ExecutionPolicy&& policy, F f); + template + void xref:#concurrent_flat_map_parallel_cvisit_all[visit_all](ExecutionPolicy&& policy, F f) const; + template + void xref:#concurrent_flat_map_parallel_cvisit_all[cvisit_all](ExecutionPolicy&& policy, F f) const; + + // capacity + ++[[nodiscard]]++ bool xref:#concurrent_flat_map_empty[empty]() const noexcept; + size_type xref:#concurrent_flat_map_size[size]() const noexcept; + size_type xref:#concurrent_flat_map_max_size[max_size]() const noexcept; + + // modifiers + template bool xref:#concurrent_flat_map_emplace[emplace](Args&&... args); + bool xref:#concurrent_flat_map_copy_insert[insert](const value_type& obj); + bool xref:#concurrent_flat_map_copy_insert[insert](const init_type& obj); + bool xref:#concurrent_flat_map_move_insert[insert](value_type&& obj); + bool xref:#concurrent_flat_map_move_insert[insert](init_type&& obj); + template void xref:#concurrent_flat_map_insert_iterator_range[insert](InputIterator first, InputIterator last); + void xref:#concurrent_flat_map_insert_initializer_list[insert](std::initializer_list il); + + template bool xref:#concurrent_flat_map_emplace_or_cvisit[emplace_or_visit](Args&&... args, F&& f); + template bool xref:#concurrent_flat_map_emplace_or_cvisit[emplace_or_cvisit](Args&&... args, F&& f); + template bool xref:#concurrent_flat_map_copy_insert_or_cvisit[insert_or_visit](const value_type& obj, F f); + template bool xref:#concurrent_flat_map_copy_insert_or_cvisit[insert_or_cvisit](const value_type& obj, F f); + template bool xref:#concurrent_flat_map_copy_insert_or_cvisit[insert_or_visit](const init_type& obj, F f); + template bool xref:#concurrent_flat_map_copy_insert_or_cvisit[insert_or_cvisit](const init_type& obj, F f); + template bool xref:#concurrent_flat_map_move_insert_or_cvisit[insert_or_visit](value_type&& obj, F f); + template bool xref:#concurrent_flat_map_move_insert_or_cvisit[insert_or_cvisit](value_type&& obj, F f); + template bool xref:#concurrent_flat_map_move_insert_or_cvisit[insert_or_visit](init_type&& obj, F f); + template bool xref:#concurrent_flat_map_move_insert_or_cvisit[insert_or_cvisit](init_type&& obj, F f); + template + void xref:#concurrent_flat_map_insert_iterator_range_or_visit[insert_or_visit](InputIterator first, InputIterator last, F f); + template + void xref:#concurrent_flat_map_insert_iterator_range_or_visit[insert_or_cvisit](InputIterator first, InputIterator last, F f); + template void xref:#concurrent_flat_map_insert_initializer_list_or_visit[insert_or_visit](std::initializer_list il, F f); + template void xref:#concurrent_flat_map_insert_initializer_list_or_visit[insert_or_cvisit](std::initializer_list il, F f); + + template bool xref:#concurrent_flat_map_try_emplace[try_emplace](const key_type& k, Args&&... args); + template bool xref:#concurrent_flat_map_try_emplace[try_emplace](key_type&& k, Args&&... args); + template bool xref:#concurrent_flat_map_try_emplace[try_emplace](K&& k, Args&&... args); + + template + bool xref:#concurrent_flat_map_try_emplace_or_cvisit[try_emplace_or_visit](const key_type& k, Args&&... args, F&& f); + template + bool xref:#concurrent_flat_map_try_emplace_or_cvisit[try_emplace_or_cvisit](const key_type& k, Args&&... args, F&& f); + template + bool xref:#concurrent_flat_map_try_emplace_or_cvisit[try_emplace_or_visit](key_type&& k, Args&&... args, F&& f); + template + bool xref:#concurrent_flat_map_try_emplace_or_cvisit[try_emplace_or_cvisit](key_type&& k, Args&&... args, F&& f); + template + bool xref:#concurrent_flat_map_try_emplace_or_cvisit[try_emplace_or_visit](K&& k, Args&&... args, F&& f); + template + bool xref:#concurrent_flat_map_try_emplace_or_cvisit[try_emplace_or_cvisit](K&& k, Args&&... args, F&& f); + + template bool xref:#concurrent_flat_map_insert_or_assign[insert_or_assign](const key_type& k, M&& obj); + template bool xref:#concurrent_flat_map_insert_or_assign[insert_or_assign](key_type&& k, M&& obj); + template bool xref:#concurrent_flat_map_insert_or_assign[insert_or_assign](K&& k, M&& obj); + + size_type xref:#concurrent_flat_map_erase[erase](const key_type& k); + template size_type xref:#concurrent_flat_map_erase[erase](const K& k); + + template size_type xref:#concurrent_flat_map_erase_if_by_key[erase_if](const key_type& k, F f); + template size_type xref:#concurrent_flat_map_erase_if_by_key[erase_if](const K& k, F f); + template size_type xref:#concurrent_flat_map_erase_if[erase_if](F f); + template void xref:#concurrent_flat_map_parallel_erase_if[erase_if](ExecutionPolicy&& policy, F f); + + void xref:#concurrent_flat_map_swap[swap](concurrent_flat_map& other) + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_swap::value); + void xref:#concurrent_flat_map_clear[clear]() noexcept; + + template + void xref:#concurrent_flat_map_merge[merge](concurrent_flat_map& source); + template + void xref:#concurrent_flat_map_merge[merge](concurrent_flat_map&& source); + + // observers + hasher xref:#concurrent_flat_map_hash_function[hash_function]() const; + key_equal xref:#concurrent_flat_map_key_eq[key_eq]() const; + + // map operations + size_type xref:#concurrent_flat_map_count[count](const key_type& k) const; + template + size_type xref:#concurrent_flat_map_count[count](const K& k) const; + bool xref:#concurrent_flat_map_contains[contains](const key_type& k) const; + template + bool xref:#concurrent_flat_map_contains[contains](const K& k) const; + + // bucket interface + size_type xref:#concurrent_flat_map_bucket_count[bucket_count]() const noexcept; + + // hash policy + float xref:#concurrent_flat_map_load_factor[load_factor]() const noexcept; + float xref:#concurrent_flat_map_max_load_factor[max_load_factor]() const noexcept; + void xref:#concurrent_flat_map_set_max_load_factor[max_load_factor](float z); + size_type xref:#concurrent_flat_map_max_load[max_load]() const noexcept; + void xref:#concurrent_flat_map_rehash[rehash](size_type n); + void xref:#concurrent_flat_map_reserve[reserve](size_type n); + }; + + // Deduction Guides + template>, + class Pred = std::equal_to>, + class Allocator = std::allocator>> + concurrent_flat_map(InputIterator, InputIterator, typename xref:#concurrent_flat_map_deduction_guides[__see below__]::size_type = xref:#concurrent_flat_map_deduction_guides[__see below__], + Hash = Hash(), Pred = Pred(), Allocator = Allocator()) + -> concurrent_flat_map, xref:#concurrent_flat_map_iter_mapped_type[__iter-mapped-type__], Hash, + Pred, Allocator>; + + template, + class Pred = std::equal_to, + class Allocator = std::allocator>> + concurrent_flat_map(std::initializer_list>, + typename xref:#concurrent_flat_map_deduction_guides[__see below__]::size_type = xref:#concurrent_flat_map_deduction_guides[__see below__], Hash = Hash(), + Pred = Pred(), Allocator = Allocator()) + -> concurrent_flat_map; + + template + concurrent_flat_map(InputIterator, InputIterator, typename xref:#concurrent_flat_map_deduction_guides[__see below__]::size_type, Allocator) + -> concurrent_flat_map, xref:#concurrent_flat_map_iter_mapped_type[__iter-mapped-type__], + boost::hash>, + std::equal_to>, Allocator>; + + template + concurrent_flat_map(InputIterator, InputIterator, Allocator) + -> concurrent_flat_map, xref:#concurrent_flat_map_iter_mapped_type[__iter-mapped-type__], + boost::hash>, + std::equal_to>, Allocator>; + + template + concurrent_flat_map(InputIterator, InputIterator, typename xref:#concurrent_flat_map_deduction_guides[__see below__]::size_type, Hash, + Allocator) + -> concurrent_flat_map, xref:#concurrent_flat_map_iter_mapped_type[__iter-mapped-type__], Hash, + std::equal_to>, Allocator>; + + template + concurrent_flat_map(std::initializer_list>, typename xref:#concurrent_flat_map_deduction_guides[__see below__]::size_type, + Allocator) + -> concurrent_flat_map, std::equal_to, Allocator>; + + template + concurrent_flat_map(std::initializer_list>, Allocator) + -> concurrent_flat_map, std::equal_to, Allocator>; + + template + concurrent_flat_map(std::initializer_list>, typename xref:#concurrent_flat_map_deduction_guides[__see below__]::size_type, + Hash, Allocator) + -> concurrent_flat_map, Allocator>; + + // swap + template + void xref:#concurrent_flat_map_swap_2[swap](concurrent_flat_map& x, + concurrent_flat_map& y) + noexcept(noexcept(x.swap(y))); + + // Erasure + template + typename concurrent_flat_map::size_type + xref:#concurrent_flat_map_erase_if_2[erase_if](concurrent_flat_map& c, Predicate pred); +} +----- + +--- + +=== Description + +*Template Parameters* + +[cols="1,1"] +|=== + +|_Key_ +.2+|`Key` and `T` must be https://en.cppreference.com/w/cpp/named_req/MoveConstructible[MoveConstructible^]. +`std::pair` must be https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] +into the table from any `std::pair` object convertible to it, and it also must be +https://en.cppreference.com/w/cpp/named_req/Erasable[Erasable^] from the table. + +|_T_ + +|_Hash_ +|A unary function object type that acts a hash function for a `Key`. It takes a single argument of type `Key` and returns a value of type `std::size_t`. + +|_Pred_ +|A binary function object that induces an equivalence relation on values of type `Key`. It takes two arguments of type `Key` and returns a value of type `bool`. + +|_Allocator_ +|An allocator whose value type is the same as the table's value type. +`std::allocator_traits::pointer` and `std::allocator_traits::const_pointer` +must be convertible to/from `value_type*` and `const value_type*`, respectively. + +|=== + +The elements of the table are held into an internal _bucket array_. An element is inserted into a bucket determined by its +hash code, but if the bucket is already occupied (a _collision_), an available one in the vicinity of the +original position is used. + +The size of the bucket array can be automatically increased by a call to `insert`/`emplace`, or as a result of calling +`rehash`/`reserve`. The _load factor_ of the table (number of elements divided by number of buckets) is never +greater than `max_load_factor()`, except possibly for small sizes where the implementation may decide to +allow for higher loads. + +If `xref:hash_traits_hash_is_avalanching[hash_is_avalanching]::value` is `true`, the hash function +is used as-is; otherwise, a bit-mixing post-processing stage is added to increase the quality of hashing +at the expense of extra computational cost. + +--- + +=== Concurrency requirements and guarantees + +Concurrent member function invocations for the same instance of `Hash`, `Pred`, and `Allocator` +are required to not introduce data races (they must be thread-safe). + +With the exception of destruction, concurrent invocations of any operation on the same instance of a +`concurrent_flat_map` do not introduce data races — that is, they are thread-safe. + +If an operation *op* is explicitly designated as _blocking on_ `x`, where `x` is an instance of a `boost::concurrent_flat_map`, +prior blocking operations on `x` synchronize with *op*. So, blocking operations on the same +`concurrent_flat_map` execute sequentially in a multithreaded scenario. + +An operation is said to be _blocking on rehashing of_ ``__x__`` if it blocks on `x` +only when an internal rehashing is issued. + +Access or modification of an element of a `boost::concurrent_flat_map` passed by reference to a +user-provided visitation function do not introduce data races when the visitation function +is executed internally by the `boost::concurrent_flat_map`. Visitation +functions executed by a `concurrent_flat_map` `x` are not allowed to invoke any operation +on `x`; invoking operations on a different `boost::concurrent_flat_map` instance `y` is allowed only +if concurrent outstanding operations on `y` do not access `x` directly or indirectly. + +--- + +=== Constructors + +==== Default Constructor +```c++ +concurrent_flat_map(); +``` + +Constructs an empty table using `hasher()` as the hash function, +`key_equal()` as the key equality predicate and `allocator_type()` as the allocator. + +[horizontal] +Postconditions:;; `size() == 0` +Requires:;; If the defaults are used, `hasher`, `key_equal` and `allocator_type` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Bucket Count Constructor +```c++ +explicit concurrent_flat_map(size_type n, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); +``` + +Constructs an empty table with at least `n` buckets, using `hf` as the hash +function, `eql` as the key equality predicate, and `a` as the allocator. + +[horizontal] +Postconditions:;; `size() == 0` +Requires:;; If the defaults are used, `hasher`, `key_equal` and `allocator_type` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Iterator Range Constructor +[source,c++,subs="+quotes"] +---- +template + concurrent_flat_map(InputIterator f, InputIterator l, + size_type n = _implementation-defined_, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); +---- + +Constructs an empty table with at least `n` buckets, using `hf` as the hash function, `eql` as the key equality predicate and `a` as the allocator, and inserts the elements from `[f, l)` into it. + +[horizontal] +Requires:;; If the defaults are used, `hasher`, `key_equal` and `allocator_type` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Copy Constructor +```c++ +concurrent_flat_map(concurrent_flat_map const& other); +``` + +The copy constructor. Copies the contained elements, hash function, predicate and allocator. + +If `Allocator::select_on_container_copy_construction` exists and has the right signature, the allocator will be constructed from its result. + +[horizontal] +Requires:;; `value_type` is copy constructible +Concurrency:;; Blocking on `other`. + +--- + +==== Move Constructor +```c++ +concurrent_flat_map(concurrent_flat_map&& other); +``` + +The move constructor. The internal bucket array of `other` is transferred directly to the new table. +The hash function, predicate and allocator are moved-constructed from `other`. + +[horizontal] +Concurrency:;; Blocking on `other`. + +--- + +==== Iterator Range Constructor with Allocator +```c++ +template + concurrent_flat_map(InputIterator f, InputIterator l, const allocator_type& a); +``` + +Constructs an empty table using `a` as the allocator, with the default hash function and key equality predicate and inserts the elements from `[f, l)` into it. + +[horizontal] +Requires:;; `hasher`, `key_equal` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Allocator Constructor +```c++ +explicit concurrent_flat_map(Allocator const& a); +``` + +Constructs an empty table, using allocator `a`. + +--- + +==== Copy Constructor with Allocator +```c++ +concurrent_flat_map(concurrent_flat_map const& other, Allocator const& a); +``` + +Constructs a table, copying ``other``'s contained elements, hash function, and predicate, but using allocator `a`. + +[horizontal] +Concurrency:;; Blocking on `other`. + +--- + +==== Move Constructor with Allocator +```c++ +concurrent_flat_map(concurrent_flat_map&& other, Allocator const& a); +``` + +If `a == other.get_allocator()`, the elements of `other` are transferred directly to the new table; +otherwise, elements are moved-constructed from those of `other`. The hash function and predicate are moved-constructed +from `other`, and the allocator is copy-constructed from `a`. + +[horizontal] +Concurrency:;; Blocking on `other`. + +--- + +==== Initializer List Constructor +[source,c++,subs="+quotes"] +---- +concurrent_flat_map(std::initializer_list il, + size_type n = _implementation-defined_ + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); +---- + +Constructs an empty table with at least `n` buckets, using `hf` as the hash function, `eql` as the key equality predicate and `a`, and inserts the elements from `il` into it. + +[horizontal] +Requires:;; If the defaults are used, `hasher`, `key_equal` and `allocator_type` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Bucket Count Constructor with Allocator +```c++ +concurrent_flat_map(size_type n, allocator_type const& a); +``` + +Constructs an empty table with at least `n` buckets, using `hf` as the hash function, the default hash function and key equality predicate and `a` as the allocator. + +[horizontal] +Postconditions:;; `size() == 0` +Requires:;; `hasher` and `key_equal` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Bucket Count Constructor with Hasher and Allocator +```c++ +concurrent_flat_map(size_type n, hasher const& hf, allocator_type const& a); +``` + +Constructs an empty table with at least `n` buckets, using `hf` as the hash function, the default key equality predicate and `a` as the allocator. + +[horizontal] +Postconditions:;; `size() == 0` +Requires:;; `key_equal` needs to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Iterator Range Constructor with Bucket Count and Allocator +[source,c++,subs="+quotes"] +---- +template + concurrent_flat_map(InputIterator f, InputIterator l, size_type n, const allocator_type& a); +---- + +Constructs an empty table with at least `n` buckets, using `a` as the allocator and default hash function and key equality predicate, and inserts the elements from `[f, l)` into it. + +[horizontal] +Requires:;; `hasher`, `key_equal` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Iterator Range Constructor with Bucket Count and Hasher +[source,c++,subs="+quotes"] +---- + template + concurrent_flat_map(InputIterator f, InputIterator l, size_type n, const hasher& hf, + const allocator_type& a); +---- + +Constructs an empty table with at least `n` buckets, using `hf` as the hash function, `a` as the allocator, with the default key equality predicate, and inserts the elements from `[f, l)` into it. + +[horizontal] +Requires:;; `key_equal` needs to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== initializer_list Constructor with Allocator + +```c++ +concurrent_flat_map(std::initializer_list il, const allocator_type& a); +``` + +Constructs an empty table using `a` and default hash function and key equality predicate, and inserts the elements from `il` into it. + +[horizontal] +Requires:;; `hasher` and `key_equal` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== initializer_list Constructor with Bucket Count and Allocator + +```c++ +concurrent_flat_map(std::initializer_list il, size_type n, const allocator_type& a); +``` + +Constructs an empty table with at least `n` buckets, using `a` and default hash function and key equality predicate, and inserts the elements from `il` into it. + +[horizontal] +Requires:;; `hasher` and `key_equal` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== initializer_list Constructor with Bucket Count and Hasher and Allocator + +```c++ +concurrent_flat_map(std::initializer_list il, size_type n, const hasher& hf, + const allocator_type& a); +``` + +Constructs an empty table with at least `n` buckets, using `hf` as the hash function, `a` as the allocator and default key equality predicate,and inserts the elements from `il` into it. + +[horizontal] +Requires:;; `key_equal` needs to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +=== Destructor + +```c++ +~concurrent_flat_map(); +``` + +[horizontal] +Note:;; The destructor is applied to every element, and all memory is deallocated + +--- + +=== Assignment + +==== Copy Assignment + +```c++ +concurrent_flat_map& operator=(concurrent_flat_map const& other); +``` + +The assignment operator. Destroys previously existing elements, copy-assigns the hash function and predicate from `other`, +copy-assigns the allocator from `other` if `Alloc::propagate_on_container_copy_assignment` exists and `Alloc::propagate_on_container_copy_assignment::value` is `true`, +and finally inserts copies of the elements of `other`. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] +Concurrency:;; Blocking on `*this` and `other`. + +--- + +==== Move Assignment +```c++ +concurrent_flat_map& operator=(concurrent_flat_map&& other) + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_move_assignment::value); +``` +The move assignment operator. Destroys previously existing elements, swaps the hash function and predicate from `other`, +and move-assigns the allocator from `other` if `Alloc::propagate_on_container_move_assignment` exists and `Alloc::propagate_on_container_move_assignment::value` is `true`. +If at this point the allocator is equal to `other.get_allocator()`, the internal bucket array of `other` is transferred directly to `*this`; +otherwise, inserts move-constructed copies of the elements of `other`. + +[horizontal] +Concurrency:;; Blocking on `*this` and `other`. + +--- + +==== Initializer List Assignment +```c++ +concurrent_flat_map& operator=(std::initializer_list il); +``` + +Assign from values in initializer list. All previously existing elements are destroyed. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] +Concurrency:;; Blocking on `*this`. + +--- + +=== Visitation + +==== [c]visit + +```c++ +template std::size_t visit(const key_type& k, F f); +template std::size_t visit(const key_type& k, F f) const; +template std::size_t cvisit(const key_type& k, F f) const; +template std::size_t visit(const K& k, F f); +template std::size_t visit(const K& k, F f) const; +template std::size_t cvisit(const K& k, F f) const; +``` + +If an element `x` exists with key equivalent to `k`, invokes `f` with a reference to `x`. +Such reference is const iff `*this` is const. + +[horizontal] +Returns:;; The number of elements visited (0 or 1). +Notes:;; The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + +--- + +==== [c]visit_all + +```c++ +template std::size_t visit_all(F f); +template std::size_t visit_all(F f) const; +template std::size_t cvisit_all(F f) const; +``` + +Successively invokes `f` with references to each of the elements in the table. +Such references are const iff `*this` is const. + +[horizontal] +Returns:;; The number of elements visited. + +--- + +==== Parallel [c]visit_all + +```c++ +template void visit_all(ExecutionPolicy&& policy, F f); +template void visit_all(ExecutionPolicy&& policy, F f) const; +template void cvisit_all(ExecutionPolicy&& policy, F f) const; +``` + +Invokes `f` with references to each of the elements in the table. Such references are const iff `*this` is const. +Execution is parallelized according to the semantics of the execution policy specified. + +[horizontal] +Throws:;; Depending on the exception handling mechanism of the execution policy used, may call `std::terminate` if an exception is thrown within `f`. +Notes:;; Only available in compilers supporting C++17 parallel algorithms. + ++ +These overloads only participate in overload resolution if `std::is_execution_policy_v>` is `true`. + ++ +Unsequenced execution policies are not allowed. + +--- + +=== Size and Capacity + +==== empty + +```c++ +[[nodiscard]] bool empty() const noexcept; +``` + +[horizontal] +Returns:;; `size() == 0` + +--- + +==== size + +```c++ +size_type size() const noexcept; +``` + +[horizontal] +Returns:;; The number of elements in the table. + +[horizontal] +Notes:;; In the presence of concurrent insertion operations, the value returned may not accurately reflect +the true size of the table right after execution. + +--- + +==== max_size + +```c++ +size_type max_size() const noexcept; +``` + +[horizontal] +Returns:;; `size()` of the largest possible table. + +--- + +=== Modifiers + +==== emplace +```c++ +template bool emplace(Args&&... args); +``` + +Inserts an object, constructed with the arguments `args`, in the table if and only if there is no element in the table with an equivalent key. + +[horizontal] +Requires:;; `value_type` is constructible from `args`. +Returns:;; `true` if an insert took place. +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + +--- + +==== Copy Insert +```c++ +bool insert(const value_type& obj); +bool insert(const init_type& obj); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^]. +Returns:;; `true` if an insert took place. + +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + ++ +A call of the form `insert(x)`, where `x` is equally convertible to both `const value_type&` and `const init_type&`, is not ambiguous and selects the `init_type` overload. + +--- + +==== Move Insert +```c++ +bool insert(value_type&& obj); +bool insert(init_type&& obj); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/MoveInsertable[MoveInsertable^]. +Returns:;; `true` if an insert took place. +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + ++ +A call of the form `insert(x)`, where `x` is equally convertible to both `value_type&&` and `init_type&&`, is not ambiguous and selects the `init_type` overload. + +--- + +==== Insert Iterator Range +```c++ +template void insert(InputIterator first, InputIterator last); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- + while(first != last) this->xref:#concurrent_flat_map_emplace[emplace](*first++); +----- + +--- + +==== Insert Initializer List +```c++ +void insert(std::initializer_list il); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- + this->xref:#concurrent_flat_map_insert_iterator_range[insert](il.begin(), il.end()); +----- + +--- + +==== emplace_or_[c]visit +```c++ +template bool emplace_or_visit(Args&&... args, F&& f); +template bool emplace_or_cvisit(Args&&... args, F&& f); +``` + +Inserts an object, constructed with the arguments `args`, in the table if there is no element in the table with an equivalent key. +Otherwise, invokes `f` with a reference to the equivalent element; such reference is const iff `emplace_or_cvisit` is used. + +[horizontal] +Requires:;; `value_type` is constructible from `args`. +Returns:;; `true` if an insert took place. +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + ++ +The interface is exposition only, as C++ does not allow to declare a parameter `f` after a variadic parameter pack. + +--- + +==== Copy insert_or_[c]visit +```c++ +template bool insert_or_visit(const value_type& obj, F f); +template bool insert_or_cvisit(const value_type& obj, F f); +template bool insert_or_visit(const init_type& obj, F f); +template bool insert_or_cvisit(const init_type& obj, F f); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key. +Otherwise, invokes `f` with a reference to the equivalent element; such reference is const iff a `*_cvisit` overload is used. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^]. +Returns:;; `true` if an insert took place. + +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + ++ +In a call of the form `insert_or_[c]visit(obj, f)`, the overloads accepting a `const value_type&` argument participate in overload resolution +only if `std::remove_cv::type>::type` is `value_type`. + +--- + +==== Move insert_or_[c]visit +```c++ +template bool insert_or_visit(value_type&& obj, F f); +template bool insert_or_cvisit(value_type&& obj, F f); +template bool insert_or_visit(init_type&& obj, F f); +template bool insert_or_cvisit(init_type&& obj, F f); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key. +Otherwise, invokes `f` with a reference to the equivalent element; such reference is const iff a `*_cvisit` overload is used. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/MoveInsertable[MoveInsertable^]. +Returns:;; `true` if an insert took place. + +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + ++ +In a call of the form `insert_or_[c]visit(obj, f)`, the overloads accepting a `value_type&&` argument participate in overload resolution +only if `std::remove_reference::type` is `value_type`. + +--- + +==== Insert Iterator Range or Visit +```c++ +template + void insert_or_visit(InputIterator first, InputIterator last, F f); +template + void insert_or_cvisit(InputIterator first, InputIterator last, F f); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- + while(first != last) this->xref:#concurrent_flat_map_emplace_or_cvisit[emplace_or_(c)visit](*first++, f); +----- + +--- + +==== Insert Initializer List or Visit +```c++ +template void insert_or_visit(std::initializer_list il, F f); +template void insert_or_cvisit(std::initializer_list il, F f); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- + this->xref:#concurrent_flat_map_insert_iterator_range_or_visit[insert_or(c)visit](il.begin(), il.end(), f); +----- + +--- + +==== try_emplace +```c++ +template bool try_emplace(const key_type& k, Args&&... args); +template bool try_emplace(key_type&& k, Args&&... args); +template bool try_emplace(K&& k, Args&&... args); +``` + +Inserts an element constructed from `k` and `args` into the table if there is no existing element with key `k` contained within it. + +[horizontal] +Returns:;; `true` if an insert took place. + +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; This function is similiar to xref:#concurrent_flat_map_emplace[emplace], with the difference that no `value_type` is constructed +if there is an element with an equivalent key; otherwise, the construction is of the form: + ++ +-- +```c++ +// first two overloads +value_type(std::piecewise_construct, + std::forward_as_tuple(boost::forward(k)), + std::forward_as_tuple(boost::forward(args)...)) + +// third overload +value_type(std::piecewise_construct, + std::forward_as_tuple(boost::forward(k)), + std::forward_as_tuple(boost::forward(args)...)) +``` + +unlike xref:#concurrent_flat_map_emplace[emplace], which simply forwards all arguments to ``value_type``'s constructor. + +Invalidates pointers and references to elements if a rehashing is issued. + +The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + +-- + +--- + +==== try_emplace_or_[c]visit +```c++ +template + bool try_emplace_or_visit(const key_type& k, Args&&... args, F&& f); +template + bool try_emplace_or_cvisit(const key_type& k, Args&&... args, F&& f); +template + bool try_emplace_or_visit(key_type&& k, Args&&... args, F&& f); +template + bool try_emplace_or_cvisit(key_type&& k, Args&&... args, F&& f); +template + bool try_emplace_or_visit(K&& k, Args&&... args, F&& f); +template + bool try_emplace_or_cvisit(K&& k, Args&&... args, F&& f); +``` + +Inserts an element constructed from `k` and `args` into the table if there is no existing element with key `k` contained within it. +Otherwise, invokes `f` with a reference to the equivalent element; such reference is const iff a `*_cvisit` overload is used. + +[horizontal] +Returns:;; `true` if an insert took place. + +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; No `value_type` is constructed +if there is an element with an equivalent key; otherwise, the construction is of the form: + ++ +-- +```c++ +// first four overloads +value_type(std::piecewise_construct, + std::forward_as_tuple(boost::forward(k)), + std::forward_as_tuple(boost::forward(args)...)) + +// last two overloads +value_type(std::piecewise_construct, + std::forward_as_tuple(boost::forward(k)), + std::forward_as_tuple(boost::forward(args)...)) +``` + +Invalidates pointers and references to elements if a rehashing is issued. + +The interface is exposition only, as C++ does not allow to declare a parameter `f` after a variadic parameter pack. + +The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + +-- + +--- + +==== insert_or_assign +```c++ +template bool insert_or_assign(const key_type& k, M&& obj); +template bool insert_or_assign(key_type&& k, M&& obj); +template bool insert_or_assign(K&& k, M&& obj); +``` + +Inserts a new element into the table or updates an existing one by assigning to the contained value. + +If there is an element with key `k`, then it is updated by assigning `boost::forward(obj)`. + +If there is no such element, it is added to the table as: +```c++ +// first two overloads +value_type(std::piecewise_construct, + std::forward_as_tuple(boost::forward(k)), + std::forward_as_tuple(boost::forward(obj))) + +// third overload +value_type(std::piecewise_construct, + std::forward_as_tuple(boost::forward(k)), + std::forward_as_tuple(boost::forward(obj))) +``` + +[horizontal] +Returns:;; `true` if an insert took place. +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + ++ +The `template` only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + +--- + +==== erase +```c++ +size_type erase(const key_type& k); +template size_type erase(const K& k); +``` + +Erases the element with key equivalent to `k` if it exists. + +[horizontal] +Returns:;; The number of elements erased (0 or 1). +Throws:;; Only throws an exception if it is thrown by `hasher` or `key_equal`. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + +--- + +==== erase_if by Key +```c++ +template size_type erase_if(const key_type& k, F f); +template size_type erase_if(const K& k, F f); +``` + +Erases the element `x` with key equivalent to `k` if it exists and `f(x)` is `true`. + +[horizontal] +Returns:;; The number of elements erased (0 or 1). +Throws:;; Only throws an exception if it is thrown by `hasher`, `key_equal` or `f`. +Notes:;; The `template` overload only participates in overload resolution if `std::is_execution_policy_v>` is `false`. + ++ +The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + +--- + +==== erase_if +```c++ +template size_type erase_if(F f); +``` + +Successively invokes `f` with references to each of the elements in the table, and erases those for which `f` returns `true`. + +[horizontal] +Returns:;; The number of elements erased. +Throws:;; Only throws an exception if it is thrown by `f`. + +--- + +==== Parallel erase_if +```c++ +template void erase_if(ExecutionPolicy&& policy, F f); +``` + +Invokes `f` with references to each of the elements in the table, and erases those for which `f` returns `true`. +Execution is parallelized according to the semantics of the execution policy specified. + +[horizontal] +Throws:;; Depending on the exception handling mechanism of the execution policy used, may call `std::terminate` if an exception is thrown within `f`. +Notes:;; Only available in compilers supporting C++17 parallel algorithms. + ++ +This overload only participates in overload resolution if `std::is_execution_policy_v>` is `true`. + ++ +Unsequenced execution policies are not allowed. + +--- + +==== swap +```c++ +void swap(concurrent_flat_map& other) + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_swap::value); +``` + +Swaps the contents of the table with the parameter. + +If `Allocator::propagate_on_container_swap` is declared and `Allocator::propagate_on_container_swap::value` is `true` then the tables' allocators are swapped. Otherwise, swapping with unequal allocators results in undefined behavior. + +[horizontal] +Throws:;; Nothing unless `key_equal` or `hasher` throw on swapping. +Concurrency:;; Blocking on `*this` and `other`. + +--- + +==== clear +```c++ +void clear() noexcept; +``` + +Erases all elements in the table. + +[horizontal] +Postconditions:;; `size() == 0`, `max_load() >= max_load_factor() * bucket_count()` +Concurrency:;; Blocking on `*this`. + +--- + +==== merge +```c++ +template + void merge(concurrent_flat_map& source); +template + void merge(concurrent_flat_map&& source); +``` + +Move-inserts all the elements from `source` whose key is not already present in `*this`, and erases them from `source`. + +[horizontal] +Concurrency:;; Blocking on `*this` and `source`. + +--- + +=== Observers + +==== get_allocator +``` +allocator_type get_allocator() const noexcept; +``` + +[horizontal] +Returns:;; The table's allocator. + +--- + +==== hash_function +``` +hasher hash_function() const; +``` + +[horizontal] +Returns:;; The table's hash function. + +--- + +==== key_eq +``` +key_equal key_eq() const; +``` + +[horizontal] +Returns:;; The table's key equality predicate. + +--- + +=== Map operations + +==== count +```c++ +size_type count(const key_type& k) const; +template + size_type count(const K& k) const; +``` + +[horizontal] +Returns:;; The number of elements with key equivalent to `k` (0 or 1). +Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + ++ +In the presence of concurrent insertion operations, the value returned may not accurately reflect +the true state of the table right after execution. + +--- + +==== contains +```c++ +bool contains(const key_type& k) const; +template + bool contains(const K& k) const; +``` + +[horizontal] +Returns:;; A boolean indicating whether or not there is an element with key equal to `k` in the table. +Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + ++ +In the presence of concurrent insertion operations, the value returned may not accurately reflect +the true state of the table right after execution. + +--- +=== Bucket Interface + +==== bucket_count +```c++ +size_type bucket_count() const noexcept; +``` + +[horizontal] +Returns:;; The size of the bucket array. + +--- + +=== Hash Policy + +==== load_factor +```c++ +float load_factor() const noexcept; +``` + +[horizontal] +Returns:;; `static_cast(size())/static_cast(bucket_count())`, or `0` if `bucket_count() == 0`. + +--- + +==== max_load_factor + +```c++ +float max_load_factor() const noexcept; +``` + +[horizontal] +Returns:;; Returns the table's maximum load factor. + +--- + +==== Set max_load_factor +```c++ +void max_load_factor(float z); +``` + +[horizontal] +Effects:;; Does nothing, as the user is not allowed to change this parameter. Kept for compatibility with `boost::unordered_map`. + +--- + + +==== max_load + +```c++ +size_type max_load() const noexcept; +``` + +[horizontal] +Returns:;; The maximum number of elements the table can hold without rehashing, assuming that no further elements will be erased. +Note:;; After construction, rehash or clearance, the table's maximum load is at least `max_load_factor() * bucket_count()`. +This number may decrease on erasure under high-load conditions. + ++ +In the presence of concurrent insertion operations, the value returned may not accurately reflect +the true state of the table right after execution. + +--- + +==== rehash +```c++ +void rehash(size_type n); +``` + +Changes if necessary the size of the bucket array so that there are at least `n` buckets, and so that the load factor is less than or equal to the maximum load factor. When applicable, this will either grow or shrink the `bucket_count()` associated with the table. + +When `size() == 0`, `rehash(0)` will deallocate the underlying buckets array. + +Invalidates pointers and references to elements, and changes the order of elements. + +[horizontal] +Throws:;; The function has no effect if an exception is thrown, unless it is thrown by the table's hash function or comparison function. +Concurrency:;; Blocking on `*this`. +--- + +==== reserve +```c++ +void reserve(size_type n); +``` + +Equivalent to `a.rehash(ceil(n / a.max_load_factor()))`. + +Similar to `rehash`, this function can be used to grow or shrink the number of buckets in the table. + +Invalidates pointers and references to elements, and changes the order of elements. + +[horizontal] +Throws:;; The function has no effect if an exception is thrown, unless it is thrown by the table's hash function or comparison function. +Concurrency:;; Blocking on `*this`. + +--- + +=== Deduction Guides +A deduction guide will not participate in overload resolution if any of the following are true: + + - It has an `InputIterator` template parameter and a type that does not qualify as an input iterator is deduced for that parameter. + - It has an `Allocator` template parameter and a type that does not qualify as an allocator is deduced for that parameter. + - It has a `Hash` template parameter and an integral type or a type that qualifies as an allocator is deduced for that parameter. + - It has a `Pred` template parameter and a type that qualifies as an allocator is deduced for that parameter. + +A `size_­type` parameter type in a deduction guide refers to the `size_­type` member type of the +table type deduced by the deduction guide. Its default value coincides with the default value +of the constructor selected. + +==== __iter-value-type__ +[listings,subs="+macros,+quotes"] +----- +template + using __iter-value-type__ = + typename std::iterator_traits::value_type; // exposition only +----- + +==== __iter-key-type__ +[listings,subs="+macros,+quotes"] +----- +template + using __iter-key-type__ = std::remove_const_t< + std::tuple_element_t<0, xref:#concurrent_map_iter_value_type[__iter-value-type__]>>; // exposition only +----- + +==== __iter-mapped-type__ +[listings,subs="+macros,+quotes"] +----- +template + using __iter-mapped-type__ = + std::tuple_element_t<1, xref:#concurrent_map_iter_value_type[__iter-value-type__]>; // exposition only +----- + +==== __iter-to-alloc-type__ +[listings,subs="+macros,+quotes"] +----- +template + using __iter-to-alloc-type__ = std::pair< + std::add_const_t>>, + std::tuple_element_t<1, xref:#concurrent_map_iter_value_type[__iter-value-type__]>>; // exposition only +----- + +=== Swap +```c++ +template + void swap(concurrent_flat_map& x, + concurrent_flat_map& y) + noexcept(noexcept(x.swap(y))); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- +x.xref:#concurrent_flat_map_swap[swap](y); +----- + +--- + +=== erase_if +```c++ +template + typename concurrent_flat_map::size_type + erase_if(concurrent_flat_map& c, Predicate pred); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- +c.xref:#concurrent_flat_map_erase_if[erase_if](pred); +----- + +--- diff --git a/doc/unordered/ref.adoc b/doc/unordered/ref.adoc index 62a84b0f..6a9673da 100644 --- a/doc/unordered/ref.adoc +++ b/doc/unordered/ref.adoc @@ -10,3 +10,4 @@ include::unordered_flat_map.adoc[] include::unordered_flat_set.adoc[] include::unordered_node_map.adoc[] include::unordered_node_set.adoc[] +include::concurrent_flat_map.adoc[] From 3fe0807ae962064c33e932b20a59196d61b05298 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 3 May 2023 10:28:19 -0700 Subject: [PATCH 211/327] Add test that intermixes insertion and visitation Attempt to test the happens-before and synchronizes-with relationship, looking for potential bugs on weakly-ordered models --- test/cfoa/visit_tests.cpp | 59 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/test/cfoa/visit_tests.cpp b/test/cfoa/visit_tests.cpp index d49be2df..bc4fd13b 100644 --- a/test/cfoa/visit_tests.cpp +++ b/test/cfoa/visit_tests.cpp @@ -423,6 +423,58 @@ namespace { BOOST_TEST_EQ(raii::destructor, 0u); } + template + void insert_and_visit(X*, G gen, test::random_generator rg) + { + // here we attempt to ensure happens-before and synchronizes-with + // the visitation thread essentially chases the insertion one + // we double-check unreloated loads/stores to ensure that a store is visible + // in the visitation thread + + BOOST_TEST(rg == test::sequential); + + auto const values = make_random_values(1024 * 16, [&] { return gen(rg); }); + + { + raii::reset_counts(); + + X x; + + std::thread t1, t2; + boost::latch l(2); + std::vector strs(values.size()); + + t1 = std::thread([&l, &values, &x, &strs] { + l.arrive_and_wait(); + for (std::size_t idx = 0; idx < values.size(); ++idx) { + strs[idx] = "rawr"; + auto const& val = values[idx]; + x.insert(val); + } + }); + + t2 = std::thread([&l, &values, &x, &strs] { + l.arrive_and_wait(); + + for (std::size_t idx = 0; idx < values.size(); ++idx) { + std::atomic_bool b{false}; + while (!b) { + x.cvisit(values[idx].first, + [&b, &strs, idx, &values](typename X::value_type const& v) { + BOOST_TEST_EQ(v.second, values[idx].second); + BOOST_TEST_EQ(strs[idx], "rawr"); + b = true; + }); + } + } + }); + + t1.join(); + t2.join(); + } + check_raii_counts(); + } + boost::unordered::concurrent_flat_map* map; boost::unordered::concurrent_flat_map* transp_map; @@ -456,6 +508,13 @@ UNORDERED_TEST( ((default_generator)(sequential)(limited_range)) ) +UNORDERED_TEST( + insert_and_visit, + ((map)) + ((value_type_generator)) + ((sequential)) +) + // clang-format on RUN_TESTS() From 3c0fb0fa1bb8a16225c2cf9753c42484950d0527 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 3 May 2023 11:22:07 -0700 Subject: [PATCH 212/327] Attempt to fix flaky CI --- test/cfoa/swap_tests.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/cfoa/swap_tests.cpp b/test/cfoa/swap_tests.cpp index b5c2bd08..22cbfded 100644 --- a/test/cfoa/swap_tests.cpp +++ b/test/cfoa/swap_tests.cpp @@ -191,6 +191,7 @@ namespace { if (idx % 100 == 0) { cv.notify_all(); } + std::this_thread::yield(); } done1 = true; @@ -201,6 +202,7 @@ namespace { for (auto const& val : vals2) { x2.insert(val); + std::this_thread::yield(); } done2 = true; @@ -214,7 +216,11 @@ namespace { } x1.swap(x2); ++num_swaps; + std::this_thread::yield(); } while (!done1 || !done2); + + BOOST_TEST(done1); + BOOST_TEST(done2); }); t1.join(); From 4fb7751b55897a42db27856257adc289cb012cbe Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 3 May 2023 11:37:14 -0700 Subject: [PATCH 213/327] Add missing #include --- include/boost/unordered/detail/foa/concurrent_table.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index b72b4910..c78c6d86 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include From 23e720a968fbb10a8bcbb294ddd5a61009c2128f Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 3 May 2023 15:32:06 -0700 Subject: [PATCH 214/327] Split up Drone jobs even further due to extended runtimes --- .drone.jsonnet | 53 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index 4e568245..edd28686 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -158,9 +158,16 @@ local windows_pipeline(name, image, environment, arch = "amd64") = ), linux_pipeline( - "Linux 18.04 GCC 8 32/64", + "Linux 18.04 GCC 8 32/64 (03,11)", "cppalliance/droneubuntu1804:1", - { TOOLSET: 'gcc', COMPILER: 'g++-8', CXXSTD: '03,11,14,17', ADDRMD: '32,64' }, + { TOOLSET: 'gcc', COMPILER: 'g++-8', CXXSTD: '03,11', ADDRMD: '32,64' }, + "g++-8-multilib", + ), + + linux_pipeline( + "Linux 18.04 GCC 8 32/64 (14,17)", + "cppalliance/droneubuntu1804:1", + { TOOLSET: 'gcc', COMPILER: 'g++-8', CXXSTD: '14,17', ADDRMD: '32,64' }, "g++-8-multilib", ), @@ -238,9 +245,16 @@ local windows_pipeline(name, image, environment, arch = "amd64") = ), linux_pipeline( - "Linux 22.04 GCC 12 32 ASAN (17,20)", + "Linux 22.04 GCC 12 32 ASAN (17)", "cppalliance/droneubuntu2204:1", - { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17,20', ADDRMD: '32' } + asan, + { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17', ADDRMD: '32' } + asan, + "g++-12-multilib", + ), + + linux_pipeline( + "Linux 22.04 GCC 12 32 ASAN (20)", + "cppalliance/droneubuntu2204:1", + { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '20', ADDRMD: '32' } + asan, "g++-12-multilib", ), @@ -259,9 +273,16 @@ local windows_pipeline(name, image, environment, arch = "amd64") = ), linux_pipeline( - "Linux 22.04 GCC 12 64 ASAN (17,20)", + "Linux 22.04 GCC 12 64 ASAN (17)", "cppalliance/droneubuntu2204:1", - { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17,20', ADDRMD: '64' } + asan, + { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17', ADDRMD: '64' } + asan, + "g++-12-multilib", + ), + + linux_pipeline( + "Linux 22.04 GCC 12 64 ASAN (20)", + "cppalliance/droneubuntu2204:1", + { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '20', ADDRMD: '64' } + asan, "g++-12-multilib", ), @@ -385,9 +406,16 @@ local windows_pipeline(name, image, environment, arch = "amd64") = ), linux_pipeline( - "Linux 22.04 Clang 14 UBSAN", + "Linux 22.04 Clang 14 UBSAN (03,11,14)", "cppalliance/droneubuntu2204:1", - { TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '03,11,14,17,20' } + ubsan, + { TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '03,11,14' } + ubsan, + "clang-14", + ), + + linux_pipeline( + "Linux 22.04 Clang 14 UBSAN (17,20)", + "cppalliance/droneubuntu2204:1", + { TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '17,20' } + ubsan, "clang-14", ), @@ -426,8 +454,13 @@ local windows_pipeline(name, image, environment, arch = "amd64") = ), macos_pipeline( - "MacOS 10.15 Xcode 12.2 UBSAN (14,1z)", - { TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '14,1z' } + ubsan, + "MacOS 10.15 Xcode 12.2 UBSAN (14)", + { TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '14' } + ubsan, + ), + + macos_pipeline( + "MacOS 10.15 Xcode 12.2 UBSAN (1z)", + { TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '1z' } + ubsan, ), macos_pipeline( From 26924c73b974b6b41f412d5ea5a2ee4f811ec687 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 4 May 2023 18:09:28 +0200 Subject: [PATCH 215/327] fixed space reservation in concurrent_table::operator=(std::initializer_list) --- .../unordered/detail/foa/concurrent_table.hpp | 4 +-- include/boost/unordered/detail/foa/core.hpp | 36 +++++++++---------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index c78c6d86..a19d28b9 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -414,9 +414,7 @@ public: concurrent_table& operator=(std::initializer_list il) { auto lck=exclusive_access(); super::clear(); - if (super::capacity()unprotected_emplace(v); } diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 0daa0206..7e47568f 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1642,6 +1642,24 @@ public: return it; } + void noshrink_reserve(std::size_t n) + { + /* used only on assignment after element clearance */ + BOOST_ASSERT(empty()); + + if(n){ + n=std::size_t(std::ceil(float(n)/mlf)); /* elements -> slots */ + n=capacity_for(n); /* exact resulting capacity */ + + if(n>capacity()){ + auto new_arrays_=new_arrays(n); + delete_arrays(arrays); + arrays=new_arrays_; + ml=initial_max_load(); + } + } + } + template void for_all_elements(F f)const { @@ -1947,24 +1965,6 @@ private: ml=initial_max_load(); } - void noshrink_reserve(std::size_t n) - { - /* used only on assignment after element clearance */ - BOOST_ASSERT(empty()); - - if(n){ - n=std::size_t(std::ceil(float(n)/mlf)); /* elements -> slots */ - n=capacity_for(n); /* exact resulting capacity */ - - if(n>capacity()){ - auto new_arrays_=new_arrays(n); - delete_arrays(arrays); - arrays=new_arrays_; - ml=initial_max_load(); - } - } - } - template void unchecked_insert(Value&& x) { From b72dbef1a93a9649c7a2dd74f8551e94248cdf3d Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 4 May 2023 18:51:03 +0200 Subject: [PATCH 216/327] added equality comparison to reference --- doc/unordered/concurrent_flat_map.adoc | 65 +++++++++++++++++++++----- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc index 7adb2323..c3222ab0 100644 --- a/doc/unordered/concurrent_flat_map.adoc +++ b/doc/unordered/concurrent_flat_map.adoc @@ -214,39 +214,39 @@ namespace boost { class Pred = std::equal_to>, class Allocator = std::allocator>> concurrent_flat_map(InputIterator, InputIterator, typename xref:#concurrent_flat_map_deduction_guides[__see below__]::size_type = xref:#concurrent_flat_map_deduction_guides[__see below__], - Hash = Hash(), Pred = Pred(), Allocator = Allocator()) + Hash = Hash(), Pred = Pred(), Allocator = Allocator()) -> concurrent_flat_map, xref:#concurrent_flat_map_iter_mapped_type[__iter-mapped-type__], Hash, - Pred, Allocator>; + Pred, Allocator>; template, class Pred = std::equal_to, class Allocator = std::allocator>> concurrent_flat_map(std::initializer_list>, - typename xref:#concurrent_flat_map_deduction_guides[__see below__]::size_type = xref:#concurrent_flat_map_deduction_guides[__see below__], Hash = Hash(), - Pred = Pred(), Allocator = Allocator()) + typename xref:#concurrent_flat_map_deduction_guides[__see below__]::size_type = xref:#concurrent_flat_map_deduction_guides[__see below__], Hash = Hash(), + Pred = Pred(), Allocator = Allocator()) -> concurrent_flat_map; template concurrent_flat_map(InputIterator, InputIterator, typename xref:#concurrent_flat_map_deduction_guides[__see below__]::size_type, Allocator) -> concurrent_flat_map, xref:#concurrent_flat_map_iter_mapped_type[__iter-mapped-type__], - boost::hash>, - std::equal_to>, Allocator>; + boost::hash>, + std::equal_to>, Allocator>; template concurrent_flat_map(InputIterator, InputIterator, Allocator) -> concurrent_flat_map, xref:#concurrent_flat_map_iter_mapped_type[__iter-mapped-type__], - boost::hash>, - std::equal_to>, Allocator>; + boost::hash>, + std::equal_to>, Allocator>; template concurrent_flat_map(InputIterator, InputIterator, typename xref:#concurrent_flat_map_deduction_guides[__see below__]::size_type, Hash, - Allocator) + Allocator) -> concurrent_flat_map, xref:#concurrent_flat_map_iter_mapped_type[__iter-mapped-type__], Hash, - std::equal_to>, Allocator>; + std::equal_to>, Allocator>; template concurrent_flat_map(std::initializer_list>, typename xref:#concurrent_flat_map_deduction_guides[__see below__]::size_type, - Allocator) + Allocator) -> concurrent_flat_map, std::equal_to, Allocator>; template @@ -255,9 +255,18 @@ namespace boost { template concurrent_flat_map(std::initializer_list>, typename xref:#concurrent_flat_map_deduction_guides[__see below__]::size_type, - Hash, Allocator) + Hash, Allocator) -> concurrent_flat_map, Allocator>; + // Equality Comparisons + template + bool xref:#concurrent_flat_map_operator[operator==](const concurrent_flat_map& x, + const concurrent_flat_map& y); + + template + bool xref:#concurrent_flat_map_operator_2[operator!=](const concurrent_flat_map& x, + const concurrent_flat_map& y); + // swap template void xref:#concurrent_flat_map_swap_2[swap](concurrent_flat_map& x, @@ -1339,6 +1348,38 @@ template std::tuple_element_t<1, xref:#concurrent_map_iter_value_type[__iter-value-type__]>>; // exposition only ----- +=== Equality Comparisons + +==== operator== +```c++ +template + bool operator==(const concurrent_flat_map& x, + const concurrent_flat_map& y); +``` + +Returns `true` if `x.size() == y.size()` and for every element in `x`, there is an element in `y` with the same key, with an equal value (using `operator==` to compare the value types). + +[horizontal] +Concurrency:;; Blocking on `x` and `y`. +Notes:;; Behavior is undefined if the two tables don't have equivalent equality predicates. + +--- + +==== operator!= +```c++ +template + bool operator!=(const concurrent_flat_map& x, + const concurrent_flat_map& y); +``` + +Returns `false` if `x.size() == y.size()` and for every element in `x`, there is an element in `y` with the same key, with an equal value (using `operator==` to compare the value types). + +[horizontal] +Concurrency:;; Blocking on `x` and `y`. +Notes:;; Behavior is undefined if the two tables don't have equivalent equality predicates. + +--- + === Swap ```c++ template From 70e3dc46288e8057ffe2511d440a959c1e10cec4 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 4 May 2023 19:16:39 +0200 Subject: [PATCH 217/327] Changed the return type of iterator/initializer_list insert[_or_[c]visit] and merge to size_type --- doc/unordered/concurrent_flat_map.adoc | 83 +++++++++++++++----------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc index c3222ab0..1d3aad8f 100644 --- a/doc/unordered/concurrent_flat_map.adoc +++ b/doc/unordered/concurrent_flat_map.adoc @@ -97,18 +97,18 @@ namespace boost { // visitation - template std::size_t xref:#concurrent_flat_map_cvisit[visit](const key_type& k, F f); - template std::size_t xref:#concurrent_flat_map_cvisit[visit](const key_type& k, F f) const; - template std::size_t xref:#concurrent_flat_map_cvisit[cvisit](const key_type& k, F f) const; - template std::size_t xref:#concurrent_flat_map_cvisit[visit](const K& k, F f); - template std::size_t xref:#concurrent_flat_map_cvisit[visit](const K& k, F f) const; - template std::size_t xref:#concurrent_flat_map_cvisit[cvisit](const K& k, F f) const; + template size_t xref:#concurrent_flat_map_cvisit[visit](const key_type& k, F f); + template size_t xref:#concurrent_flat_map_cvisit[visit](const key_type& k, F f) const; + template size_t xref:#concurrent_flat_map_cvisit[cvisit](const key_type& k, F f) const; + template size_t xref:#concurrent_flat_map_cvisit[visit](const K& k, F f); + template size_t xref:#concurrent_flat_map_cvisit[visit](const K& k, F f) const; + template size_t xref:#concurrent_flat_map_cvisit[cvisit](const K& k, F f) const; - template std::size_t xref:#concurrent_flat_map_cvisit_all[visit_all](F f); - template std::size_t xref:#concurrent_flat_map_cvisit_all[visit_all](F f) const; - template std::size_t xref:#concurrent_flat_map_cvisit_all[cvisit_all](F f) const; + template size_t xref:#concurrent_flat_map_cvisit_all[visit_all](F f); + template size_t xref:#concurrent_flat_map_cvisit_all[visit_all](F f) const; + template size_t xref:#concurrent_flat_map_cvisit_all[cvisit_all](F f) const; template - void std::size_t xref:#concurrent_flat_map_parallel_cvisit_all[visit_all](ExecutionPolicy&& policy, F f); + void xref:#concurrent_flat_map_parallel_cvisit_all[visit_all](ExecutionPolicy&& policy, F f); template void xref:#concurrent_flat_map_parallel_cvisit_all[visit_all](ExecutionPolicy&& policy, F f) const; template @@ -125,8 +125,8 @@ namespace boost { bool xref:#concurrent_flat_map_copy_insert[insert](const init_type& obj); bool xref:#concurrent_flat_map_move_insert[insert](value_type&& obj); bool xref:#concurrent_flat_map_move_insert[insert](init_type&& obj); - template void xref:#concurrent_flat_map_insert_iterator_range[insert](InputIterator first, InputIterator last); - void xref:#concurrent_flat_map_insert_initializer_list[insert](std::initializer_list il); + template size_type xref:#concurrent_flat_map_insert_iterator_range[insert](InputIterator first, InputIterator last); + size_type xref:#concurrent_flat_map_insert_initializer_list[insert](std::initializer_list il); template bool xref:#concurrent_flat_map_emplace_or_cvisit[emplace_or_visit](Args&&... args, F&& f); template bool xref:#concurrent_flat_map_emplace_or_cvisit[emplace_or_cvisit](Args&&... args, F&& f); @@ -139,11 +139,11 @@ namespace boost { template bool xref:#concurrent_flat_map_move_insert_or_cvisit[insert_or_visit](init_type&& obj, F f); template bool xref:#concurrent_flat_map_move_insert_or_cvisit[insert_or_cvisit](init_type&& obj, F f); template - void xref:#concurrent_flat_map_insert_iterator_range_or_visit[insert_or_visit](InputIterator first, InputIterator last, F f); + size_type xref:#concurrent_flat_map_insert_iterator_range_or_visit[insert_or_visit](InputIterator first, InputIterator last, F f); template - void xref:#concurrent_flat_map_insert_iterator_range_or_visit[insert_or_cvisit](InputIterator first, InputIterator last, F f); - template void xref:#concurrent_flat_map_insert_initializer_list_or_visit[insert_or_visit](std::initializer_list il, F f); - template void xref:#concurrent_flat_map_insert_initializer_list_or_visit[insert_or_cvisit](std::initializer_list il, F f); + size_type xref:#concurrent_flat_map_insert_iterator_range_or_visit[insert_or_cvisit](InputIterator first, InputIterator last, F f); + template size_type xref:#concurrent_flat_map_insert_initializer_list_or_visit[insert_or_visit](std::initializer_list il, F f); + template size_type xref:#concurrent_flat_map_insert_initializer_list_or_visit[insert_or_cvisit](std::initializer_list il, F f); template bool xref:#concurrent_flat_map_try_emplace[try_emplace](const key_type& k, Args&&... args); template bool xref:#concurrent_flat_map_try_emplace[try_emplace](key_type&& k, Args&&... args); @@ -180,9 +180,9 @@ namespace boost { void xref:#concurrent_flat_map_clear[clear]() noexcept; template - void xref:#concurrent_flat_map_merge[merge](concurrent_flat_map& source); + size_type xref:#concurrent_flat_map_merge[merge](concurrent_flat_map& source); template - void xref:#concurrent_flat_map_merge[merge](concurrent_flat_map&& source); + size_type xref:#concurrent_flat_map_merge[merge](concurrent_flat_map&& source); // observers hasher xref:#concurrent_flat_map_hash_function[hash_function]() const; @@ -651,12 +651,12 @@ Concurrency:;; Blocking on `*this`. ==== [c]visit ```c++ -template std::size_t visit(const key_type& k, F f); -template std::size_t visit(const key_type& k, F f) const; -template std::size_t cvisit(const key_type& k, F f) const; -template std::size_t visit(const K& k, F f); -template std::size_t visit(const K& k, F f) const; -template std::size_t cvisit(const K& k, F f) const; +template size_t visit(const key_type& k, F f); +template size_t visit(const key_type& k, F f) const; +template size_t cvisit(const key_type& k, F f) const; +template size_t visit(const K& k, F f); +template size_t visit(const K& k, F f) const; +template size_t cvisit(const K& k, F f) const; ``` If an element `x` exists with key equivalent to `k`, invokes `f` with a reference to `x`. @@ -671,9 +671,9 @@ Notes:;; The `template ` overloads only participate in overloa ==== [c]visit_all ```c++ -template std::size_t visit_all(F f); -template std::size_t visit_all(F f) const; -template std::size_t cvisit_all(F f) const; +template size_t visit_all(F f); +template size_t visit_all(F f) const; +template size_t cvisit_all(F f) const; ``` Successively invokes `f` with references to each of the elements in the table. @@ -799,7 +799,7 @@ A call of the form `insert(x)`, where `x` is equally convertible to both `value_ ==== Insert Iterator Range ```c++ -template void insert(InputIterator first, InputIterator last); +template size_type insert(InputIterator first, InputIterator last); ``` Equivalent to @@ -808,11 +808,14 @@ Equivalent to while(first != last) this->xref:#concurrent_flat_map_emplace[emplace](*first++); ----- +[horizontal] +Returns:;; The number of elements inserted. + --- ==== Insert Initializer List ```c++ -void insert(std::initializer_list il); +size_type insert(std::initializer_list il); ``` Equivalent to @@ -821,6 +824,9 @@ Equivalent to this->xref:#concurrent_flat_map_insert_iterator_range[insert](il.begin(), il.end()); ----- +[horizontal] +Returns:;; The number of elements inserted. + --- ==== emplace_or_[c]visit @@ -889,9 +895,9 @@ only if `std::remove_reference::type` is `value_type`. ==== Insert Iterator Range or Visit ```c++ template - void insert_or_visit(InputIterator first, InputIterator last, F f); + size_type insert_or_visit(InputIterator first, InputIterator last, F f); template - void insert_or_cvisit(InputIterator first, InputIterator last, F f); + size_type insert_or_cvisit(InputIterator first, InputIterator last, F f); ``` Equivalent to @@ -900,12 +906,15 @@ Equivalent to while(first != last) this->xref:#concurrent_flat_map_emplace_or_cvisit[emplace_or_(c)visit](*first++, f); ----- +[horizontal] +Returns:;; The number of elements inserted. + --- ==== Insert Initializer List or Visit ```c++ -template void insert_or_visit(std::initializer_list il, F f); -template void insert_or_cvisit(std::initializer_list il, F f); +template size_type insert_or_visit(std::initializer_list il, F f); +template size_type insert_or_cvisit(std::initializer_list il, F f); ``` Equivalent to @@ -914,6 +923,9 @@ Equivalent to this->xref:#concurrent_flat_map_insert_iterator_range_or_visit[insert_or(c)visit](il.begin(), il.end(), f); ----- +[horizontal] +Returns:;; The number of elements inserted. + --- ==== try_emplace @@ -1131,14 +1143,15 @@ Concurrency:;; Blocking on `*this`. ==== merge ```c++ template - void merge(concurrent_flat_map& source); + size_type merge(concurrent_flat_map& source); template - void merge(concurrent_flat_map&& source); + size_type merge(concurrent_flat_map&& source); ``` Move-inserts all the elements from `source` whose key is not already present in `*this`, and erases them from `source`. [horizontal] +Returns:;; The number of elements inserted. Concurrency:;; Blocking on `*this` and `source`. --- From 814264082f6d08f4fcb21388ee3733caa4a4d1e8 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 4 May 2023 19:19:37 +0200 Subject: [PATCH 218/327] fixed BNF syntax --- doc/unordered/concurrent_flat_map.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc index 1d3aad8f..58c211a8 100644 --- a/doc/unordered/concurrent_flat_map.adoc +++ b/doc/unordered/concurrent_flat_map.adoc @@ -903,7 +903,7 @@ template Equivalent to [listing,subs="+macros,+quotes"] ----- - while(first != last) this->xref:#concurrent_flat_map_emplace_or_cvisit[emplace_or_(c)visit](*first++, f); + while(first != last) this->xref:#concurrent_flat_map_emplace_or_cvisit[emplace_or_[c\]visit](*first++, f); ----- [horizontal] @@ -920,7 +920,7 @@ template size_type insert_or_cvisit(std::initializer_list i Equivalent to [listing,subs="+macros,+quotes"] ----- - this->xref:#concurrent_flat_map_insert_iterator_range_or_visit[insert_or(c)visit](il.begin(), il.end(), f); + this->xref:#concurrent_flat_map_insert_iterator_range_or_visit[insert_or[c\]visit](il.begin(), il.end(), f); ----- [horizontal] From 03fccc19472bdf42065879a4165ab270c75a6658 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 5 May 2023 17:37:20 +0200 Subject: [PATCH 219/327] refined concurrency requirements and guarantees --- doc/unordered/concurrent_flat_map.adoc | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc index 58c211a8..3a1c021f 100644 --- a/doc/unordered/concurrent_flat_map.adoc +++ b/doc/unordered/concurrent_flat_map.adoc @@ -327,8 +327,19 @@ at the expense of extra computational cost. === Concurrency requirements and guarantees -Concurrent member function invocations for the same instance of `Hash`, `Pred`, and `Allocator` -are required to not introduce data races (they must be thread-safe). +Concurrent invocations of `operator()` on the same const instance of `Hash` or `Pred` are required +to not introduce data races. For `Alloc` being either `Allocator` or any allocator type rebound +from `Allocator`, concurrent invocations of the following operations on the same instance `al` of `Alloc` +are required to not introduce data races: + +* Copy construction from `al` of an allocator rebound from `Alloc` +* `std::allocator_traits::allocate` +* `std::allocator_traits::deallocate` +* `std::allocator_traits::construct` +* `std::allocator_traits::destruct` + +In general, these requirements on `Hash`, `Pred` and `Allocator` are met if these types +are not stateful or if the operations only involve constant access to internal data members. With the exception of destruction, concurrent invocations of any operation on the same instance of a `concurrent_flat_map` do not introduce data races — that is, they are thread-safe. @@ -342,8 +353,10 @@ only when an internal rehashing is issued. Access or modification of an element of a `boost::concurrent_flat_map` passed by reference to a user-provided visitation function do not introduce data races when the visitation function -is executed internally by the `boost::concurrent_flat_map`. Visitation -functions executed by a `concurrent_flat_map` `x` are not allowed to invoke any operation +is executed internally by the `boost::concurrent_flat_map`. +Any `boost::concurrent_flat_map operation` that inserts or modifies an element `e` +synchronizes with the internal invocation of a visitation function on `e`. +Visitation functions executed by a `boost::concurrent_flat_map` `x` are not allowed to invoke any operation on `x`; invoking operations on a different `boost::concurrent_flat_map` instance `y` is allowed only if concurrent outstanding operations on `y` do not access `x` directly or indirectly. From 719394c522a66f550b5c55f7f50b67c420a08147 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 5 May 2023 17:55:02 +0200 Subject: [PATCH 220/327] Split important info int separate paragraphs --- doc/unordered/concurrent_flat_map.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc index 3a1c021f..7425c462 100644 --- a/doc/unordered/concurrent_flat_map.adoc +++ b/doc/unordered/concurrent_flat_map.adoc @@ -354,8 +354,10 @@ only when an internal rehashing is issued. Access or modification of an element of a `boost::concurrent_flat_map` passed by reference to a user-provided visitation function do not introduce data races when the visitation function is executed internally by the `boost::concurrent_flat_map`. + Any `boost::concurrent_flat_map operation` that inserts or modifies an element `e` synchronizes with the internal invocation of a visitation function on `e`. + Visitation functions executed by a `boost::concurrent_flat_map` `x` are not allowed to invoke any operation on `x`; invoking operations on a different `boost::concurrent_flat_map` instance `y` is allowed only if concurrent outstanding operations on `y` do not access `x` directly or indirectly. From bf733661170202d8e5c89f401cdabc24a9b929b2 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 5 May 2023 17:55:53 +0200 Subject: [PATCH 221/327] typo --- doc/unordered/concurrent_flat_map.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc index 7425c462..7a189a92 100644 --- a/doc/unordered/concurrent_flat_map.adoc +++ b/doc/unordered/concurrent_flat_map.adoc @@ -336,7 +336,7 @@ are required to not introduce data races: * `std::allocator_traits::allocate` * `std::allocator_traits::deallocate` * `std::allocator_traits::construct` -* `std::allocator_traits::destruct` +* `std::allocator_traits::destroy` In general, these requirements on `Hash`, `Pred` and `Allocator` are met if these types are not stateful or if the operations only involve constant access to internal data members. From 1c98a4a8f11025c5c64bf326924761d3f07b97f3 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 5 May 2023 18:56:33 +0200 Subject: [PATCH 222/327] changed all titles to Title Casing --- doc/unordered/concurrent_flat_map.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc index 7a189a92..a83dc635 100644 --- a/doc/unordered/concurrent_flat_map.adoc +++ b/doc/unordered/concurrent_flat_map.adoc @@ -325,7 +325,7 @@ at the expense of extra computational cost. --- -=== Concurrency requirements and guarantees +=== Concurrency Requirements and Guarantees Concurrent invocations of `operator()` on the same const instance of `Hash` or `Pred` are required to not introduce data races. For `Alloc` being either `Allocator` or any allocator type rebound @@ -1203,7 +1203,7 @@ Returns:;; The table's key equality predicate. --- -=== Map operations +=== Map Operations ==== count ```c++ From 99b0868283e9f570527a4205c6e356f020c0b3e0 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 5 May 2023 10:20:37 -0700 Subject: [PATCH 223/327] Add initial impl of merge() --- .../boost/unordered/concurrent_flat_map.hpp | 17 ++ .../unordered/detail/foa/concurrent_table.hpp | 49 +++-- test/Jamfile.v2 | 1 + test/cfoa/helpers.hpp | 10 + test/cfoa/merge_tests.cpp | 193 ++++++++++++++++++ 5 files changed, 258 insertions(+), 12 deletions(-) create mode 100644 test/cfoa/merge_tests.cpp diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index df3718dd..45e65284 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -140,6 +140,10 @@ namespace boost { class concurrent_flat_map { private: + template + friend class concurrent_flat_map; + using type_policy = detail::concurrent_map_types; detail::foa::concurrent_table table_; @@ -703,6 +707,19 @@ namespace boost { void clear() noexcept { table_.clear(); } + template + size_type merge(concurrent_flat_map& x) + { + BOOST_ASSERT(get_allocator() == x.get_allocator()); + return table_.merge(x.table_); + } + + template + size_type merge(concurrent_flat_map&& x) + { + return merge(x); + } + /// Hash Policy /// void rehash(size_type n) { table_.rehash(n); } diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index a19d28b9..f933e774 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -362,6 +362,10 @@ class concurrent_table: using super::N; using prober=typename super::prober; + template< + typename TypePolicy2,typename Hash2,typename Pred2,typename Allocator2> + friend class concurrent_table; + public: using key_type=typename super::key_type; using init_type=typename super::init_type; @@ -683,14 +687,19 @@ public: // TODO: should we accept different allocator too? template - void merge(concurrent_table& x) + size_type merge(concurrent_table& x) { - auto lck=exclusive_access(*this,x); - x.super::for_all_elements( /* super::for_all_elements -> unprotected */ + using merge_table_type=concurrent_table; + using super2=typename merge_table_type::super; + + auto lck=exclusive_access(*this,x); + size_type s=unprotected_size(); + static_cast(x).for_all_elements( /* super::for_all_elements -> unprotected */ [&,this](group_type* pg,unsigned int n,element_type* p){ - erase_on_exit e{x,pg,n,p}; + erase_on_exit e{x,pg,n,p}; if(!unprotected_emplace(type_policy::move(*p)))e.rollback(); }); + return size_type{unprotected_size()-s}; } template @@ -799,6 +808,14 @@ private: return {x.mutexes,y.mutexes}; } + template + inline exclusive_bilock_guard exclusive_access( + const concurrent_table& x, + const concurrent_table& y) + { + return {x.mutexes,y.mutexes}; + } + /* Tag-dispatched shared/exclusive group access */ using group_shared=std::false_type; @@ -835,21 +852,29 @@ private: >::type cast_for(group_exclusive,value_type& x){return x;} + template struct erase_on_exit { + using table_group_type=typename Table::group_type; + using table_element_type=typename Table::element_type; + using table_super_type=typename Table::super; + erase_on_exit( - concurrent_table& x_, - group_type* pg_,unsigned int pos_,element_type* p_): + Table& x_,table_group_type* pg_,unsigned int pos_,table_element_type* p_): x{x_},pg{pg_},pos{pos_},p{p_}{} - ~erase_on_exit(){if(!rollback_)x.super::erase(pg,pos,p);} + ~erase_on_exit() + { + if(!rollback_) + static_cast(x).erase(pg,pos,p); + } void rollback(){rollback_=true;} - concurrent_table &x; - group_type *pg; - unsigned int pos; - element_type *p; - bool rollback_=false; + Table &x; + table_group_type *pg; + unsigned int pos; + table_element_type *p; + bool rollback_=false; }; template diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 209e73c4..57be13da 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -186,6 +186,7 @@ local CFOA_TESTS = assign_tests clear_tests swap_tests + merge_tests ; for local test in $(CFOA_TESTS) diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index aa5a86a4..59a35be7 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -258,6 +258,16 @@ std::size_t hash_value(raii const& r) noexcept return hasher(r.x_); } +namespace std { + template <> struct hash + { + std::size_t operator()(raii const& r) const noexcept + { + return hash_value(r); + } + }; +} // namespace std + template auto make_random_values(std::size_t count, F f) -> std::vector { diff --git a/test/cfoa/merge_tests.cpp b/test/cfoa/merge_tests.cpp new file mode 100644 index 00000000..9b6bbb0e --- /dev/null +++ b/test/cfoa/merge_tests.cpp @@ -0,0 +1,193 @@ +// 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{402031699}; + +using test::default_generator; +using test::limited_range; +using test::sequential; + +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; + +struct +{ + template + std::size_t operator()(X1& x1, X2& x2) const noexcept + { + return x1.merge(x2); + } +} lvalue_merge; + +struct +{ + template + std::size_t operator()(X1& x1, X2& x2) const noexcept + { + return x1.merge(std::move(x2)); + } +} rvalue_merge; + +namespace { + template + void merge_tests(F merger, G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 8, [&] { return gen(rg); }); + + auto ref_map = + boost::unordered_flat_map(values.begin(), values.end()); + + { + raii::reset_counts(); + + map_type x(values.size(), hasher(1), key_equal(2), allocator_type(3)); + + auto const old_cc = +raii::copy_constructor; + + std::atomic expected_copies{0}; + std::atomic num_merged{0}; + + thread_runner(values, [&x, &expected_copies, &num_merged, merger]( + boost::span s) { + using map2_type = boost::unordered::concurrent_flat_map, std::equal_to, allocator_type>; + + map2_type y(s.begin(), s.end(), s.size(), allocator_type(3)); + expected_copies += 2 * y.size(); + + BOOST_TEST(x.get_allocator() == y.get_allocator()); + num_merged += merger(x, y); + }); + + BOOST_TEST_EQ(raii::copy_constructor, old_cc + expected_copies); + BOOST_TEST_EQ(raii::move_constructor, 2 * ref_map.size()); + BOOST_TEST_EQ(+num_merged, ref_map.size()); + + test_fuzzy_matches_reference(x, ref_map, rg); + } + check_raii_counts(); + } + + template + void insert_and_merge_tests(G gen, test::random_generator rg) + { + using map2_type = boost::unordered::concurrent_flat_map, std::equal_to, allocator_type>; + + auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); }); + auto vals2 = make_random_values(1024 * 4, [&] { return gen(rg); }); + + auto ref_map = boost::unordered_flat_map(); + ref_map.insert(vals1.begin(), vals1.end()); + ref_map.insert(vals2.begin(), vals2.end()); + + { + raii::reset_counts(); + + map_type x1(2 * vals1.size(), hasher(1), key_equal(2), allocator_type(3)); + + map2_type x2(2 * vals1.size(), 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_merges{0}; + + auto const old_mc = +raii::move_constructor; + BOOST_TEST_EQ(old_mc, 0u); + + 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(); + std::this_thread::yield(); + } + } + + done1 = true; + }); + + t2 = std::thread([&x2, &vals2, &l, &done2] { + l.arrive_and_wait(); + + for (std::size_t idx = 0; idx < vals2.size(); ++idx) { + auto const& val = vals2[idx]; + x2.insert(val); + if (idx % 100 == 0) { + std::this_thread::yield(); + } + } + + done2 = true; + }); + + t3 = std::thread([&x1, &x2, &m, &cv, &done1, &done2, &num_merges] { + while (x1.empty() && x2.empty()) { + } + + do { + { + std::unique_lock lk(m); + cv.wait(lk, [] { return true; }); + } + num_merges += x1.merge(x2); + std::this_thread::yield(); + num_merges += x2.merge(x1); + + } while (!done1 || !done2); + + BOOST_TEST(done1); + BOOST_TEST(done2); + }); + + t1.join(); + t2.join(); + t3.join(); + + if (num_merges > 0) { + // num merges is 0 most commonly in the cast of the limited_range + // generator as both maps will contains keys from 0 to 99 + BOOST_TEST_EQ(+raii::move_constructor, 2 * num_merges); + } + + x1.merge(x2); + test_fuzzy_matches_reference(x1, ref_map, rg); + } + + check_raii_counts(); + } + +} // namespace + +// clang-format off +UNORDERED_TEST( + merge_tests, + ((lvalue_merge)(rvalue_merge)) + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + insert_and_merge_tests, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) +// clang-format on + +RUN_TESTS() From 5b775345ba2138ef2f902f9ce8d792d25c20229e Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 5 May 2023 11:47:20 -0700 Subject: [PATCH 224/327] Clean up concurrent_table's merge impl --- .../unordered/detail/foa/concurrent_table.hpp | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index f933e774..8c0542e4 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -692,11 +693,14 @@ public: using merge_table_type=concurrent_table; using super2=typename merge_table_type::super; + // for clang + boost::ignore_unused(); + auto lck=exclusive_access(*this,x); size_type s=unprotected_size(); - static_cast(x).for_all_elements( /* super::for_all_elements -> unprotected */ + x.super2::for_all_elements( /* super2::for_all_elements -> unprotected */ [&,this](group_type* pg,unsigned int n,element_type* p){ - erase_on_exit e{x,pg,n,p}; + typename merge_table_type::erase_on_exit e{x,pg,n,p}; if(!unprotected_emplace(type_policy::move(*p)))e.rollback(); }); return size_type{unprotected_size()-s}; @@ -852,29 +856,21 @@ private: >::type cast_for(group_exclusive,value_type& x){return x;} - template struct erase_on_exit { - using table_group_type=typename Table::group_type; - using table_element_type=typename Table::element_type; - using table_super_type=typename Table::super; - erase_on_exit( - Table& x_,table_group_type* pg_,unsigned int pos_,table_element_type* p_): + concurrent_table& x_, + group_type* pg_,unsigned int pos_,element_type* p_): x{x_},pg{pg_},pos{pos_},p{p_}{} - ~erase_on_exit() - { - if(!rollback_) - static_cast(x).erase(pg,pos,p); - } + ~erase_on_exit(){if(!rollback_)x.super::erase(pg,pos,p);} void rollback(){rollback_=true;} - Table &x; - table_group_type *pg; - unsigned int pos; - table_element_type *p; - bool rollback_=false; + concurrent_table &x; + group_type *pg; + unsigned int pos; + element_type *p; + bool rollback_=false; }; template From 53328766b952e88b50218b7c69d8b6e5f2a959a1 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 5 May 2023 15:41:08 -0700 Subject: [PATCH 225/327] Return size_type instead of size_t --- .../boost/unordered/concurrent_flat_map.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 45e65284..04b69eaf 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -306,21 +306,21 @@ namespace boost { } template - BOOST_FORCEINLINE std::size_t visit(key_type const& k, F f) + BOOST_FORCEINLINE size_type visit(key_type const& k, F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.visit(k, f); } template - BOOST_FORCEINLINE std::size_t visit(key_type const& k, F f) const + BOOST_FORCEINLINE size_type visit(key_type const& k, F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.visit(k, f); } template - BOOST_FORCEINLINE std::size_t cvisit(key_type const& k, F f) const + BOOST_FORCEINLINE size_type cvisit(key_type const& k, F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.visit(k, f); @@ -328,7 +328,7 @@ namespace boost { template BOOST_FORCEINLINE typename std::enable_if< - detail::are_transparent::value, std::size_t>::type + detail::are_transparent::value, size_type>::type visit(K&& k, F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) @@ -337,7 +337,7 @@ namespace boost { template BOOST_FORCEINLINE typename std::enable_if< - detail::are_transparent::value, std::size_t>::type + detail::are_transparent::value, size_type>::type visit(K&& k, F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) @@ -346,26 +346,26 @@ namespace boost { template BOOST_FORCEINLINE typename std::enable_if< - detail::are_transparent::value, std::size_t>::type + detail::are_transparent::value, size_type>::type cvisit(K&& k, F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.visit(std::forward(k), f); } - template BOOST_FORCEINLINE std::size_t visit_all(F f) + template BOOST_FORCEINLINE size_type visit_all(F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.visit_all(f); } - template BOOST_FORCEINLINE std::size_t visit_all(F f) const + template BOOST_FORCEINLINE size_type visit_all(F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.visit_all(f); } - template BOOST_FORCEINLINE std::size_t cvisit_all(F f) const + template BOOST_FORCEINLINE size_type cvisit_all(F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.cvisit_all(f); From f0fe62d6abd295e6e6f44a5348afa5cff8172c49 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 5 May 2023 15:41:23 -0700 Subject: [PATCH 226/327] Add count(), contains() --- .../boost/unordered/concurrent_flat_map.hpp | 20 +++++ test/cfoa/visit_tests.cpp | 79 +++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 04b69eaf..e3283a80 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -720,6 +720,26 @@ namespace boost { return merge(x); } + size_type count(key_type const& k) const { return table_.count(k); } + + template + typename std::enable_if< + detail::are_transparent::value, size_type>::type + count(K const& k) + { + return table_.count(k); + } + + bool contains(key_type const& k) const { return table_.contains(k); } + + template + typename std::enable_if< + detail::are_transparent::value, bool>::type + contains(K const& k) const + { + return table_.contains(k); + } + /// Hash Policy /// void rehash(size_type n) { table_.rehash(n); } diff --git a/test/cfoa/visit_tests.cpp b/test/cfoa/visit_tests.cpp index bc4fd13b..fae4deae 100644 --- a/test/cfoa/visit_tests.cpp +++ b/test/cfoa/visit_tests.cpp @@ -107,6 +107,45 @@ namespace { num_visits = 0; total_count = 0; } + + { + thread_runner(values, [&x, &total_count](boost::span s) { + for (auto const& val : s) { + auto r = val.first.x_; + BOOST_TEST(r >= 0); + + auto count = x.count(val.first); + BOOST_TEST_EQ(count, 1u); + total_count += count; + + count = x.count(val.second); + BOOST_TEST_EQ(count, 0u); + } + }); + + BOOST_TEST_EQ(total_count, values.size()); + + num_visits = 0; + total_count = 0; + } + + { + thread_runner(values, [&x](boost::span s) { + for (auto const& val : s) { + auto r = val.first.x_; + BOOST_TEST(r >= 0); + + auto contains = x.contains(val.first); + BOOST_TEST(contains); + + contains = x.contains(val.second); + BOOST_TEST(!contains); + } + }); + + num_visits = 0; + total_count = 0; + } } } lvalue_visitor; @@ -204,6 +243,45 @@ namespace { num_visits = 0; total_count = 0; } + + { + thread_runner(values, [&x, &total_count](boost::span s) { + for (auto const& val : s) { + auto r = val.first.x_; + BOOST_TEST(r >= 0); + + auto count = x.count(val.first.x_); + BOOST_TEST_EQ(count, 1u); + total_count += count; + + count = x.count(val.second.x_); + BOOST_TEST_EQ(count, 0u); + } + }); + + BOOST_TEST_EQ(total_count, values.size()); + + num_visits = 0; + total_count = 0; + } + + { + thread_runner(values, [&x](boost::span s) { + for (auto const& val : s) { + auto r = val.first.x_; + BOOST_TEST(r >= 0); + + auto contains = x.contains(val.first.x_); + BOOST_TEST(contains); + + contains = x.contains(val.second.x_); + BOOST_TEST(!contains); + } + }); + + num_visits = 0; + total_count = 0; + } } } transp_visitor; @@ -342,6 +420,7 @@ namespace { auto reference_map = boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); { From 02197674f489d062d6177ce1102920a9dac58c7d Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 6 May 2023 12:44:07 +0200 Subject: [PATCH 227/327] prevented VS C4800 warning --- include/boost/unordered/detail/foa/concurrent_table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 8c0542e4..7eee515e 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -730,7 +730,7 @@ public: template BOOST_FORCEINLINE bool contains(Key&& x)const { - return visit(std::forward(x),[](const value_type&){}); + return visit(std::forward(x),[](const value_type&){})!=0; } std::size_t capacity()const noexcept From ba25041fc8e314bb2c63ed4afdb8f736841a4cee Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 8 May 2023 18:37:36 +0200 Subject: [PATCH 228/327] added tutorial on boost::concurrent_flat_map --- doc/unordered.adoc | 1 + doc/unordered/concurrent_flat_map_intro.adoc | 180 +++++++++++++++++++ doc/unordered/intro.adoc | 10 ++ 3 files changed, 191 insertions(+) create mode 100644 doc/unordered/concurrent_flat_map_intro.adoc diff --git a/doc/unordered.adoc b/doc/unordered.adoc index 07e09dbb..3d7a54fb 100644 --- a/doc/unordered.adoc +++ b/doc/unordered.adoc @@ -14,6 +14,7 @@ include::unordered/intro.adoc[] include::unordered/buckets.adoc[] include::unordered/hash_equality.adoc[] include::unordered/comparison.adoc[] +include::unordered/concurrent_flat_map_intro.adoc[] include::unordered/compliance.adoc[] include::unordered/benchmarks.adoc[] include::unordered/rationale.adoc[] diff --git a/doc/unordered/concurrent_flat_map_intro.adoc b/doc/unordered/concurrent_flat_map_intro.adoc new file mode 100644 index 00000000..722e5ef4 --- /dev/null +++ b/doc/unordered/concurrent_flat_map_intro.adoc @@ -0,0 +1,180 @@ +[#concurrent_flat_map_intro] += An introduction to boost::concurrent_flat_map + +:idprefix: concurrent_flat_map_intro_ + +`boost::concurrent_flat_map` is a hash table that allows concurrent write/read access from +different threads without having to implement any synchronzation mechanism on the user's side. + +[source,c++] +---- +std::vector input; +boost::concurrent_flat_map m; + +... + +// process input in parallel +const int num_threads = 8; +std::vector threads; +std::size_t chunk = input.size() / num_threads; // how many elements per thread + +for (int i = 0; i < num_threads; ++i) { + threads.emplace_back([&,i] { + // calculate the portion of input this thread takes care of + std::size_t start = i * chunk; + std::size_t end = (i == num_threads - 1)? input.size(): (i + 1) * chunk; + + for (std::size_t n = start; n < end; ++n) { + m.emplace(input[n], calculation(input[n])); + } + }); +} +---- + +In the example above, threads access `m` without synchronization, just as we'd do in a +single-threaded scenario. In an ideal setting, if a given workload is distributed among +_N_ threads, execution is _N_ times faster than with one thread —this limit is +never attained in practice due to synchronization overheads and _contention_ (one thread +waiting for another to leave a locked portion of the map), but `boost::concurrent_flat_map` +is designed to perform with very little overhead and typically achieves _linear scaling_ +(that is, performance is proportional to the number of threads up to the number of +logical cores in the CPU). + +== Visitation-based API + +The first thing a new user of `boost::concurrent_flat_map` will notice is that this +class _does not provide iterators_ (which makes it technically +not a https://en.cppreference.com/w/cpp/named_req/Container[Container^] +in the C++ standard sense). The reason for this is that iterators are inherently +thread-unsafe. Consider this hypothetical code: + +[source,c++] +---- +auto it = m.find(k); // A: get an iterator pointing to the element with key k +if (it != m.end() ) { + some_function(*it); // B: use the value of the element +} +---- + +In a multithreaded scenario, the iterator `it` may be invalid at point B if some other +thread issues an `m.erase(k)` operation between A and B. There are designs that +can remedy this by making iterators lock the element they point to, but this +approach lends itself to high contention and can easily produce deadlocks in a program. +`operator[]` has similar concurrency issues, and is not provided by +`boost::concurrent_flat_map` either. Instead, element access is done through +so-called _visitation functions_: + +[source,c++] +---- +m.visit(k, [](const auto& x) { // x is the element with key k (if it exists) + some_function(x); // use it +}); +---- + +The visitation function passed by the user (in this case, a lambda function) +is executed internally by `boost::concurrent_flat_map` in +a thread-safe manner, so it can access the element without worries about other +threads interfering in the process. On the other hand, a +visitation function can _not_ access the container itself: + +[source,c++] +---- +m.visit(k, [&](const auto& x) { + some_function(x, m.size()); // forbidden: m can't be accessed inside visitation +}); +---- + +Access to a different container is allowed, though: + +[source,c++] +---- +m.visit(k, [&](const auto& x) { + if (some_function(x)) { + m2.insert(x); // OK, m2 is a different boost::concurrent_flat_map + } +}); +---- + +But, in general, visitation functions should be as lightweight as possible to +reduce contention and increase parallelization. In some cases, moving heavy work +outside of visitation may be beneficial: + +[source,c++] +---- +std::optional o; +bool found = m.visit(k, [&](const auto& x) { + o = x; +}); +if (found) { + some_heavy_duty_function(*o); +} +---- + +Visitation is pervasive in the API provided by `boost::concurrent_flat_map`, and +many classical operations have visitation-enabled variations: + +[source,c++] +---- +m.insert_or_visit(x, [](auto& y) { + // if insertion failed because of an equivalent element y, + // do something with it, for instance: + ++y.second; // increment the mapped part of the element +}); +---- + +Note that in this last example the visitation function could actually _modify_ +the element: as a general rule, operations on a `boost::concurrent_flat_map` `m` +will grant visitation functions const/non-const access to the element depending on whether +`m` is const/non-const. Const access can be always be explicitly requested +by using `cvisit` overloads (for instance, `insert_or_cvisit`) and may result +in higher parallelization. Consult the xref:#concurrent_flat_map[reference] +for a complete list of available operations. + +== Whole-table visitation + +In the absence of iterators, `boost::concurrent_flat_map` provides `visit_all` +as an alternative way to process all the elements in the map: + +[source,c++] +---- +m.visit_all([](auto& x) { + x.second = 0; // reset the mapped part of the element +}); +---- + +In C++17 compilers implementing standard parallel algorithms, whole-table +visitation can be parallelized: + +[source,c++] +---- +m.visit_all(std::execution::par, [](auto& x) { // run in parallel + x.second = 0; // reset the mapped part of the element +}); +---- + +There is another whole-table visitation operation, `erase_if`: + +[source,c++] +---- +m.erase_if([](auto& x) { + return x.second == 0; // erase the elements whose mapped value is zero +}); +---- + +`erase_if` can also be parallelized. Note that, in order to increase efficiency, +these operations do not block the table during execution: this implies that elements +may be inserted, modified or erased by other threads during visitation. It is +advisable not to assume too much about the exact global state of a `boost::concurrent_flat_map` +at any point in your program. + +== Blocking operations + +``boost::concurrent_flat_map``s can be copied, assigned, cleared and merged just like any +Boost.Unordered container. Unlike most other operations, these are _blocking_, +that is, all other threads are prevented from accesing the tables involved while a copy, assignment, +clear or merge operation is in progress. Blocking is taken care of automatically by the library +and the user need not take any special precaution, but overall performance may be affected. + +Another blocking operation is _rehashing_, which happens explicitly via `rehash`/`reserve` +or during insertion when the table's load hits `max_load()`. As with non-concurrent hashmaps, +reserving space in advance of bulk insertions will generally speed up the process. diff --git a/doc/unordered/intro.adoc b/doc/unordered/intro.adoc index a809958f..eebe4878 100644 --- a/doc/unordered/intro.adoc +++ b/doc/unordered/intro.adoc @@ -216,3 +216,13 @@ for more details. There are other differences, which are listed in the <> section. + +== A concurrent hashmap + +Starting in Boost 1.83, Boost.Unordered provides `boost::concurrent_flat_map`, +a thread-safe hash table for high performance multithreaded scenarios. Although +it shares the internal data structure and most of the algorithms with Boost.Unordered +open-addressing `boost::unordered_flat_map`, ``boost::concurrent_flat_map``'s API departs significantly +from that of C++ unordered associative containers to make this table suitable for +concurrent usage. Consult the xref:#concurrent_flat_map_intro[dedicated tutorial] +for more information. From 69ba1c7c0017eb5d0864cfc0c0c11633802bc787 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Tue, 9 May 2023 19:53:56 +0200 Subject: [PATCH 229/327] editorial --- doc/unordered/concurrent_flat_map_intro.adoc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/unordered/concurrent_flat_map_intro.adoc b/doc/unordered/concurrent_flat_map_intro.adoc index 722e5ef4..3d701764 100644 --- a/doc/unordered/concurrent_flat_map_intro.adoc +++ b/doc/unordered/concurrent_flat_map_intro.adoc @@ -73,9 +73,10 @@ m.visit(k, [](const auto& x) { // x is the element with key k (if it exists) The visitation function passed by the user (in this case, a lambda function) is executed internally by `boost::concurrent_flat_map` in -a thread-safe manner, so it can access the element without worries about other -threads interfering in the process. On the other hand, a -visitation function can _not_ access the container itself: +a thread-safe manner, so it can access the element without worrying about other +threads interfering in the process. + +On the other hand, a visitation function can _not_ access the container itself: [source,c++] ---- @@ -110,7 +111,7 @@ if (found) { } ---- -Visitation is pervasive in the API provided by `boost::concurrent_flat_map`, and +Visitation is prominent in the API provided by `boost::concurrent_flat_map`, and many classical operations have visitation-enabled variations: [source,c++] From c2c34f96a3a6cb19df91448c88bf5874fd65ae5a Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 9 May 2023 13:33:50 -0700 Subject: [PATCH 230/327] Improve robustness of merge_tests to schedule merges without spurious wakeups and in a wider stride of insertions --- test/cfoa/merge_tests.cpp | 65 ++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/test/cfoa/merge_tests.cpp b/test/cfoa/merge_tests.cpp index 9b6bbb0e..2691c09d 100644 --- a/test/cfoa/merge_tests.cpp +++ b/test/cfoa/merge_tests.cpp @@ -106,26 +106,38 @@ namespace { std::condition_variable cv; std::atomic_bool done1{false}, done2{false}; std::atomic num_merges{0}; + std::atomic call_count{0}; + bool ready = false; auto const old_mc = +raii::move_constructor; BOOST_TEST_EQ(old_mc, 0u); - t1 = std::thread([&x1, &vals1, &l, &done1, &cv] { + t1 = std::thread([&x1, &vals1, &l, &done1, &cv, &ready, &m] { 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) { + + if (idx % (vals1.size() / 128) == 0) { + { + std::unique_lock lk(m); + ready = true; + } cv.notify_all(); std::this_thread::yield(); } } done1 = true; + { + std::unique_lock lk(m); + ready = true; + } + cv.notify_all(); }); - t2 = std::thread([&x2, &vals2, &l, &done2] { + t2 = std::thread([&x2, &vals2, &l, &done2, &cv, &m, &ready] { l.arrive_and_wait(); for (std::size_t idx = 0; idx < vals2.size(); ++idx) { @@ -137,27 +149,37 @@ namespace { } done2 = true; - }); - - t3 = std::thread([&x1, &x2, &m, &cv, &done1, &done2, &num_merges] { - while (x1.empty() && x2.empty()) { + { + std::unique_lock lk(m); + ready = true; } - - do { - { - std::unique_lock lk(m); - cv.wait(lk, [] { return true; }); - } - num_merges += x1.merge(x2); - std::this_thread::yield(); - num_merges += x2.merge(x1); - - } while (!done1 || !done2); - - BOOST_TEST(done1); - BOOST_TEST(done2); + cv.notify_all(); }); + t3 = std::thread( + [&x1, &x2, &m, &cv, &done1, &done2, &num_merges, &call_count, &ready] { + while (x1.empty() && x2.empty()) { + } + + do { + { + std::unique_lock lk(m); + cv.wait(lk, [&ready] { return ready; }); + ready = false; + } + + num_merges += x1.merge(x2); + std::this_thread::yield(); + num_merges += x2.merge(x1); + + call_count += 1; + + } while (!done1 || !done2); + + BOOST_TEST(done1); + BOOST_TEST(done2); + }); + t1.join(); t2.join(); t3.join(); @@ -166,6 +188,7 @@ namespace { // num merges is 0 most commonly in the cast of the limited_range // generator as both maps will contains keys from 0 to 99 BOOST_TEST_EQ(+raii::move_constructor, 2 * num_merges); + BOOST_TEST_GE(call_count, 1u); } x1.merge(x2); From c90b72a643d890ade2f98d2fec10c3f184c79165 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 9 May 2023 13:43:14 -0700 Subject: [PATCH 231/327] Squelch gcc self-move warning for version 13 --- test/cfoa/assign_tests.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/cfoa/assign_tests.cpp b/test/cfoa/assign_tests.cpp index 3936572d..a023cb54 100644 --- a/test/cfoa/assign_tests.cpp +++ b/test/cfoa/assign_tests.cpp @@ -18,6 +18,11 @@ #endif /* defined(__clang__) && defined(__has_warning) */ +#if defined(BOOST_GCC) && BOOST_GCC >= 130000 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wself-move" +#endif + test::seed_t initialize_seed{2762556623}; using test::default_generator; From 21afc698941bb920dd68404ad1bbf5d14fc6b6b6 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 10 May 2023 13:22:02 -0700 Subject: [PATCH 232/327] Add initial tests for rehash(), reserve() --- .../boost/unordered/concurrent_flat_map.hpp | 12 ++ test/Jamfile.v2 | 1 + test/cfoa/helpers.hpp | 3 +- test/cfoa/rehash_tests.cpp | 181 ++++++++++++++++++ 4 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 test/cfoa/rehash_tests.cpp diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index e3283a80..3f2bb793 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -742,6 +742,18 @@ namespace boost { /// Hash Policy /// + size_type bucket_count() const noexcept { + return table_.capacity(); + } + + float load_factor() const noexcept { return table_.load_factor(); } + float max_load_factor() const noexcept + { + return table_.max_load_factor(); + }; + void max_load_factor(float) {} + size_type max_load() const noexcept { return table_.max_load(); } + void rehash(size_type n) { table_.rehash(n); } void reserve(size_type n) { table_.reserve(n); } diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 57be13da..ce98085c 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -187,6 +187,7 @@ local CFOA_TESTS = clear_tests swap_tests merge_tests + rehash_tests ; for local test in $(CFOA_TESTS) diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index 59a35be7..116664a6 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -398,7 +399,7 @@ void check_raii_counts() raii::destructor); } -template void shuffle_values(std::vector v) +template void shuffle_values(std::vector& v) { std::random_device rd; std::mt19937 g(rd()); diff --git a/test/cfoa/rehash_tests.cpp b/test/cfoa/rehash_tests.cpp new file mode 100644 index 00000000..2d9d9fe1 --- /dev/null +++ b/test/cfoa/rehash_tests.cpp @@ -0,0 +1,181 @@ +// 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 + +using test::default_generator; +using test::limited_range; +using test::sequential; + +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; + +namespace { + test::seed_t initialize_seed{748775921}; + + UNORDERED_AUTO_TEST (rehash_no_insert) { + map_type x(0, hasher(1), key_equal(2), allocator_type(3)); + BOOST_TEST_EQ(x.bucket_count(), 0u); + + x.rehash(1024); + BOOST_TEST_GE(x.bucket_count(), 1024u); + + x.rehash(512); + BOOST_TEST_GE(x.bucket_count(), 512u); + BOOST_TEST_LT(x.bucket_count(), 1024u); + + x.rehash(0); + BOOST_TEST_EQ(x.bucket_count(), 0u); + } + + UNORDERED_AUTO_TEST (reserve_no_insert) { + using size_type = map_type::size_type; + + map_type x(0, hasher(1), key_equal(2), allocator_type(3)); + + auto f = [&x](double c) { + return static_cast(std::ceil(c / x.max_load_factor())); + }; + + BOOST_TEST_EQ(x.bucket_count(), f(0.0)); + + x.reserve(1024); + BOOST_TEST_GE(x.bucket_count(), f(1024.0)); + + x.reserve(512); + BOOST_TEST_GE(x.bucket_count(), f(512.0)); + BOOST_TEST_LT(x.bucket_count(), f(1024.0)); + + x.reserve(0); + BOOST_TEST_EQ(x.bucket_count(), f(0.0)); + } + + template + void insert_and_erase_with_rehash(G gen, test::random_generator rg) + { + auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); }); + + auto erase_indices = std::vector(vals1.size()); + for (std::size_t idx = 0; idx < erase_indices.size(); ++idx) { + erase_indices[idx] = idx; + } + shuffle_values(erase_indices); + + auto ref_map = boost::unordered_flat_map(); + ref_map.insert(vals1.begin(), vals1.end()); + + { + raii::reset_counts(); + + map_type x(0, hasher(1), key_equal(2), 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 call_count{0}; + bool ready = false; + + auto const old_mc = +raii::move_constructor; + BOOST_TEST_EQ(old_mc, 0u); + + t1 = std::thread([&x, &vals1, &l, &done1, &cv, &ready, &m] { + l.arrive_and_wait(); + + for (std::size_t idx = 0; idx < vals1.size(); ++idx) { + auto const& val = vals1[idx]; + x.insert(val); + + if (idx % (vals1.size() / 128) == 0) { + { + std::unique_lock lk(m); + ready = true; + } + cv.notify_all(); + std::this_thread::yield(); + } + } + + done1 = true; + { + std::unique_lock lk(m); + ready = true; + } + cv.notify_all(); + }); + + t2 = + std::thread([&x, &vals1, &erase_indices, &l, &done2, &cv, &m, &ready] { + l.arrive_and_wait(); + + for (std::size_t idx = 0; idx < erase_indices.size(); ++idx) { + auto const& val = vals1[erase_indices[idx]]; + x.erase(val.first); + if (idx % 100 == 0) { + std::this_thread::yield(); + } + } + + done2 = true; + { + std::unique_lock lk(m); + ready = true; + } + cv.notify_all(); + }); + + t3 = + std::thread([&x, &vals1, &m, &cv, &done1, &done2, &call_count, &ready] { + while (x.empty()) { + } + + do { + { + std::unique_lock lk(m); + cv.wait(lk, [&ready] { return ready; }); + ready = false; + } + + auto const bc = static_cast(rand()) % vals1.size(); + x.rehash(bc); + call_count += 1; + + std::this_thread::yield(); + } while (!done1 || !done2); + + BOOST_TEST(done1); + BOOST_TEST(done2); + }); + + t1.join(); + t2.join(); + t3.join(); + + BOOST_TEST_GT(call_count, 1u); + + test_fuzzy_matches_reference(x, ref_map, rg); + } + + check_raii_counts(); + } +} // namespace + +// clang-format off +UNORDERED_TEST( + insert_and_erase_with_rehash, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) +// clang-format on + +RUN_TESTS() From 80a1904d92dde709632242945c7fde0d061908c9 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 11 May 2023 08:39:16 -0700 Subject: [PATCH 233/327] Fix call_count check in rehash_tests --- test/cfoa/rehash_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cfoa/rehash_tests.cpp b/test/cfoa/rehash_tests.cpp index 2d9d9fe1..c59c7717 100644 --- a/test/cfoa/rehash_tests.cpp +++ b/test/cfoa/rehash_tests.cpp @@ -162,7 +162,7 @@ namespace { t2.join(); t3.join(); - BOOST_TEST_GT(call_count, 1u); + BOOST_TEST_GE(call_count, 1u); test_fuzzy_matches_reference(x, ref_map, rg); } From bcf5d0cf13f7d4818a673d1b14765b616484dc5b Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 11 May 2023 08:39:29 -0700 Subject: [PATCH 234/327] Attempt to disable extraneous runs on CI --- .drone.jsonnet | 2 +- .github/workflows/ci.yml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index edd28686..c8b03a84 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -6,7 +6,7 @@ local library = "unordered"; local triggers = { - branch: [ "master", "develop", "feature/*", "bugfix/*", "fix/*", "pr/*" ] + branch: [ "master", "develop", "bugfix/*", "fix/*", "pr/*" ] }; local ubsan = { UBSAN: '1', UBSAN_OPTIONS: 'print_stacktrace=1' }; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 973e9e27..2aba3970 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,6 @@ on: - master - develop - bugfix/** - - feature/** - fix/** - pr/** From 511e2b3272be12d89d421852ea4d6c6a139a7619 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 12 May 2023 11:24:20 +0200 Subject: [PATCH 235/327] refactored to provide equality comparison from table_core --- .../boost/unordered/concurrent_flat_map.hpp | 19 ++++ .../unordered/detail/foa/concurrent_table.hpp | 23 ++-- include/boost/unordered/detail/foa/core.hpp | 102 +++++++++++++++--- include/boost/unordered/detail/foa/table.hpp | 55 ++-------- .../boost/unordered/unordered_flat_map.hpp | 43 +++----- .../boost/unordered/unordered_flat_set.hpp | 43 +++----- .../boost/unordered/unordered_node_map.hpp | 43 +++----- .../boost/unordered/unordered_node_set.hpp | 43 +++----- 8 files changed, 200 insertions(+), 171 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 3f2bb793..3679df58 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -766,6 +766,25 @@ namespace boost { hasher hash_function() const { return table_.hash_function(); } key_equal key_eq() const { return table_.key_eq(); } + + /// Equality + /// + + template + friend bool operator==( + concurrent_flat_map const& lhs, + concurrent_flat_map const& rhs) + { + return lhs.table_ == rhs.table_; + } + + template + friend bool operator!=( + concurrent_flat_map const& lhs, + concurrent_flat_map const& rhs) + { + return !(lhs == rhs); + } }; } // namespace unordered } // namespace boost diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 7eee515e..1b9ade9c 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -697,13 +697,13 @@ public: boost::ignore_unused(); auto lck=exclusive_access(*this,x); - size_type s=unprotected_size(); + size_type s=super::size(); x.super2::for_all_elements( /* super2::for_all_elements -> unprotected */ [&,this](group_type* pg,unsigned int n,element_type* p){ typename merge_table_type::erase_on_exit e{x,pg,n,p}; if(!unprotected_emplace(type_policy::move(*p)))e.rollback(); }); - return size_type{unprotected_size()-s}; + return size_type{super::size()-s}; } template @@ -773,6 +773,17 @@ public: return x.erase_if(std::forward(pr)); } + friend bool operator==(const concurrent_table& x,const concurrent_table& y) + { + auto lck=exclusive_access(x,y); + return static_cast(x)==static_cast(y); + } + + friend bool operator!=(const concurrent_table& x,const concurrent_table& y) + { + return !(x==y); + } + private: using mutex_type=cacheline_protected; using multimutex_type=multimutex; // TODO: adapt 128 to the machine @@ -806,14 +817,14 @@ private: return exclusive_lock_guard{mutexes}; } - inline exclusive_bilock_guard exclusive_access( + static inline exclusive_bilock_guard exclusive_access( const concurrent_table& x,const concurrent_table& y) { return {x.mutexes,y.mutexes}; } template - inline exclusive_bilock_guard exclusive_access( + static inline exclusive_bilock_guard exclusive_access( const concurrent_table& x, const concurrent_table& y) { @@ -1079,9 +1090,7 @@ private: auto hash=this->hash_for(k); auto pos0=this->position_for(hash); - // TODO: could be made more efficient (nonconcurrent scenario) - if(unprotected_visit( - group_shared{},k,pos0,hash,[](const value_type&){}))return false; + if(!this->find(k,pos0,hash))return false; if(BOOST_LIKELY(this->size_ml)){ this->unchecked_emplace_at(pos0,hash,std::forward(args)...); diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 7e47568f..295f580f 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1097,9 +1097,14 @@ static constexpr float mlf=0.875f; template struct table_locator { - Group *pg; - unsigned int n; - Element *p; + table_locator()=default; + table_locator(Group* pg_,unsigned int n_,Element* p_):pg{pg_},n{n_},p{p_}{} + + explicit operator bool()const noexcept{return p!=nullptr;} + + Group *pg=nullptr; + unsigned int n=0; + Element *p=nullptr; }; struct try_emplace_args_t{}; @@ -1425,6 +1430,52 @@ public: recover_slot(pc); } + template + BOOST_FORCEINLINE locator find(const Key& x)const + { + auto hash=hash_for(x); + return find(x,position_for(hash),hash); + } + +#if defined(BOOST_MSVC) +/* warning: forcing value to bool 'true' or 'false' in bool(pred()...) */ +#pragma warning(push) +#pragma warning(disable:4800) +#endif + + template + BOOST_FORCEINLINE locator find( + const Key& x,std::size_t pos0,std::size_t hash)const + { + prober pb(pos0); + do{ + auto pos=pb.get(); + auto pg=arrays.groups+pos; + auto mask=pg->match(hash); + if(mask){ + BOOST_UNORDERED_ASSUME(arrays.elements!=nullptr); + auto p=arrays.elements+pos*N; + prefetch_elements(p); + do{ + auto n=unchecked_countr_zero(mask); + if(BOOST_LIKELY(bool(pred()(x,key_from(p[n]))))){ + return {pg,n,p+n}; + } + mask&=mask-1; + }while(mask); + } + if(BOOST_LIKELY(pg->is_not_overflowed(hash))){ + return {}; + } + } + while(BOOST_LIKELY(pb.next(arrays.groups_size_mask))); + return {}; + } + +#if defined(BOOST_MSVC) +#pragma warning(pop) /* C4800 */ +#endif + void swap(table_core& x) noexcept( alloc_traits::propagate_on_container_swap::value|| @@ -1503,6 +1554,23 @@ public: rehash(std::size_t(std::ceil(float(n)/mlf))); } + friend bool operator==(const table_core& x,const table_core& y) + { + return + x.size()==y.size()&& + x.for_all_elements_while([&](element_type* p){ + auto loc=y.find(key_from(*p)); + return loc&& + const_cast(type_policy::value_from(*p))== + const_cast(type_policy::value_from(*loc.p)); + }); + } + + friend bool operator!=(const table_core& x,const table_core& y) + { + return !(x==y); + } + struct clear_on_exit { ~clear_on_exit(){x.clear();} @@ -1683,34 +1751,36 @@ public: } template - void for_all_elements_while(F f)const + bool for_all_elements_while(F f)const { - for_all_elements_while(arrays,f); + return for_all_elements_while(arrays,f); } template static auto for_all_elements_while(const arrays_type& arrays_,F f) - ->decltype(f(nullptr),void()) + ->decltype(f(nullptr),bool()) { - for_all_elements_while( + return for_all_elements_while( arrays_,[&](group_type*,unsigned int,element_type* p){return f(p);}); } template static auto for_all_elements_while(const arrays_type& arrays_,F f) - ->decltype(f(nullptr,0,nullptr),void()) + ->decltype(f(nullptr,0,nullptr),bool()) { auto p=arrays_.elements; - if(!p){return;} - for(auto pg=arrays_.groups,last=pg+arrays_.groups_size_mask+1; - pg!=last;++pg,p+=N){ - auto mask=match_really_occupied(pg,last); - while(mask){ - auto n=unchecked_countr_zero(mask); - if(!f(pg,n,p+n))return; - mask&=mask-1; + if(p){ + for(auto pg=arrays_.groups,last=pg+arrays_.groups_size_mask+1; + pg!=last;++pg,p+=N){ + auto mask=match_really_occupied(pg,last); + while(mask){ + auto n=unchecked_countr_zero(mask); + if(!f(pg,n,p+n))return false; + mask&=mask-1; + } } } + return true; } arrays_type arrays; diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 1946bd9f..554e762a 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -404,8 +404,7 @@ public: template BOOST_FORCEINLINE iterator find(const Key& x) { - auto hash=this->hash_for(x); - return find_impl(x,this->position_for(hash),hash); + return make_iterator(super::find(x)); } template @@ -440,6 +439,13 @@ public: return std::size_t(s-x.size()); } + friend bool operator==(const table& x,const table& y) + { + return static_cast(x)==static_cast(y); + } + + friend bool operator!=(const table& x,const table& y){return !(x==y);} + private: struct erase_on_exit { @@ -458,55 +464,16 @@ private: return {l.pg,l.n,l.p}; } -#if defined(BOOST_MSVC) -/* warning: forcing value to bool 'true' or 'false' in bool(pred()...) */ -#pragma warning(push) -#pragma warning(disable:4800) -#endif - - template - BOOST_FORCEINLINE iterator find_impl( - const Key& x,std::size_t pos0,std::size_t hash)const - { - prober pb(pos0); - do{ - auto pos=pb.get(); - auto pg=this->arrays.groups+pos; - auto mask=pg->match(hash); - if(mask){ - BOOST_UNORDERED_ASSUME(this->arrays.elements!=nullptr); - auto p=this->arrays.elements+pos*N; - this->prefetch_elements(p); - do{ - auto n=unchecked_countr_zero(mask); - if(BOOST_LIKELY(bool(this->pred()(x,this->key_from(p[n]))))){ - return {pg,n,p+n}; - } - mask&=mask-1; - }while(mask); - } - if(BOOST_LIKELY(pg->is_not_overflowed(hash))){ - return {}; /* end() */ - } - } - while(BOOST_LIKELY(pb.next(this->arrays.groups_size_mask))); - return {}; /* end() */ - } - -#if defined(BOOST_MSVC) -#pragma warning(pop) /* C4800 */ -#endif - template BOOST_FORCEINLINE std::pair emplace_impl(Args&&... args) { const auto &k=this->key_from(std::forward(args)...); auto hash=this->hash_for(k); auto pos0=this->position_for(hash); - auto it=find_impl(k,pos0,hash); + auto loc=super::find(k,pos0,hash); - if(it!=end()){ - return {it,false}; + if(loc){ + return {make_iterator(loc),false}; } if(BOOST_LIKELY(this->size_ml)){ return { diff --git a/include/boost/unordered/unordered_flat_map.hpp b/include/boost/unordered/unordered_flat_map.hpp index a7b6482b..8a844dd3 100644 --- a/include/boost/unordered/unordered_flat_map.hpp +++ b/include/boost/unordered/unordered_flat_map.hpp @@ -695,35 +695,26 @@ namespace boost { hasher hash_function() const { return table_.hash_function(); } key_equal key_eq() const { return table_.key_eq(); } - }; - template - bool operator==( - unordered_flat_map const& lhs, - unordered_flat_map const& rhs) - { - if (&lhs == &rhs) { - return true; + /// Equality + /// + + template + friend bool operator==( + unordered_flat_map const& lhs, + unordered_flat_map const& rhs) + { + return lhs.table_ == rhs.table_; } - return (lhs.size() == rhs.size()) && ([&] { - for (auto const& kvp : lhs) { - auto pos = rhs.find(kvp.first); - if ((pos == rhs.end()) || (*pos != kvp)) { - return false; - } - } - return true; - })(); - } - - template - bool operator!=( - unordered_flat_map const& lhs, - unordered_flat_map const& rhs) - { - return !(lhs == rhs); - } + template + friend bool operator!=( + unordered_flat_map const& lhs, + unordered_flat_map const& rhs) + { + return !(lhs == rhs); + } + }; template void swap(unordered_flat_map& lhs, diff --git a/include/boost/unordered/unordered_flat_set.hpp b/include/boost/unordered/unordered_flat_set.hpp index 4562b1e5..b9ded1ff 100644 --- a/include/boost/unordered/unordered_flat_set.hpp +++ b/include/boost/unordered/unordered_flat_set.hpp @@ -492,35 +492,26 @@ namespace boost { hasher hash_function() const { return table_.hash_function(); } key_equal key_eq() const { return table_.key_eq(); } - }; - template - bool operator==( - unordered_flat_set const& lhs, - unordered_flat_set const& rhs) - { - if (&lhs == &rhs) { - return true; + /// Equality + /// + + template + friend bool operator==( + unordered_flat_set const& lhs, + unordered_flat_set const& rhs) + { + return lhs.table_ == rhs.table_; } - return (lhs.size() == rhs.size()) && ([&] { - for (auto const& key : lhs) { - auto pos = rhs.find(key); - if ((pos == rhs.end()) || (key != *pos)) { - return false; - } - } - return true; - })(); - } - - template - bool operator!=( - unordered_flat_set const& lhs, - unordered_flat_set const& rhs) - { - return !(lhs == rhs); - } + template + friend bool operator!=( + unordered_flat_set const& lhs, + unordered_flat_set const& rhs) + { + return !(lhs == rhs); + } + }; template void swap(unordered_flat_set& lhs, diff --git a/include/boost/unordered/unordered_node_map.hpp b/include/boost/unordered/unordered_node_map.hpp index 6c598354..513eab95 100644 --- a/include/boost/unordered/unordered_node_map.hpp +++ b/include/boost/unordered/unordered_node_map.hpp @@ -847,35 +847,26 @@ namespace boost { hasher hash_function() const { return table_.hash_function(); } key_equal key_eq() const { return table_.key_eq(); } - }; - template - bool operator==( - unordered_node_map const& lhs, - unordered_node_map const& rhs) - { - if (&lhs == &rhs) { - return true; + /// Equality + /// + + template + friend bool operator==( + unordered_node_map const& lhs, + unordered_node_map const& rhs) + { + return lhs.table_ == rhs.table_; } - return (lhs.size() == rhs.size()) && ([&] { - for (auto const& kvp : lhs) { - auto pos = rhs.find(kvp.first); - if ((pos == rhs.end()) || (*pos != kvp)) { - return false; - } - } - return true; - })(); - } - - template - bool operator!=( - unordered_node_map const& lhs, - unordered_node_map const& rhs) - { - return !(lhs == rhs); - } + template + friend bool operator!=( + unordered_node_map const& lhs, + unordered_node_map const& rhs) + { + return !(lhs == rhs); + } + }; template void swap(unordered_node_map& lhs, diff --git a/include/boost/unordered/unordered_node_set.hpp b/include/boost/unordered/unordered_node_set.hpp index 2e2a9dd4..3758802e 100644 --- a/include/boost/unordered/unordered_node_set.hpp +++ b/include/boost/unordered/unordered_node_set.hpp @@ -631,35 +631,26 @@ namespace boost { hasher hash_function() const { return table_.hash_function(); } key_equal key_eq() const { return table_.key_eq(); } - }; - template - bool operator==( - unordered_node_set const& lhs, - unordered_node_set const& rhs) - { - if (&lhs == &rhs) { - return true; + /// Equality + /// + + template + friend bool operator==( + unordered_node_set const& lhs, + unordered_node_set const& rhs) + { + return lhs.table_ == rhs.table_; } - return (lhs.size() == rhs.size()) && ([&] { - for (auto const& key : lhs) { - auto pos = rhs.find(key); - if ((pos == rhs.end()) || (key != *pos)) { - return false; - } - } - return true; - })(); - } - - template - bool operator!=( - unordered_node_set const& lhs, - unordered_node_set const& rhs) - { - return !(lhs == rhs); - } + template + friend bool operator!=( + unordered_node_set const& lhs, + unordered_node_set const& rhs) + { + return !(lhs == rhs); + } + }; template void swap(unordered_node_set& lhs, From dacc1c8234e2908cff57e4958982fe023b089980 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 12 May 2023 11:41:50 +0200 Subject: [PATCH 236/327] made operator[==|!=] non-templated --- include/boost/unordered/concurrent_flat_map.hpp | 8 ++------ include/boost/unordered/unordered_flat_map.hpp | 8 ++------ include/boost/unordered/unordered_flat_set.hpp | 8 ++------ include/boost/unordered/unordered_node_map.hpp | 8 ++------ include/boost/unordered/unordered_node_set.hpp | 8 ++------ 5 files changed, 10 insertions(+), 30 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 3679df58..d17942cf 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -770,18 +770,14 @@ namespace boost { /// Equality /// - template friend bool operator==( - concurrent_flat_map const& lhs, - concurrent_flat_map const& rhs) + concurrent_flat_map const& lhs, concurrent_flat_map const& rhs) { return lhs.table_ == rhs.table_; } - template friend bool operator!=( - concurrent_flat_map const& lhs, - concurrent_flat_map const& rhs) + concurrent_flat_map const& lhs, concurrent_flat_map const& rhs) { return !(lhs == rhs); } diff --git a/include/boost/unordered/unordered_flat_map.hpp b/include/boost/unordered/unordered_flat_map.hpp index 8a844dd3..633f466b 100644 --- a/include/boost/unordered/unordered_flat_map.hpp +++ b/include/boost/unordered/unordered_flat_map.hpp @@ -699,18 +699,14 @@ namespace boost { /// Equality /// - template friend bool operator==( - unordered_flat_map const& lhs, - unordered_flat_map const& rhs) + unordered_flat_map const& lhs, unordered_flat_map const& rhs) { return lhs.table_ == rhs.table_; } - template friend bool operator!=( - unordered_flat_map const& lhs, - unordered_flat_map const& rhs) + unordered_flat_map const& lhs, unordered_flat_map const& rhs) { return !(lhs == rhs); } diff --git a/include/boost/unordered/unordered_flat_set.hpp b/include/boost/unordered/unordered_flat_set.hpp index b9ded1ff..eb0aa728 100644 --- a/include/boost/unordered/unordered_flat_set.hpp +++ b/include/boost/unordered/unordered_flat_set.hpp @@ -496,18 +496,14 @@ namespace boost { /// Equality /// - template friend bool operator==( - unordered_flat_set const& lhs, - unordered_flat_set const& rhs) + unordered_flat_set const& lhs, unordered_flat_set const& rhs) { return lhs.table_ == rhs.table_; } - template friend bool operator!=( - unordered_flat_set const& lhs, - unordered_flat_set const& rhs) + unordered_flat_set const& lhs, unordered_flat_set const& rhs) { return !(lhs == rhs); } diff --git a/include/boost/unordered/unordered_node_map.hpp b/include/boost/unordered/unordered_node_map.hpp index 513eab95..910996c5 100644 --- a/include/boost/unordered/unordered_node_map.hpp +++ b/include/boost/unordered/unordered_node_map.hpp @@ -851,18 +851,14 @@ namespace boost { /// Equality /// - template friend bool operator==( - unordered_node_map const& lhs, - unordered_node_map const& rhs) + unordered_node_map const& lhs, unordered_node_map const& rhs) { return lhs.table_ == rhs.table_; } - template friend bool operator!=( - unordered_node_map const& lhs, - unordered_node_map const& rhs) + unordered_node_map const& lhs, unordered_node_map const& rhs) { return !(lhs == rhs); } diff --git a/include/boost/unordered/unordered_node_set.hpp b/include/boost/unordered/unordered_node_set.hpp index 3758802e..f9f3762d 100644 --- a/include/boost/unordered/unordered_node_set.hpp +++ b/include/boost/unordered/unordered_node_set.hpp @@ -635,18 +635,14 @@ namespace boost { /// Equality /// - template friend bool operator==( - unordered_node_set const& lhs, - unordered_node_set const& rhs) + unordered_node_set const& lhs, unordered_node_set const& rhs) { return lhs.table_ == rhs.table_; } - template friend bool operator!=( - unordered_node_set const& lhs, - unordered_node_set const& rhs) + unordered_node_set const& lhs, unordered_node_set const& rhs) { return !(lhs == rhs); } From d615a08f76fae442984009d45389e959065829b9 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 12 May 2023 12:33:27 +0200 Subject: [PATCH 237/327] made operator [==|!=] templated again to accommodate fwd declarations --- .../boost/unordered/concurrent_flat_map.hpp | 36 +++++++++++-------- .../boost/unordered/unordered_flat_map.hpp | 36 +++++++++++-------- .../boost/unordered/unordered_flat_set.hpp | 36 +++++++++++-------- .../boost/unordered/unordered_node_map.hpp | 36 +++++++++++-------- .../boost/unordered/unordered_node_set.hpp | 36 +++++++++++-------- 5 files changed, 105 insertions(+), 75 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index d17942cf..4d5d6d64 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -148,6 +148,11 @@ namespace boost { detail::foa::concurrent_table table_; + template + bool friend operator==( + concurrent_flat_map const& lhs, + concurrent_flat_map const& rhs); + public: using key_type = Key; using mapped_type = T; @@ -766,22 +771,23 @@ namespace boost { hasher hash_function() const { return table_.hash_function(); } key_equal key_eq() const { return table_.key_eq(); } - - /// Equality - /// - - friend bool operator==( - concurrent_flat_map const& lhs, concurrent_flat_map const& rhs) - { - return lhs.table_ == rhs.table_; - } - - friend bool operator!=( - concurrent_flat_map const& lhs, concurrent_flat_map const& rhs) - { - return !(lhs == rhs); - } }; + + template + bool operator==( + concurrent_flat_map const& lhs, + concurrent_flat_map const& rhs) + { + return lhs.table_ == rhs.table_; + } + + template + bool operator!=( + concurrent_flat_map const& lhs, + concurrent_flat_map const& rhs) + { + return !(lhs == rhs); + } } // namespace unordered } // namespace boost diff --git a/include/boost/unordered/unordered_flat_map.hpp b/include/boost/unordered/unordered_flat_map.hpp index 633f466b..23a13438 100644 --- a/include/boost/unordered/unordered_flat_map.hpp +++ b/include/boost/unordered/unordered_flat_map.hpp @@ -100,6 +100,11 @@ namespace boost { table_type table_; + template + bool friend operator==( + unordered_flat_map const& lhs, + unordered_flat_map const& rhs); + template typename unordered_flat_map::size_type friend erase_if( unordered_flat_map& set, Pred pred); @@ -695,23 +700,24 @@ namespace boost { hasher hash_function() const { return table_.hash_function(); } key_equal key_eq() const { return table_.key_eq(); } - - /// Equality - /// - - friend bool operator==( - unordered_flat_map const& lhs, unordered_flat_map const& rhs) - { - return lhs.table_ == rhs.table_; - } - - friend bool operator!=( - unordered_flat_map const& lhs, unordered_flat_map const& rhs) - { - return !(lhs == rhs); - } }; + template + bool operator==( + unordered_flat_map const& lhs, + unordered_flat_map const& rhs) + { + return lhs.table_ == rhs.table_; + } + + template + bool operator!=( + unordered_flat_map const& lhs, + unordered_flat_map const& rhs) + { + return !(lhs == rhs); + } + template void swap(unordered_flat_map& lhs, unordered_flat_map& rhs) diff --git a/include/boost/unordered/unordered_flat_set.hpp b/include/boost/unordered/unordered_flat_set.hpp index eb0aa728..d81e1082 100644 --- a/include/boost/unordered/unordered_flat_set.hpp +++ b/include/boost/unordered/unordered_flat_set.hpp @@ -69,6 +69,11 @@ namespace boost { table_type table_; + template + bool friend operator==( + unordered_flat_set const& lhs, + unordered_flat_set const& rhs); + template typename unordered_flat_set::size_type friend erase_if( unordered_flat_set& set, Pred pred); @@ -492,23 +497,24 @@ namespace boost { hasher hash_function() const { return table_.hash_function(); } key_equal key_eq() const { return table_.key_eq(); } - - /// Equality - /// - - friend bool operator==( - unordered_flat_set const& lhs, unordered_flat_set const& rhs) - { - return lhs.table_ == rhs.table_; - } - - friend bool operator!=( - unordered_flat_set const& lhs, unordered_flat_set const& rhs) - { - return !(lhs == rhs); - } }; + template + bool operator==( + unordered_flat_set const& lhs, + unordered_flat_set const& rhs) + { + return lhs.table_ == rhs.table_; + } + + template + bool operator!=( + unordered_flat_set const& lhs, + unordered_flat_set const& rhs) + { + return !(lhs == rhs); + } + template void swap(unordered_flat_set& lhs, unordered_flat_set& rhs) diff --git a/include/boost/unordered/unordered_node_map.hpp b/include/boost/unordered/unordered_node_map.hpp index 910996c5..8d32ba0a 100644 --- a/include/boost/unordered/unordered_node_map.hpp +++ b/include/boost/unordered/unordered_node_map.hpp @@ -187,6 +187,11 @@ namespace boost { table_type table_; + template + bool friend operator==( + unordered_node_map const& lhs, + unordered_node_map const& rhs); + template typename unordered_node_map::size_type friend erase_if( unordered_node_map& set, Pred pred); @@ -847,23 +852,24 @@ namespace boost { hasher hash_function() const { return table_.hash_function(); } key_equal key_eq() const { return table_.key_eq(); } - - /// Equality - /// - - friend bool operator==( - unordered_node_map const& lhs, unordered_node_map const& rhs) - { - return lhs.table_ == rhs.table_; - } - - friend bool operator!=( - unordered_node_map const& lhs, unordered_node_map const& rhs) - { - return !(lhs == rhs); - } }; + template + bool operator==( + unordered_node_map const& lhs, + unordered_node_map const& rhs) + { + return lhs.table_ == rhs.table_; + } + + template + bool operator!=( + unordered_node_map const& lhs, + unordered_node_map const& rhs) + { + return !(lhs == rhs); + } + template void swap(unordered_node_map& lhs, unordered_node_map& rhs) diff --git a/include/boost/unordered/unordered_node_set.hpp b/include/boost/unordered/unordered_node_set.hpp index f9f3762d..87d8677f 100644 --- a/include/boost/unordered/unordered_node_set.hpp +++ b/include/boost/unordered/unordered_node_set.hpp @@ -143,6 +143,11 @@ namespace boost { table_type table_; + template + bool friend operator==( + unordered_node_set const& lhs, + unordered_node_set const& rhs); + template typename unordered_node_set::size_type friend erase_if( unordered_node_set& set, Pred pred); @@ -631,23 +636,24 @@ namespace boost { hasher hash_function() const { return table_.hash_function(); } key_equal key_eq() const { return table_.key_eq(); } - - /// Equality - /// - - friend bool operator==( - unordered_node_set const& lhs, unordered_node_set const& rhs) - { - return lhs.table_ == rhs.table_; - } - - friend bool operator!=( - unordered_node_set const& lhs, unordered_node_set const& rhs) - { - return !(lhs == rhs); - } }; + template + bool operator==( + unordered_node_set const& lhs, + unordered_node_set const& rhs) + { + return lhs.table_ == rhs.table_; + } + + template + bool operator!=( + unordered_node_set const& lhs, + unordered_node_set const& rhs) + { + return !(lhs == rhs); + } + template void swap(unordered_node_set& lhs, unordered_node_set& rhs) From 81480feeb452318f93871a0fd0b5ad9cc75078c5 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 12 May 2023 12:45:31 +0200 Subject: [PATCH 238/327] fixed regression at unprotected_emplace --- include/boost/unordered/detail/foa/concurrent_table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 1b9ade9c..d66f090d 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -1090,7 +1090,7 @@ private: auto hash=this->hash_for(k); auto pos0=this->position_for(hash); - if(!this->find(k,pos0,hash))return false; + if(this->find(k,pos0,hash))return false; if(BOOST_LIKELY(this->size_ml)){ this->unchecked_emplace_at(pos0,hash,std::forward(args)...); From 4b4db3dfb34711793b48a5fa086bccb0f3fd79c8 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 13 May 2023 10:00:35 +0200 Subject: [PATCH 239/327] fixed links --- doc/unordered/buckets.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/unordered/buckets.adoc b/doc/unordered/buckets.adoc index cf563f8c..74066392 100644 --- a/doc/unordered/buckets.adoc +++ b/doc/unordered/buckets.adoc @@ -250,7 +250,7 @@ A more detailed description of Boost.Unordered's closed-addressing implementatio given in an https://bannalia.blogspot.com/2022/06/advancing-state-of-art-for.html[external article]. For more information on implementation rationale, read the -xref:#rationale_boostunordered_multiset_and_boostunordered_multimap[corresponding section]. +xref:#rationale_closed_addressing_containers[corresponding section]. == Open Addressing Implementation @@ -312,4 +312,4 @@ A more detailed description of Boost.Unordered's open-addressing implementation given in an https://bannalia.blogspot.com/2022/11/inside-boostunorderedflatmap.html[external article]. For more information on implementation rationale, read the -xref:#rationale_boostunordered_flat_set_and_boostunordered_flat_map[corresponding section]. +xref:#rationale_open_addresing_containers[corresponding section]. From add01e2dfd55ad953e7b62d6149b8f294d201cfe Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 13 May 2023 19:28:43 +0200 Subject: [PATCH 240/327] added compliance section for concurrent hashmap --- doc/unordered/compliance.adoc | 57 +++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/doc/unordered/compliance.adoc b/doc/unordered/compliance.adoc index bc53e36e..cd93770b 100644 --- a/doc/unordered/compliance.adoc +++ b/doc/unordered/compliance.adoc @@ -144,4 +144,61 @@ The main differences with C++ unordered associative containers are: ** Pointer stability is not kept under rehashing. ** There is no API for node extraction/insertion. +== Concurrent Hashmap + +There is currently no specification in the C++ standard for this or any other concurrent +data structure. `boost::concurrent_flat_map` takes the same template parameters as `std::unordered_map` +and all the maps provided by Boost.Unordered, and its API is modelled after that of +`boost::unordered_flat_map` with the crucial difference that iterators are not provided +due to their inherent problems in concurrent scenarios (high contention, prone to deadlocking): +so, `boost::concurrent_flat_map` is technically not a +https://en.cppreference.com/w/cpp/named_req/Container[Container^], although +it meets all the requirements of https://en.cppreference.com/w/cpp/named_req/AllocatorAwareContainer[AllocatorAware^] +containers except those implying iterators. + +In a non-concurrent unordered container, iterators serve two main purposes: + +* Access to an element previously located via lookup. +* Container traversal. + +In place of iterators, `boost::unordered_flat_map` uses _internal visitation_ +facilities as a thread-safe substitute. Classical operations returning an iterator to an +element already existing in the container, like for instance: + +[source,c++] +---- +iterator find(const key_type& k); +std::pair insert(const value_type& obj); +---- + +are transformed to accept a _visitation function_ that is passed such element: + +[source,c++] +---- +template size_t visit(const key_type& k, F f); +template bool insert_or_visit(const value_type& obj, F f); +---- + +(In the second case `f` is only invoked if there's an equivalent element +to `obj` in the table, not if insertion is successful). Container traversal +is served by: + +[source,c++] +---- +template size_t visit_all(F f); +---- + +of which there are parallelized version in C++17 compilers with parallel +algorithm support. In general, the interface of `boost::concurrent_flat_map` +is derived from that of `boost::unordered_flat_map` by a fairly straightforward +process of replacing iterators with visitation where applicable. If +`iterator` and `const_iterator` provide mutable and const access to elements, +respectively, here visitation is granted mutable or const access depending on +the constness of the member function used (there are also `*cvisit` overloads for +explicit const visitation). + +The one notable operation not provided is `operator[]`/`at`, which can be +replaced, if in a more convoluted manner, by +xref:#concurrent_flat_map_try_emplace_or_cvisit[`try_emplace_or_visit`]. + //- From 69ee0039e0277bbd5438179d28a980ee6813eb59 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 13 May 2023 19:29:08 +0200 Subject: [PATCH 241/327] added implementation rationale for concurrent hashmap --- doc/unordered/rationale.adoc | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/doc/unordered/rationale.adoc b/doc/unordered/rationale.adoc index 50758164..15a6aa9d 100644 --- a/doc/unordered/rationale.adoc +++ b/doc/unordered/rationale.adoc @@ -108,7 +108,7 @@ When using a hash function directly suitable for open addressing, post-mixing ca === Platform interoperability The observable behavior of `boost::unordered_flat_set`/`unordered_node_set` and `boost::unordered_flat_map`/`unordered_node_map` is deterministically -identical across different compilers as long as their ``std::size_type``s are the same size and the user-provided +identical across different compilers as long as their ``std::size_t``s are the same size and the user-provided hash function and equality predicate are also interoperable —this includes elements being ordered in exactly the same way for the same sequence of operations. @@ -117,3 +117,25 @@ Although the implementation internally uses SIMD technologies, such as https://e and https://en.wikipedia.org/wiki/ARM_architecture_family#Advanced_SIMD_(NEON)[Neon^], when available, this does not affect interoperatility. For instance, the behavior is the same for Visual Studio on an x64-mode Intel CPU with SSE2 and for GCC on an IBM s390x without any supported SIMD technology. + +== Concurrent Hashmap + +The same data structure used by Boost.Unordered open-addressing containers has been chosen +also as the foundation of `boost::concurrent_flat_map`: + +* Open-addressing is faster than closed-addressing alternatives, both in non-concurrent and +concurrent scenarios. +* Open-addressing layouts are eminently suitable for concurrent access and modification +with minimal locking. In particular, the metadata array can be used for implementations of +lookup that are lock-free up to the last step of actual element comparison. +* Layout compatibility with Boost.Unordered flat containers allows for fast transfer +of all elements between `boost::concurrent_flat_map` and `boost::unordered_flat_map`. +(This feature has not been implemented yet.) + +=== Hash function and platform interopersability + +`boost::concurrent_flat_map` makes the same decisions and provides the same guarantees +as Boost.Unordered open-addressing containers with regards to +xref:#rationale_hash_function[hash function defaults] and +xref:#rationale_platform_interoperability[platform interoperability]. + From 48f703132e884f3f0d86c86bb39ad0771256d45f Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 13 May 2023 19:29:41 +0200 Subject: [PATCH 242/327] added implementation description for cfoa --- doc/diagrams/cfoa.png | Bin 0 -> 9397 bytes doc/unordered/buckets.adoc | 48 +++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 doc/diagrams/cfoa.png diff --git a/doc/diagrams/cfoa.png b/doc/diagrams/cfoa.png new file mode 100644 index 0000000000000000000000000000000000000000..a72e3d535cd2e7b2ba5c541362f1f7c0ba9340e3 GIT binary patch literal 9397 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 jYN`wjOMkzw|1bYlqR7nXMdoh?1_lOCS3j3^P6 Date: Sun, 14 May 2023 11:13:48 +0200 Subject: [PATCH 243/327] typos/editorial --- doc/unordered/buckets.adoc | 3 +++ doc/unordered/compliance.adoc | 2 +- doc/unordered/rationale.adoc | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/unordered/buckets.adoc b/doc/unordered/buckets.adoc index e4a8fe3c..ed5f15dc 100644 --- a/doc/unordered/buckets.adoc +++ b/doc/unordered/buckets.adoc @@ -361,3 +361,6 @@ to be started over if some other thread interferes in the process by performing a succesful insertion beginning at the same group. In practice, the start-over frequency is extremely small, measured in the range of parts per million for some of our benchmarks. + +For more information on implementation rationale, read the +xref:#rationale_concurrent_hashmap[corresponding section]. diff --git a/doc/unordered/compliance.adoc b/doc/unordered/compliance.adoc index cd93770b..71768b26 100644 --- a/doc/unordered/compliance.adoc +++ b/doc/unordered/compliance.adoc @@ -188,7 +188,7 @@ is served by: template size_t visit_all(F f); ---- -of which there are parallelized version in C++17 compilers with parallel +of which there are parallelized versions in C++17 compilers with parallel algorithm support. In general, the interface of `boost::concurrent_flat_map` is derived from that of `boost::unordered_flat_map` by a fairly straightforward process of replacing iterators with visitation where applicable. If diff --git a/doc/unordered/rationale.adoc b/doc/unordered/rationale.adoc index 15a6aa9d..e63459dd 100644 --- a/doc/unordered/rationale.adoc +++ b/doc/unordered/rationale.adoc @@ -132,7 +132,7 @@ lookup that are lock-free up to the last step of actual element comparison. of all elements between `boost::concurrent_flat_map` and `boost::unordered_flat_map`. (This feature has not been implemented yet.) -=== Hash function and platform interopersability +=== Hash function and platform interoperability `boost::concurrent_flat_map` makes the same decisions and provides the same guarantees as Boost.Unordered open-addressing containers with regards to From 9260bff8f8fc5077f625820b6d35030b3252f1f1 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 15 May 2023 10:20:45 +0200 Subject: [PATCH 244/327] editorial --- include/boost/unordered/detail/foa/core.hpp | 2 +- include/boost/unordered/detail/foa/table.hpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 295f580f..2e58403e 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -111,7 +111,7 @@ static constexpr std::size_t default_bucket_count=0; /* foa::table_core is the common base of foa::table and foa::concurrent_table, * which in their turn serve as the foundational core of - * boost::unordered_flat_[map|set] and boost::concurrent_flat_map, + * boost::unordered_(flat|node)_(map|set) and boost::concurrent_flat_map, * respectively. Its main internal design aspects are: * * - Element slots are logically split into groups of size N=15. The number diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 554e762a..4bb66fd5 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -194,7 +194,7 @@ private: /* foa::table interface departs in a number of ways from that of C++ unordered * associative containers because it's not for end-user consumption - * (boost::unordered_[flat|node]_[map|set] wrappers complete it as + * (boost::unordered_(flat|node)_(map|set) wrappers complete it as * appropriate). * * The table supports two main modes of operation: flat and node-based. In the @@ -215,7 +215,7 @@ private: * * try_emplace, erase and find support heterogeneous lookup by default, * that is, without checking for any ::is_transparent typedefs --the - * checking is done by boost::unordered_[flat|node]_[map|set]. + * checking is done by boost::unordered_(flat|node)_(map|set). */ template From c3879e238dbcc0f19698cbc80c4bb5be7812a5a0 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 11 May 2023 10:17:02 -0700 Subject: [PATCH 245/327] Add free function swap() --- .../boost/unordered/concurrent_flat_map.hpp | 16 ++++-- test/cfoa/swap_tests.cpp | 56 ++++++++++++------- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 4d5d6d64..0be6fa60 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -149,8 +149,7 @@ namespace boost { detail::foa::concurrent_table table_; template - bool friend operator==( - concurrent_flat_map const& lhs, + bool friend operator==(concurrent_flat_map const& lhs, concurrent_flat_map const& rhs); public: @@ -747,9 +746,7 @@ namespace boost { /// Hash Policy /// - size_type bucket_count() const noexcept { - return table_.capacity(); - } + size_type bucket_count() const noexcept { return table_.capacity(); } float load_factor() const noexcept { return table_.load_factor(); } float max_load_factor() const noexcept @@ -788,7 +785,16 @@ namespace boost { { return !(lhs == rhs); } + + template + void swap(concurrent_flat_map& x, + concurrent_flat_map& y) + noexcept(noexcept(x.swap(y))) + { + x.swap(y); + } } // namespace unordered + } // namespace boost #undef BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE diff --git a/test/cfoa/swap_tests.cpp b/test/cfoa/swap_tests.cpp index 22cbfded..3ef830f3 100644 --- a/test/cfoa/swap_tests.cpp +++ b/test/cfoa/swap_tests.cpp @@ -83,8 +83,22 @@ 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) + struct + { + template void operator()(T& x1, T& x2) const { x1.swap(x2); } + } member_fn_swap; + + struct + { + template void operator()(T& x1, T& x2) const + { + using boost::unordered::swap; + swap(x1, x2); + } + } free_fn_swap; + + template + void swap_tests(X*, F swapper, G gen, test::random_generator rg) { using allocator = typename X::allocator_type; @@ -118,11 +132,11 @@ namespace { auto const old_cc = +raii::copy_constructor; auto const old_mc = +raii::move_constructor; - thread_runner(vals1, [&x1, &x2](boost::span s) { + thread_runner(vals1, [&x1, &x2, swapper](boost::span s) { (void)s; - x1.swap(x2); - x2.swap(x1); + swapper(x1, x2); + swapper(x2, x1); }); BOOST_TEST_EQ(raii::copy_constructor, old_cc); @@ -163,7 +177,8 @@ namespace { check_raii_counts(); } - template void insert_and_swap(G gen, test::random_generator rg) + template + void insert_and_swap(F swapper, 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); }); @@ -208,20 +223,21 @@ namespace { 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; - std::this_thread::yield(); - } while (!done1 || !done2); + t3 = + std::thread([&x1, &x2, &m, &cv, &done1, &done2, &num_swaps, swapper] { + do { + { + std::unique_lock lk(m); + cv.wait(lk, [] { return true; }); + } + swapper(x1, x2); + ++num_swaps; + std::this_thread::yield(); + } while (!done1 || !done2); - BOOST_TEST(done1); - BOOST_TEST(done2); - }); + BOOST_TEST(done1); + BOOST_TEST(done2); + }); t1.join(); t2.join(); @@ -258,10 +274,12 @@ namespace { UNORDERED_TEST( swap_tests, ((map)(pocs_map)) + ((member_fn_swap)(free_fn_swap)) ((value_type_generator)) ((default_generator)(sequential)(limited_range))) UNORDERED_TEST(insert_and_swap, + ((member_fn_swap)(free_fn_swap)) ((value_type_generator)) ((default_generator)(sequential)(limited_range))) // clang-format on From 6295c7f0d4c3684575cc524b0cb12ad3f3bb9170 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 15 May 2023 13:40:33 -0700 Subject: [PATCH 246/327] Add free function erase_if() --- .../boost/unordered/concurrent_flat_map.hpp | 8 +++ test/cfoa/erase_tests.cpp | 54 ++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 0be6fa60..499fe4a2 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -793,6 +793,14 @@ namespace boost { { x.swap(y); } + + template + typename concurrent_flat_map::size_type erase_if( + concurrent_flat_map& c, Predicate pred) + { + return c.erase_if(pred); + } + } // namespace unordered } // namespace boost diff --git a/test/cfoa/erase_tests.cpp b/test/cfoa/erase_tests.cpp index 3bffb8a2..75b3c73e 100644 --- a/test/cfoa/erase_tests.cpp +++ b/test/cfoa/erase_tests.cpp @@ -245,6 +245,58 @@ namespace { } } erase_if; + struct free_fn_erase_if_type + { + template void operator()(std::vector& values, X& x) + { + using value_type = typename X::value_type; + + std::atomic num_erased{0}; + + auto const old_size = x.size(); + + auto const old_dc = +raii::default_constructor; + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + auto const old_d = +raii::destructor; + + auto max = 0; + x.visit_all([&max](value_type const& v) { + if (v.second.x_ > max) { + max = v.second.x_; + } + }); + + auto threshold = max / 2; + + auto expected_erasures = 0u; + x.visit_all([&expected_erasures, threshold](value_type const& v) { + if (v.second.x_ > threshold) { + ++expected_erasures; + } + }); + + thread_runner(values, [&num_erased, &x, threshold](boost::span s) { + for (auto const& k : s) { + (void)k; + auto count = boost::unordered::erase_if( + x, [threshold](value_type& v) { return v.second.x_ > threshold; }); + num_erased += count; + } + }); + + BOOST_TEST_EQ(num_erased, expected_erasures); + BOOST_TEST_EQ(x.size(), old_size - num_erased); + + BOOST_TEST_EQ(raii::default_constructor, old_dc); + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + + BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased); + } + } free_fn_erase_if; + struct erase_if_exec_policy_type { template void operator()(std::vector& values, X& x) @@ -353,7 +405,7 @@ UNORDERED_TEST( erase, ((map)) ((value_type_generator)(init_type_generator)) - ((lvalue_eraser)(lvalue_eraser_if)(erase_if)(erase_if_exec_policy)) + ((lvalue_eraser)(lvalue_eraser_if)(erase_if)(free_fn_erase_if)(erase_if_exec_policy)) ((default_generator)(sequential)(limited_range))) UNORDERED_TEST( From 63026fd3201ea7e95b9459207bdb778e4dc12f3f Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 16 May 2023 09:19:43 -0700 Subject: [PATCH 247/327] Clean up tests to avoid needless yields and extraneous spurious wakeups --- test/cfoa/clear_tests.cpp | 30 +++++++++++++++++++++++------- test/cfoa/helpers.hpp | 4 ---- test/cfoa/swap_tests.cpp | 35 +++++++++++++++++++++++++++-------- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/test/cfoa/clear_tests.cpp b/test/cfoa/clear_tests.cpp index a3f1e846..c72b8155 100644 --- a/test/cfoa/clear_tests.cpp +++ b/test/cfoa/clear_tests.cpp @@ -59,32 +59,48 @@ namespace { std::mutex m; std::condition_variable cv; - std::atomic done{false}; + std::atomic num_clears{0}; - t1 = std::thread([&x, &values, &cv, &done] { + bool ready = false; + + t1 = std::thread([&x, &values, &cv, &done, &m, &ready] { for (auto i = 0u; i < values.size(); ++i) { x.insert(values[i]); - if (i % 100 == 0) { + if (i % (values.size() / 128) == 0) { + { + std::unique_lock lk(m); + ready = true; + } cv.notify_all(); } } + done = true; + { + std::unique_lock lk(m); + ready = true; + } + cv.notify_all(); }); - t2 = std::thread([&x, &m, &cv, &done] { - while (!done) { + t2 = std::thread([&x, &m, &cv, &done, &ready, &num_clears] { + do { { std::unique_lock lk(m); - cv.wait(lk, [] { return true; }); + cv.wait(lk, [&ready] { return ready; }); + ready = false; } x.clear(); - } + ++num_clears; + } while (!done); }); t1.join(); t2.join(); + BOOST_TEST_GE(num_clears, 1u); + if (!x.empty()) { test_fuzzy_matches_reference(x, reference_map, rg); } diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index 116664a6..bd773e8d 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -35,7 +35,6 @@ struct transp_hash template std::size_t operator()(T const& t) const noexcept { - std::this_thread::yield(); return boost::hash()(t); } }; @@ -46,7 +45,6 @@ struct transp_key_equal template bool operator()(T const& lhs, U const& rhs) const { - std::this_thread::yield(); return lhs == rhs; } }; @@ -70,7 +68,6 @@ struct stateful_hash { std::size_t h = static_cast(x_); boost::hash_combine(h, t); - std::this_thread::yield(); return h; } @@ -107,7 +104,6 @@ struct stateful_key_equal template bool operator()(T const& t, U const& u) const { - std::this_thread::yield(); return t == u; } diff --git a/test/cfoa/swap_tests.cpp b/test/cfoa/swap_tests.cpp index 3ef830f3..e0160f02 100644 --- a/test/cfoa/swap_tests.cpp +++ b/test/cfoa/swap_tests.cpp @@ -196,39 +196,58 @@ namespace { std::condition_variable cv; std::atomic_bool done1{false}, done2{false}; std::atomic num_swaps{0}; + bool ready = false; - t1 = std::thread([&x1, &vals1, &l, &done1, &cv] { + t1 = std::thread([&x1, &vals1, &l, &done1, &cv, &ready, &m] { 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) { + if (idx % (vals1.size() / 128) == 0) { + { + std::unique_lock lk(m); + ready = true; + } cv.notify_all(); } std::this_thread::yield(); } done1 = true; + { + std::unique_lock lk(m); + ready = true; + } + cv.notify_all(); }); - t2 = std::thread([&x2, &vals2, &l, &done2] { + t2 = std::thread([&x2, &vals2, &l, &done2, &ready, &cv, &m] { l.arrive_and_wait(); - for (auto const& val : vals2) { + for (std::size_t idx = 0; idx < vals2.size(); ++idx) { + auto const& val = vals2[idx]; x2.insert(val); - std::this_thread::yield(); + if (idx % 100 == 0) { + std::this_thread::yield(); + } } done2 = true; + { + std::unique_lock lk(m); + ready = true; + } + cv.notify_all(); }); - t3 = - std::thread([&x1, &x2, &m, &cv, &done1, &done2, &num_swaps, swapper] { + t3 = std::thread( + [&x1, &x2, &m, &cv, &done1, &done2, &num_swaps, swapper, &ready] { do { { std::unique_lock lk(m); - cv.wait(lk, [] { return true; }); + cv.wait(lk, [&ready] { return ready; }); + ready = false; } swapper(x1, x2); ++num_swaps; From 32ff2f145e798acc2aa92915d42dc290469f2c76 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 16 May 2023 11:55:56 -0700 Subject: [PATCH 248/327] Add initial draft of equality tests --- test/Jamfile.v2 | 3 +- test/cfoa/equality_tests.cpp | 142 +++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 test/cfoa/equality_tests.cpp diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index ce98085c..75ada20f 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -188,12 +188,13 @@ local CFOA_TESTS = swap_tests merge_tests rehash_tests + equality_tests ; for local test in $(CFOA_TESTS) { run cfoa/$(test).cpp - : requirements $(CPP11) + : requirements $(CPP11) multi : target-name cfoa_$(test) ; } diff --git a/test/cfoa/equality_tests.cpp b/test/cfoa/equality_tests.cpp new file mode 100644 index 00000000..8ab2fbb6 --- /dev/null +++ b/test/cfoa/equality_tests.cpp @@ -0,0 +1,142 @@ +// 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{1634048962}; + +using test::default_generator; +using test::limited_range; +using test::sequential; + +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; + +namespace { + + UNORDERED_AUTO_TEST (simple_equality) { + { + map_type x1( + {{1, 11}, {2, 22}}, 0, hasher(1), key_equal(2), allocator_type(3)); + + map_type x2( + {{1, 11}, {2, 22}}, 0, hasher(2), key_equal(2), allocator_type(3)); + + map_type x3( + {{1, 11}, {2, 23}}, 0, hasher(2), key_equal(2), allocator_type(3)); + + map_type x4({{1, 11}}, 0, hasher(2), key_equal(2), allocator_type(3)); + + BOOST_TEST_EQ(x1.size(), x2.size()); + BOOST_TEST(x1 == x2); + BOOST_TEST(!(x1 != x2)); + + BOOST_TEST_EQ(x1.size(), x3.size()); + BOOST_TEST(!(x1 == x3)); + BOOST_TEST(x1 != x3); + + BOOST_TEST(x1.size() != x4.size()); + BOOST_TEST(!(x1 == x4)); + BOOST_TEST(x1 != x4); + } + } + + template void insert_and_compare(G gen, test::random_generator rg) + { + auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); }); + boost::unordered_flat_map reference_map( + vals1.begin(), vals1.end()); + + { + raii::reset_counts(); + + map_type x1(vals1.size(), hasher(1), key_equal(2), allocator_type(3)); + map_type x2(vals1.begin(), vals1.end(), vals1.size(), hasher(2), + key_equal(2), allocator_type(3)); + + std::thread t1, t2; + + std::mutex m; + std::condition_variable cv; + std::atomic_bool done{false}; + std::atomic num_compares{0}; + bool ready = false; + + BOOST_TEST(x1.empty()); + + t1 = std::thread([&x1, &m, &cv, &vals1, &done, &ready] { + for (std::size_t idx = 0; idx < vals1.size(); ++idx) { + auto const& v = vals1[idx]; + x1.insert(v); + + if (idx % (vals1.size() / 128) == 0) { + { + std::unique_lock lk(m); + ready = true; + } + cv.notify_all(); + } + std::this_thread::yield(); + } + + done = true; + { + std::unique_lock lk(m); + ready = true; + } + cv.notify_all(); + }); + + t2 = std::thread([&x1, &x2, &m, &cv, &done, &num_compares, &ready] { + do { + { + std::unique_lock lk(m); + cv.wait(lk, [&ready] { return ready; }); + ready = false; + } + + volatile bool b = false; + + b = x1 == x2; + b = x1 != x2; + + b; + + ++num_compares; + std::this_thread::yield(); + } while (!done); + + BOOST_TEST(done); + }); + + t1.join(); + t2.join(); + + BOOST_TEST_GE(num_compares, 1u); + + BOOST_TEST(x1 == x2); + BOOST_TEST(!(x1 != x2)); + + test_matches_reference(x1, reference_map); + } + check_raii_counts(); + } +} // namespace + +// clang-format off +UNORDERED_TEST( + insert_and_compare, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) +// clang-format on + +RUN_TESTS() From dbd1a929e6aa4232d8250cc3f1fe7651aa9726b3 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 16 May 2023 12:34:50 -0700 Subject: [PATCH 249/327] Remove unnenecessary spinning --- test/cfoa/rehash_tests.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/cfoa/rehash_tests.cpp b/test/cfoa/rehash_tests.cpp index c59c7717..1a3092aa 100644 --- a/test/cfoa/rehash_tests.cpp +++ b/test/cfoa/rehash_tests.cpp @@ -137,9 +137,6 @@ namespace { t3 = std::thread([&x, &vals1, &m, &cv, &done1, &done2, &call_count, &ready] { - while (x.empty()) { - } - do { { std::unique_lock lk(m); From 5f249bc6815539d6f87023434dbd83ccc50c0cf7 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 16 May 2023 13:31:35 -0700 Subject: [PATCH 250/327] Add fwd header --- .../boost/unordered/concurrent_flat_map.hpp | 6 +- .../unordered/concurrent_flat_map_fwd.hpp | 58 +++++++++++++++++++ test/Jamfile.v2 | 1 + test/cfoa/fwd_tests.cpp | 55 ++++++++++++++++++ 4 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 include/boost/unordered/concurrent_flat_map_fwd.hpp create mode 100644 test/cfoa/fwd_tests.cpp diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 499fe4a2..d5ac9cc0 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -15,6 +15,7 @@ #ifndef BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP #define BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP +#include #include #include @@ -134,9 +135,7 @@ namespace boost { }; } // namespace detail - template , - class Pred = std::equal_to, - class Allocator = std::allocator > > + template class concurrent_flat_map { private: @@ -803,6 +802,7 @@ namespace boost { } // namespace unordered + using unordered::concurrent_flat_map; } // namespace boost #undef BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE diff --git a/include/boost/unordered/concurrent_flat_map_fwd.hpp b/include/boost/unordered/concurrent_flat_map_fwd.hpp new file mode 100644 index 00000000..308f099c --- /dev/null +++ b/include/boost/unordered/concurrent_flat_map_fwd.hpp @@ -0,0 +1,58 @@ +/* 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_FWD_HPP +#define BOOST_UNORDERED_CONCURRENT_FLAT_MAP_FWD_HPP + +#include + +#include +#include + +namespace boost { + namespace unordered { + + template , + class Pred = std::equal_to, + class Allocator = std::allocator > > + class concurrent_flat_map; + + template + bool operator==( + concurrent_flat_map const& lhs, + concurrent_flat_map const& rhs); + + template + bool operator!=( + concurrent_flat_map const& lhs, + concurrent_flat_map const& rhs); + + template + void swap(concurrent_flat_map& x, + concurrent_flat_map& y) + noexcept(noexcept(x.swap(y))); + + template + typename concurrent_flat_map::size_type erase_if( + concurrent_flat_map& c, Predicate pred); + + } // namespace unordered + + using unordered::concurrent_flat_map; + using unordered::swap; + using unordered::operator==; + using unordered::operator!=; +} // namespace boost + +#endif // BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 75ada20f..a84bc78f 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -189,6 +189,7 @@ local CFOA_TESTS = merge_tests rehash_tests equality_tests + fwd_tests ; for local test in $(CFOA_TESTS) diff --git a/test/cfoa/fwd_tests.cpp b/test/cfoa/fwd_tests.cpp new file mode 100644 index 00000000..8e88f849 --- /dev/null +++ b/test/cfoa/fwd_tests.cpp @@ -0,0 +1,55 @@ +// 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{32304628}; + +using test::default_generator; +using test::limited_range; +using test::sequential; + +template +void swap_call(boost::unordered::concurrent_flat_map& x1, + boost::unordered::concurrent_flat_map& x2) +{ + swap(x1, x2); +} + +template +bool equal_call(boost::unordered::concurrent_flat_map& x1, + boost::unordered::concurrent_flat_map& x2) +{ + return x1 == x2; +} + +template +bool unequal_call(boost::unordered::concurrent_flat_map& x1, + boost::unordered::concurrent_flat_map& x2) +{ + return x1 != x2; +} + +#include + +using map_type = boost::unordered::concurrent_flat_map; + +UNORDERED_AUTO_TEST (fwd_swap_call) { + map_type x1, x2; + swap_call(x1, x2); +} + +UNORDERED_AUTO_TEST (fwd_equal_call) { + map_type x1, x2; + BOOST_TEST(equal_call(x1, x2)); +} + +UNORDERED_AUTO_TEST (fwd_unequal_call) { + map_type x1, x2; + BOOST_TEST_NOT(unequal_call(x1, x2)); +} + +RUN_TESTS() From 8ddfc8ec7a2aca7c40b1ea8444fa83e1f5c60cd9 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 16 May 2023 14:33:41 -0700 Subject: [PATCH 251/327] Update execution policies to accept by forwarding reference --- include/boost/unordered/concurrent_flat_map.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index d5ac9cc0..e0d35255 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -379,7 +379,7 @@ namespace boost { BOOST_FORCEINLINE typename std::enable_if::value, void>::type - visit_all(ExecPolicy p, F f) + visit_all(ExecPolicy&& p, F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) @@ -390,7 +390,7 @@ namespace boost { BOOST_FORCEINLINE typename std::enable_if::value, void>::type - visit_all(ExecPolicy p, F f) const + visit_all(ExecPolicy&& p, F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) @@ -401,7 +401,7 @@ namespace boost { BOOST_FORCEINLINE typename std::enable_if::value, void>::type - cvisit_all(ExecPolicy p, F f) const + cvisit_all(ExecPolicy&& p, F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) @@ -689,7 +689,7 @@ namespace boost { BOOST_FORCEINLINE typename std::enable_if::value, void>::type - erase_if(ExecPolicy p, F f) + erase_if(ExecPolicy&& p, F f) { BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) table_.erase_if(p, f); From fcf6fee0f6ad3b35b214aed2609810e46f3f1e36 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 16 May 2023 15:42:47 -0700 Subject: [PATCH 252/327] Make usage of forceinline consistent with the underlying concurrent_table --- .../boost/unordered/concurrent_flat_map.hpp | 69 +++++++++---------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index e0d35255..d6d5937c 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -356,19 +356,19 @@ namespace boost { return table_.visit(std::forward(k), f); } - template BOOST_FORCEINLINE size_type visit_all(F f) + template size_type visit_all(F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) return table_.visit_all(f); } - template BOOST_FORCEINLINE size_type visit_all(F f) const + template size_type visit_all(F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.visit_all(f); } - template BOOST_FORCEINLINE size_type cvisit_all(F f) const + template size_type cvisit_all(F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) return table_.cvisit_all(f); @@ -376,10 +376,9 @@ namespace boost { #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) template - BOOST_FORCEINLINE - typename std::enable_if::value, - void>::type - visit_all(ExecPolicy&& p, F f) + typename std::enable_if::value, + void>::type + visit_all(ExecPolicy&& p, F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) @@ -387,10 +386,9 @@ namespace boost { } template - BOOST_FORCEINLINE - typename std::enable_if::value, - void>::type - visit_all(ExecPolicy&& p, F f) const + typename std::enable_if::value, + void>::type + visit_all(ExecPolicy&& p, F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) @@ -398,10 +396,9 @@ namespace boost { } template - BOOST_FORCEINLINE - typename std::enable_if::value, - void>::type - cvisit_all(ExecPolicy&& p, F f) const + typename std::enable_if::value, + void>::type + cvisit_all(ExecPolicy&& p, F f) const { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) @@ -431,14 +428,14 @@ namespace boost { } template - BOOST_FORCEINLINE void insert(InputIterator begin, InputIterator end) + void insert(InputIterator begin, InputIterator end) { for (auto pos = begin; pos != end; ++pos) { table_.insert(*pos); } } - BOOST_FORCEINLINE void insert(std::initializer_list ilist) + void insert(std::initializer_list ilist) { this->insert(ilist.begin(), ilist.end()); } @@ -496,8 +493,7 @@ namespace boost { } template - BOOST_FORCEINLINE void insert_or_visit( - InputIterator first, InputIterator last, F f) + void insert_or_visit(InputIterator first, InputIterator last, F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) for (; first != last; ++first) { @@ -506,8 +502,7 @@ namespace boost { } template - BOOST_FORCEINLINE void insert_or_visit( - std::initializer_list ilist, F f) + void insert_or_visit(std::initializer_list ilist, F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) this->insert_or_visit(ilist.begin(), ilist.end(), f); @@ -542,8 +537,7 @@ namespace boost { } template - BOOST_FORCEINLINE void insert_or_cvisit( - InputIterator first, InputIterator last, F f) + void insert_or_cvisit(InputIterator first, InputIterator last, F f) { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) for (; first != last; ++first) { @@ -552,8 +546,7 @@ namespace boost { } template - BOOST_FORCEINLINE void insert_or_cvisit( - std::initializer_list ilist, F f) + void insert_or_cvisit(std::initializer_list ilist, F f) { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) this->insert_or_visit(ilist.begin(), ilist.end(), f); @@ -686,20 +679,16 @@ namespace boost { #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) template - BOOST_FORCEINLINE - typename std::enable_if::value, - void>::type - erase_if(ExecPolicy&& p, F f) + typename std::enable_if::value, + void>::type + erase_if(ExecPolicy&& p, F f) { BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) table_.erase_if(p, f); } #endif - template BOOST_FORCEINLINE size_type erase_if(F f) - { - return table_.erase_if(f); - } + template size_type erase_if(F f) { return table_.erase_if(f); } void swap(concurrent_flat_map& other) noexcept( boost::allocator_is_always_equal::type::value || @@ -723,20 +712,26 @@ namespace boost { return merge(x); } - size_type count(key_type const& k) const { return table_.count(k); } + BOOST_FORCEINLINE size_type count(key_type const& k) const + { + return table_.count(k); + } template - typename std::enable_if< + BOOST_FORCEINLINE typename std::enable_if< detail::are_transparent::value, size_type>::type count(K const& k) { return table_.count(k); } - bool contains(key_type const& k) const { return table_.contains(k); } + BOOST_FORCEINLINE bool contains(key_type const& k) const + { + return table_.contains(k); + } template - typename std::enable_if< + BOOST_FORCEINLINE typename std::enable_if< detail::are_transparent::value, bool>::type contains(K const& k) const { From bf06fa97e36a421951e894a0bd24670b476a1658 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 17 May 2023 09:38:29 -0700 Subject: [PATCH 253/327] Add deduction guides --- .../boost/unordered/concurrent_flat_map.hpp | 91 +++++++++++++++++++ .../boost/unordered/detail/type_traits.hpp | 13 +++ .../boost/unordered/unordered_flat_map.hpp | 12 --- include/boost/unordered/unordered_map.hpp | 12 --- .../boost/unordered/unordered_node_map.hpp | 12 --- test/unordered/deduction_tests.cpp | 2 + 6 files changed, 106 insertions(+), 36 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index d6d5937c..2a70ea67 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -795,6 +795,97 @@ namespace boost { return c.erase_if(pred); } +#ifdef BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES + + template >, + class Pred = + std::equal_to >, + class Allocator = std::allocator< + boost::unordered::detail::iter_to_alloc_t >, + class = boost::enable_if_t >, + class = boost::enable_if_t >, + class = boost::enable_if_t >, + class = boost::enable_if_t > > + concurrent_flat_map(InputIterator, InputIterator, + std::size_t = boost::unordered::detail::foa::default_bucket_count, + Hash = Hash(), Pred = Pred(), Allocator = Allocator()) + -> concurrent_flat_map< + boost::unordered::detail::iter_key_t, + boost::unordered::detail::iter_val_t, Hash, Pred, + Allocator>; + + template >, + class Pred = std::equal_to >, + class Allocator = std::allocator >, + class = boost::enable_if_t >, + class = boost::enable_if_t >, + class = boost::enable_if_t > > + concurrent_flat_map(std::initializer_list >, + std::size_t = boost::unordered::detail::foa::default_bucket_count, + Hash = Hash(), Pred = Pred(), Allocator = Allocator()) + -> concurrent_flat_map, T, Hash, Pred, + Allocator>; + + template >, + class = boost::enable_if_t > > + concurrent_flat_map(InputIterator, InputIterator, std::size_t, Allocator) + -> concurrent_flat_map< + boost::unordered::detail::iter_key_t, + boost::unordered::detail::iter_val_t, + boost::hash >, + std::equal_to >, + Allocator>; + + template >, + class = boost::enable_if_t > > + concurrent_flat_map(InputIterator, InputIterator, Allocator) + -> concurrent_flat_map< + boost::unordered::detail::iter_key_t, + boost::unordered::detail::iter_val_t, + boost::hash >, + std::equal_to >, + Allocator>; + + template >, + class = boost::enable_if_t >, + class = boost::enable_if_t > > + concurrent_flat_map( + InputIterator, InputIterator, std::size_t, Hash, Allocator) + -> concurrent_flat_map< + boost::unordered::detail::iter_key_t, + boost::unordered::detail::iter_val_t, Hash, + std::equal_to >, + Allocator>; + + template > > + concurrent_flat_map(std::initializer_list >, std::size_t, + Allocator) -> concurrent_flat_map, T, + boost::hash >, + std::equal_to >, Allocator>; + + template > > + concurrent_flat_map(std::initializer_list >, Allocator) + -> concurrent_flat_map, T, + boost::hash >, + std::equal_to >, Allocator>; + + template >, + class = boost::enable_if_t > > + concurrent_flat_map(std::initializer_list >, std::size_t, + Hash, Allocator) -> concurrent_flat_map, T, + Hash, std::equal_to >, Allocator>; + +#endif + } // namespace unordered using unordered::concurrent_flat_map; diff --git a/include/boost/unordered/detail/type_traits.hpp b/include/boost/unordered/detail/type_traits.hpp index fd37a8e4..838611ce 100644 --- a/include/boost/unordered/detail/type_traits.hpp +++ b/include/boost/unordered/detail/type_traits.hpp @@ -20,6 +20,9 @@ #include #include #include + +#include +#include #endif // BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES @@ -101,6 +104,16 @@ namespace boost { !boost::is_integral::value && !is_allocator_v; template constexpr bool const is_pred_v = !is_allocator_v

; + + template + using iter_key_t = + typename std::iterator_traits::value_type::first_type; + template + using iter_val_t = + typename std::iterator_traits::value_type::second_type; + template + using iter_to_alloc_t = + typename std::pair const, iter_val_t >; #endif } // namespace detail } // namespace unordered diff --git a/include/boost/unordered/unordered_flat_map.hpp b/include/boost/unordered/unordered_flat_map.hpp index 23a13438..9597d26f 100644 --- a/include/boost/unordered/unordered_flat_map.hpp +++ b/include/boost/unordered/unordered_flat_map.hpp @@ -741,18 +741,6 @@ namespace boost { #if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES - namespace detail { - template - using iter_key_t = - typename std::iterator_traits::value_type::first_type; - template - using iter_val_t = - typename std::iterator_traits::value_type::second_type; - template - using iter_to_alloc_t = - typename std::pair const, iter_val_t >; - } // namespace detail - template >, diff --git a/include/boost/unordered/unordered_map.hpp b/include/boost/unordered/unordered_map.hpp index 8a8bc062..35eb2f2e 100644 --- a/include/boost/unordered/unordered_map.hpp +++ b/include/boost/unordered/unordered_map.hpp @@ -1061,18 +1061,6 @@ namespace boost { #if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES - namespace detail { - template - using iter_key_t = - typename std::iterator_traits::value_type::first_type; - template - using iter_val_t = - typename std::iterator_traits::value_type::second_type; - template - using iter_to_alloc_t = - typename std::pair const, iter_val_t >; - } // namespace detail - template >, diff --git a/include/boost/unordered/unordered_node_map.hpp b/include/boost/unordered/unordered_node_map.hpp index 8d32ba0a..33e3c7c7 100644 --- a/include/boost/unordered/unordered_node_map.hpp +++ b/include/boost/unordered/unordered_node_map.hpp @@ -893,18 +893,6 @@ namespace boost { #if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES - namespace detail { - template - using iter_key_t = - typename std::iterator_traits::value_type::first_type; - template - using iter_val_t = - typename std::iterator_traits::value_type::second_type; - template - using iter_to_alloc_t = - typename std::pair const, iter_val_t >; - } // namespace detail - template >, diff --git a/test/unordered/deduction_tests.cpp b/test/unordered/deduction_tests.cpp index 7c942186..571aa7fa 100644 --- a/test/unordered/deduction_tests.cpp +++ b/test/unordered/deduction_tests.cpp @@ -13,6 +13,7 @@ #include #include +#include struct hash_equals { @@ -432,6 +433,7 @@ int main() map_tests(); map_tests(); map_tests(); + map_tests(); set_tests(); set_tests(); set_tests(); From 4a416501c8b5f92317ad165fe1aea4835adaa773 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 17 May 2023 10:14:45 -0700 Subject: [PATCH 254/327] Fix misuse of ctad macro --- include/boost/unordered/concurrent_flat_map.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 2a70ea67..d3a0c565 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -795,7 +795,7 @@ namespace boost { return c.erase_if(pred); } -#ifdef BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES +#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES template Date: Wed, 17 May 2023 13:36:56 -0700 Subject: [PATCH 255/327] Remove unreliable check from swap_tests --- test/cfoa/swap_tests.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/cfoa/swap_tests.cpp b/test/cfoa/swap_tests.cpp index e0160f02..3ab29791 100644 --- a/test/cfoa/swap_tests.cpp +++ b/test/cfoa/swap_tests.cpp @@ -264,9 +264,6 @@ namespace { 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)); From 3d640ac0321750aa114af7138c2154aa8a62282e Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 18 May 2023 20:18:58 +0200 Subject: [PATCH 256/327] refactored to modernize and improve flow --- doc/unordered.adoc | 5 +- doc/unordered/buckets.adoc | 230 +----------------- doc/unordered/changes.adoc | 3 +- doc/unordered/compliance.adoc | 12 +- ...nt_flat_map_intro.adoc => concurrent.adoc} | 13 +- doc/unordered/intro.adoc | 230 ++++-------------- doc/unordered/rationale.adoc | 12 +- .../{comparison.adoc => regular.adoc} | 95 +++++++- doc/unordered/structures.adoc | 179 ++++++++++++++ 9 files changed, 354 insertions(+), 425 deletions(-) rename doc/unordered/{concurrent_flat_map_intro.adoc => concurrent.adoc} (96%) rename doc/unordered/{comparison.adoc => regular.adoc} (56%) create mode 100644 doc/unordered/structures.adoc diff --git a/doc/unordered.adoc b/doc/unordered.adoc index 3d7a54fb..5da1b442 100644 --- a/doc/unordered.adoc +++ b/doc/unordered.adoc @@ -13,9 +13,10 @@ include::unordered/intro.adoc[] include::unordered/buckets.adoc[] include::unordered/hash_equality.adoc[] -include::unordered/comparison.adoc[] -include::unordered/concurrent_flat_map_intro.adoc[] +include::unordered/regular.adoc[] +include::unordered/concurrent.adoc[] include::unordered/compliance.adoc[] +include::unordered/structures.adoc[] include::unordered/benchmarks.adoc[] include::unordered/rationale.adoc[] include::unordered/ref.adoc[] diff --git a/doc/unordered/buckets.adoc b/doc/unordered/buckets.adoc index ed5f15dc..7028ddce 100644 --- a/doc/unordered/buckets.adoc +++ b/doc/unordered/buckets.adoc @@ -2,9 +2,9 @@ :idprefix: buckets_ :imagesdir: ../diagrams -= The Data Structure += Basics of Hash Tables -The containers are made up of a number of 'buckets', each of which can contain +The containers are made up of a number of _buckets_, each of which can contain any number of elements. For example, the following diagram shows a <> with 7 buckets containing 5 elements, `A`, `B`, `C`, `D` and `E` (this is just for illustration, containers will typically have more buckets). @@ -12,8 +12,7 @@ have more buckets). image::buckets.png[] In order to decide which bucket to place an element in, the container applies -the hash function, `Hash`, to the element's key (for `unordered_set` and -`unordered_multiset` the key is the whole element, but is referred to as the key +the hash function, `Hash`, to the element's key (for sets the key is the whole element, but is referred to as the key so that the same terminology can be used for sets and maps). This returns a value of type `std::size_t`. `std::size_t` has a much greater range of values then the number of buckets, so the container applies another transformation to @@ -80,7 +79,7 @@ h|*Method* h|*Description* |=== -== Controlling the number of buckets +== Controlling the Number of Buckets As more elements are added to an unordered associative container, the number of collisions will increase causing performance to degrade. @@ -90,8 +89,8 @@ calling `rehash`. The standard leaves a lot of freedom to the implementer to decide how the number of buckets is chosen, but it does make some requirements based on the -container's 'load factor', the number of elements divided by the number of buckets. -Containers also have a 'maximum load factor' which they should try to keep the +container's _load factor_, the number of elements divided by the number of buckets. +Containers also have a _maximum load factor_ which they should try to keep the load factor below. You can't control the bucket count directly but there are two ways to @@ -133,9 +132,10 @@ h|*Method* h|*Description* |`void rehash(size_type n)` |Changes the number of buckets so that there at least `n` buckets, and so that the load factor is less than the maximum load factor. -2+^h| *Open-addressing containers only* + +2+^h| *Open-addressing and concurrent containers only* + `boost::unordered_flat_set`, `boost::unordered_flat_map` + `boost::unordered_node_set`, `boost::unordered_node_map` + +`boost::concurrent_flat_map` h|*Method* h|*Description* |`size_type max_load() const` @@ -143,7 +143,7 @@ h|*Method* h|*Description* |=== -A note on `max_load` for open-addressing containers: the maximum load will be +A note on `max_load` for open-addressing and concurrent containers: the maximum load will be (`max_load_factor() * bucket_count()`) right after `rehash` or on container creation, but may slightly decrease when erasing elements in high-load situations. For instance, if we have a <> with `size()` almost @@ -151,216 +151,4 @@ at `max_load()` level and then erase 1,000 elements, `max_load()` may decrease b few dozen elements. This is done internally by Boost.Unordered in order to keep its performance stable, and must be taken into account when planning for rehash-free insertions. -== Iterator Invalidation -It is not specified how member functions other than `rehash` and `reserve` affect -the bucket count, although `insert` can only invalidate iterators -when the insertion causes the container's load to be greater than the maximum allowed. -For most implementations this means that `insert` will only -change the number of buckets when this happens. Iterators can be -invalidated by calls to `insert`, `rehash` and `reserve`. - -As for pointers and references, -they are never invalidated for node-based containers -(`boost::unordered_[multi]set`, `boost::unordered_[multi]map`, `boost::unordered_node_set`, `boost::unordered_node_map`), -but they will when rehashing occurs for -`boost::unordered_flat_set` and `boost::unordered_flat_map`: this is because -these containers store elements directly into their holding buckets, so -when allocating a new bucket array the elements must be transferred by means of move construction. - -In a similar manner to using `reserve` for ``vector``s, it can be a good idea -to call `reserve` before inserting a large number of elements. This will get -the expensive rehashing out of the way and let you store iterators, safe in -the knowledge that they won't be invalidated. If you are inserting `n` -elements into container `x`, you could first call: - -``` -x.reserve(n); -``` - -Note:: `reserve(n)` reserves space for at least `n` elements, allocating enough buckets -so as to not exceed the maximum load factor. -+ -Because the maximum load factor is defined as the number of elements divided by the total -number of available buckets, this function is logically equivalent to: -+ -``` -x.rehash(std::ceil(n / x.max_load_factor())) -``` -+ -See the <> on the `rehash` function. - -== Fast Closed Addressing Implementation - -++++ - -++++ - -Boost.Unordered sports one of the fastest implementations of closed addressing, also commonly known as https://en.wikipedia.org/wiki/Hash_table#Separate_chaining[separate chaining]. An example figure representing the data structure is below: - -[#img-bucket-groups,.text-center] -.A simple bucket group approach -image::bucket-groups.png[align=center] - -An array of "buckets" is allocated and each bucket in turn points to its own individual linked list. This makes meeting the standard requirements of bucket iteration straight-forward. Unfortunately, iteration of the entire container is often times slow using this layout as each bucket must be examined for occupancy, yielding a time complexity of `O(bucket_count() + size())` when the standard requires complexity to be `O(size())`. - -Canonical standard implementations will wind up looking like the diagram below: - -[.text-center] -.The canonical standard approach -image::singly-linked.png[align=center,link=../diagrams/singly-linked.png,window=_blank] - -It's worth noting that this approach is only used by pass:[libc++] and pass:[libstdc++]; the MSVC Dinkumware implementation uses a different one. A more detailed analysis of the standard containers can be found http://bannalia.blogspot.com/2013/10/implementation-of-c-unordered.html[here]. - -This unusually laid out data structure is chosen to make iteration of the entire container efficient by inter-connecting all of the nodes into a singly-linked list. One might also notice that buckets point to the node _before_ the start of the bucket's elements. This is done so that removing elements from the list can be done efficiently without introducing the need for a doubly-linked list. Unfortunately, this data structure introduces a guaranteed extra indirection. For example, to access the first element of a bucket, something like this must be done: - -```c++ -auto const idx = get_bucket_idx(hash_function(key)); -node* p = buckets[idx]; // first load -node* n = p->next; // second load -if (n && is_in_bucket(n, idx)) { - value_type const& v = *n; // third load - // ... -} -``` - -With a simple bucket group layout, this is all that must be done: -```c++ -auto const idx = get_bucket_idx(hash_function(key)); -node* n = buckets[idx]; // first load -if (n) { - value_type const& v = *n; // second load - // ... -} -``` - -In practice, the extra indirection can have a dramatic performance impact to common operations such as `insert`, `find` and `erase`. But to keep iteration of the container fast, Boost.Unordered introduces a novel data structure, a "bucket group". A bucket group is a fixed-width view of a subsection of the buckets array. It contains a bitmask (a `std::size_t`) which it uses to track occupancy of buckets and contains two pointers so that it can form a doubly-linked list with non-empty groups. An example diagram is below: - -[#img-fca-layout] -.The new layout used by Boost -image::fca.png[align=center] - -Thus container-wide iteration is turned into traversing the non-empty bucket groups (an operation with constant time complexity) which reduces the time complexity back to `O(size())`. In total, a bucket group is only 4 words in size and it views `sizeof(std::size_t) * CHAR_BIT` buckets meaning that for all common implementations, there's only 4 bits of space overhead per bucket introduced by the bucket groups. - -A more detailed description of Boost.Unordered's closed-addressing implementation is -given in an -https://bannalia.blogspot.com/2022/06/advancing-state-of-art-for.html[external article]. -For more information on implementation rationale, read the -xref:#rationale_closed_addressing_containers[corresponding section]. - -== Open Addressing Implementation - -The diagram shows the basic internal layout of `boost::unordered_flat_map`/`unordered_node_map` and -`boost:unordered_flat_set`/`unordered_node_set`. - - -[#img-foa-layout] -.Open-addressing layout used by Boost.Unordered. -image::foa.png[align=center] - -As with all open-addressing containers, elements (or pointers to the element nodes in the case of -`boost::unordered_node_map` and `boost::unordered_node_set`) are stored directly in the bucket array. -This array is logically divided into 2^_n_^ _groups_ of 15 elements each. -In addition to the bucket array, there is an associated _metadata array_ with 2^_n_^ -16-byte words. - -[#img-foa-metadata] -.Breakdown of a metadata word. -image::foa-metadata.png[align=center] - -A metadata word is divided into 15 _h_~_i_~ bytes (one for each associated -bucket), and an _overflow byte_ (_ofw_ in the diagram). The value of _h_~_i_~ is: - - - 0 if the corresponding bucket is empty. - - 1 to encode a special empty bucket called a _sentinel_, which is used internally to - stop iteration when the container has been fully traversed. - - If the bucket is occupied, a _reduced hash value_ obtained from the hash value of - the element. - -When looking for an element with hash value _h_, SIMD technologies such as -https://en.wikipedia.org/wiki/SSE2[SSE2] and -https://en.wikipedia.org/wiki/ARM_architecture_family#Advanced_SIMD_(Neon)[Neon] allow us -to very quickly inspect the full metadata word and look for the reduced value of _h_ among all the -15 buckets with just a handful of CPU instructions: non-matching buckets can be -readily discarded, and those whose reduced hash value matches need be inspected via full -comparison with the corresponding element. If the looked-for element is not present, -the overflow byte is inspected: - -- If the bit in the position _h_ mod 8 is zero, lookup terminates (and the -element is not present). -- If the bit is set to 1 (the group has been _overflowed_), further groups are -checked using https://en.wikipedia.org/wiki/Quadratic_probing[_quadratic probing_], and -the process is repeated. - -Insertion is algorithmically similar: empty buckets are located using SIMD, -and when going past a full group its corresponding overflow bit is set to 1. - -In architectures without SIMD support, the logical layout stays the same, but the metadata -word is codified using a technique we call _bit interleaving_: this layout allows us -to emulate SIMD with reasonably good performance using only standard arithmetic and -logical operations. - -[#img-foa-metadata-interleaving] -.Bit-interleaved metadata word. -image::foa-metadata-interleaving.png[align=center] - -A more detailed description of Boost.Unordered's open-addressing implementation is -given in an -https://bannalia.blogspot.com/2022/11/inside-boostunorderedflatmap.html[external article]. -For more information on implementation rationale, read the -xref:#rationale_open_addresing_containers[corresponding section]. - -== Concurrent Open Addressing Implementation - -`boost::concurrent_flat_map` uses the basic -xref::#buckets_open_addressing_implementation[open-addressing layout] described above -augmented with synchronization mechanisms. - - -[#img-cfoa-layout] -.Concurrent open-addressing layout used by Boost.Unordered. -image::cfoa.png[align=center] - -Two levels of synchronization are used: - -* Container level: A read-write mutex is used to control access from any operation -to the container. Typically, such access is in read mode (that is, concurrent) even -for modifying operations, so for most practical purposes there is no thread -contention at this level. Access is only in write mode (blocking) when rehashing or -performing container-wide operations such as swapping or assignment. -* Group level: Each 15-slot group is equipped with an 8-byte word containing: - ** A read-write spinlock for synchronized access to any element in the group. - ** An atomic _insertion counter_ used for optimistic insertion as described - below. - -By using atomic operations to access the group metadata, lookup is (group-level) -lock-free up to the point where an actual comparison needs to be done with an element -that has been previously SIMD-matched: only then it's the group's spinlock used. - -Insertion uses the following _optimistic algorithm_: - -* The value of the insertion counter for the initial group in the probe -sequence is locally recorded (let's call this value `c0`). -* Lookup is as described above. If lookup finds no equivalent element, -search for an available slot for insertion successively locks/unlocks -each group in the probing sequence. -* When an available slot is located, it is preemptively occupied (its -reduced hash value is set) and the insertion counter is atomically -incremented: if no other thread has incremented the counter during the -whole operation (which is checked by comparing with `c0`), then we're -good to go and complete the insertion, otherwise we roll back and start -over. - -This algorithm has very low contention both at the lookup and actual -insertion phases in exchange for the possibility that computations have -to be started over if some other thread interferes in the process by -performing a succesful insertion beginning at the same group. In -practice, the start-over frequency is extremely small, measured in the range -of parts per million for some of our benchmarks. - -For more information on implementation rationale, read the -xref:#rationale_concurrent_hashmap[corresponding section]. diff --git a/doc/unordered/changes.adoc b/doc/unordered/changes.adoc index dfd12081..04f0bc44 100644 --- a/doc/unordered/changes.adoc +++ b/doc/unordered/changes.adoc @@ -6,8 +6,9 @@ :github-pr-url: https://github.com/boostorg/unordered/pull :cpp: C++ -== Release 1.83.0 +== Release 1.83.0 - Major update +* Added `boost::concurrent_flat_map`, a fast, thread-safe hashmap based on open addressing. * Sped up iteration of open-addressing containers. == Release 1.82.0 - Major update diff --git a/doc/unordered/compliance.adoc b/doc/unordered/compliance.adoc index 71768b26..4a24801d 100644 --- a/doc/unordered/compliance.adoc +++ b/doc/unordered/compliance.adoc @@ -5,7 +5,7 @@ :cpp: C++ -== Closed-addressing containers +== Closed-addressing Containers `unordered_[multi]set` and `unordered_[multi]map` are intended to provide a conformant implementation of the {cpp}20 standard that will work with {cpp}98 upwards. @@ -13,7 +13,7 @@ This wide compatibility does mean some compromises have to be made. With a compiler and library that fully support {cpp}11, the differences should be minor. -=== Move emulation +=== Move Emulation Support for move semantics is implemented using Boost.Move. If rvalue references are available it will use them, but if not it uses a close, @@ -25,7 +25,7 @@ but imperfect emulation. On such compilers: * The containers themselves are not movable. * Argument forwarding is not perfect. -=== Use of allocators +=== Use of Allocators {cpp}11 introduced a new allocator system. It's backwards compatible due to the lax requirements for allocators in the old standard, but might need @@ -58,7 +58,7 @@ Due to imperfect move emulation, some assignments might check `propagate_on_container_copy_assignment` on some compilers and `propagate_on_container_move_assignment` on others. -=== Construction/Destruction using allocators +=== Construction/Destruction Using Allocators The following support is required for full use of {cpp}11 style construction/destruction: @@ -117,7 +117,7 @@ Variadic constructor arguments for `emplace` are only used when both rvalue references and variadic template parameters are available. Otherwise `emplace` can only take up to 10 constructors arguments. -== Open-addressing containers +== Open-addressing Containers The C++ standard does not currently provide any open-addressing container specification to adhere to, so `boost::unordered_flat_set`/`unordered_node_set` and @@ -144,7 +144,7 @@ The main differences with C++ unordered associative containers are: ** Pointer stability is not kept under rehashing. ** There is no API for node extraction/insertion. -== Concurrent Hashmap +== Concurrent Containers There is currently no specification in the C++ standard for this or any other concurrent data structure. `boost::concurrent_flat_map` takes the same template parameters as `std::unordered_map` diff --git a/doc/unordered/concurrent_flat_map_intro.adoc b/doc/unordered/concurrent.adoc similarity index 96% rename from doc/unordered/concurrent_flat_map_intro.adoc rename to doc/unordered/concurrent.adoc index 3d701764..9026b31e 100644 --- a/doc/unordered/concurrent_flat_map_intro.adoc +++ b/doc/unordered/concurrent.adoc @@ -1,8 +1,9 @@ -[#concurrent_flat_map_intro] -= An introduction to boost::concurrent_flat_map +[#concurrent] += Concurrent Containers -:idprefix: concurrent_flat_map_intro_ +:idprefix: concurrent_ +Boost.Unordered currently provides just one concurrent container named `boost::concurrent_flat_map`. `boost::concurrent_flat_map` is a hash table that allows concurrent write/read access from different threads without having to implement any synchronzation mechanism on the user's side. @@ -131,7 +132,7 @@ by using `cvisit` overloads (for instance, `insert_or_cvisit`) and may result in higher parallelization. Consult the xref:#concurrent_flat_map[reference] for a complete list of available operations. -== Whole-table visitation +== Whole-table Visitation In the absence of iterators, `boost::concurrent_flat_map` provides `visit_all` as an alternative way to process all the elements in the map: @@ -168,7 +169,7 @@ may be inserted, modified or erased by other threads during visitation. It is advisable not to assume too much about the exact global state of a `boost::concurrent_flat_map` at any point in your program. -== Blocking operations +== Blocking Operations ``boost::concurrent_flat_map``s can be copied, assigned, cleared and merged just like any Boost.Unordered container. Unlike most other operations, these are _blocking_, @@ -177,5 +178,5 @@ clear or merge operation is in progress. Blocking is taken care of automatically and the user need not take any special precaution, but overall performance may be affected. Another blocking operation is _rehashing_, which happens explicitly via `rehash`/`reserve` -or during insertion when the table's load hits `max_load()`. As with non-concurrent hashmaps, +or during insertion when the table's load hits `max_load()`. As with non-concurrent containers, reserving space in advance of bulk insertions will generally speed up the process. diff --git a/doc/unordered/intro.adoc b/doc/unordered/intro.adoc index eebe4878..74540002 100644 --- a/doc/unordered/intro.adoc +++ b/doc/unordered/intro.adoc @@ -4,146 +4,22 @@ :idprefix: intro_ :cpp: C++ -For accessing data based on key lookup, the {cpp} standard library offers `std::set`, -`std::map`, `std::multiset` and `std::multimap`. These are generally -implemented using balanced binary trees so that lookup time has -logarithmic complexity. That is generally okay, but in many cases a -link:https://en.wikipedia.org/wiki/Hash_table[hash table^] can perform better, as accessing data has constant complexity, -on average. The worst case complexity is linear, but that occurs rarely and -with some care, can be avoided. +link:https://en.wikipedia.org/wiki/Hash_table[Hash tables^] are extremely popular +computer data structures and can be found under one form or another in virtually any programming +language. Whereas other associative structures such as rb-trees (used in {cpp} by `std::set` and `std::map`) +have logarithmic-time complexity for insertion and lookup, hash tables, if configured properly, +perform these operations in constant time on average, and are generally much faster. -Also, the existing containers require a 'less than' comparison object -to order their elements. For some data types this is impossible to implement -or isn't practical. In contrast, a hash table only needs an equality function -and a hash function for the key. +{cpp} introduced __unordered associative containers__ `std::unordered_set`, `std::unordered_map`, +`std::unordered_multiset` and `std::unordered_multimap` in {cpp}11, but research on hash tables +hasn't stopped since: advances in CPU architectures such as +more powerful caches, link:https://en.wikipedia.org/wiki/Single_instruction,_multiple_data[SIMD] operations +and increasingly available link:https://en.wikipedia.org/wiki/Multi-core_processor[multicore processors] +open up possibilities for improved hash-based data structures and new use cases that +are simply beyond reach of unordered associative containers as specified in 2011. -With this in mind, unordered associative containers were added to the {cpp} -standard. Boost.Unordered provides an implementation of the containers described in {cpp}11, -with some <> in -order to work with non-{cpp}11 compilers and libraries. - -`unordered_set` and `unordered_multiset` are defined in the header -`` -[source,c++] ----- -namespace boost { - template < - class Key, - class Hash = boost::hash, - class Pred = std::equal_to, - class Alloc = std::allocator > - class unordered_set; - - template< - class Key, - class Hash = boost::hash, - class Pred = std::equal_to, - class Alloc = std::allocator > - class unordered_multiset; -} ----- - -`unordered_map` and `unordered_multimap` are defined in the header -`` - -[source,c++] ----- -namespace boost { - template < - class Key, class Mapped, - class Hash = boost::hash, - class Pred = std::equal_to, - class Alloc = std::allocator > > - class unordered_map; - - template< - class Key, class Mapped, - class Hash = boost::hash, - class Pred = std::equal_to, - class Alloc = std::allocator > > - class unordered_multimap; -} ----- - -These containers, and all other implementations of standard unordered associative -containers, use an approach to its internal data structure design called -*closed addressing*. Starting in Boost 1.81, Boost.Unordered also provides containers -`boost::unordered_flat_set` and `boost::unordered_flat_map`, which use a -different data structure strategy commonly known as *open addressing* and depart in -a small number of ways from the standard so as to offer much better performance -in exchange (more than 2 times faster in typical scenarios): - - -[source,c++] ----- -// #include -// -// Note: no multiset version - -namespace boost { - template < - class Key, - class Hash = boost::hash, - class Pred = std::equal_to, - class Alloc = std::allocator > - class unordered_flat_set; -} ----- - -[source,c++] ----- -// #include -// -// Note: no multimap version - -namespace boost { - template < - class Key, class Mapped, - class Hash = boost::hash, - class Pred = std::equal_to, - class Alloc = std::allocator > > - class unordered_flat_map; -} ----- - -Starting in Boost 1.82, the containers `boost::unordered_node_set` and `boost::unordered_node_map` -are introduced: they use open addressing like `boost::unordered_flat_set` and `boost::unordered_flat_map`, -but internally store element _nodes_, like `boost::unordered_set` and `boost::unordered_map`, -which provide stability of pointers and references to the elements: - -[source,c++] ----- -// #include -// -// Note: no multiset version - -namespace boost { - template < - class Key, - class Hash = boost::hash, - class Pred = std::equal_to, - class Alloc = std::allocator > - class unordered_node_set; -} ----- - -[source,c++] ----- -// #include -// -// Note: no multimap version - -namespace boost { - template < - class Key, class Mapped, - class Hash = boost::hash, - class Pred = std::equal_to, - class Alloc = std::allocator > > - class unordered_node_map; -} ----- - -These are all the containers provided by Boost.Unordered: +Boost.Unordered offers a catalog of hash containers with different standards compliance levels, +performances and intented usage scenarios: [caption=, title='Table {counter:table-counter}. Boost.Unordered containers'] [cols="1,1,.^1", frame=all, grid=rows] @@ -165,44 +41,49 @@ These are all the containers provided by Boost.Unordered: ^| `boost::unordered_flat_set` + `boost::unordered_flat_map` +^.^h|*Concurrent* +^| +^| `boost::concurrent_flat_map` + |=== -Closed-addressing containers are pass:[C++]98-compatible. Open-addressing containers require a -reasonably compliant pass:[C++]11 compiler. +* **Closed-addressing containers** are fully compliant with the C++ specification +for unordered associative containers and feature one of the fastest implementations +in the market within the technical constraints imposed by the required standard interface. +* **Open-addressing containers** rely on much faster data structures and algorithms +(more than 2 times faster in typical scenarios) while slightly diverging from the standard +interface to accommodate the implementation. +There are two variants: **flat** (the fastest) and **node-based**, which +provide pointer stability under rehashing at the expense of being slower. +* Finally, `boost::concurrent_flat_map` (the only **concurrent container** provided +at present) is a hashmap designed and implemented to be used in high-performance +multithreaded scenarios. Its interface is radically different from that of regular C++ containers. -Boost.Unordered containers are used in a similar manner to the normal associative -containers: - -[source,cpp] ----- -typedef boost::unordered_map map; -map x; -x["one"] = 1; -x["two"] = 2; -x["three"] = 3; - -assert(x.at("one") == 1); -assert(x.find("missing") == x.end()); ----- - -But since the elements aren't ordered, the output of: +All sets and maps in Boost.Unordered are instantiatied similarly as +`std::unordered_set` and `std::unordered_map`, respectively: [source,c++] ----- -for(const map::value_type& i: x) { - std::cout<, + class Pred = std::equal_to, + class Alloc = std::allocator > + class unordered_set; + // same for unordered_multiset, unordered_flat_set, unordered_node_set + + template < + class Key, class Mapped, + class Hash = boost::hash, + class Pred = std::equal_to, + class Alloc = std::allocator > > + class unordered_map; + // same for unordered_multimap, unordered_flat_map, unordered_node_map + // and concurrent_flat_map } ---- -can be in any order. For example, it might be: - -[source] ----- -two,2 -one,1 -three,3 ----- - To store an object in an unordered associative container requires both a key equality function and a hash function. The default function objects in the standard containers support a few basic types including integer types, @@ -213,16 +94,3 @@ you have to extend Boost.Hash to support the type or use your own custom equality predicates and hash functions. See the <> section for more details. - -There are other differences, which are listed in the -<> section. - -== A concurrent hashmap - -Starting in Boost 1.83, Boost.Unordered provides `boost::concurrent_flat_map`, -a thread-safe hash table for high performance multithreaded scenarios. Although -it shares the internal data structure and most of the algorithms with Boost.Unordered -open-addressing `boost::unordered_flat_map`, ``boost::concurrent_flat_map``'s API departs significantly -from that of C++ unordered associative containers to make this table suitable for -concurrent usage. Consult the xref:#concurrent_flat_map_intro[dedicated tutorial] -for more information. diff --git a/doc/unordered/rationale.adoc b/doc/unordered/rationale.adoc index e63459dd..7bbf2260 100644 --- a/doc/unordered/rationale.adoc +++ b/doc/unordered/rationale.adoc @@ -4,7 +4,7 @@ = Implementation Rationale -== Closed-addressing containers +== Closed-addressing Containers `boost::unordered_[multi]set` and `boost::unordered_[multi]map` adhere to the standard requirements for unordered associative @@ -74,7 +74,7 @@ Since release 1.80.0, prime numbers are chosen for the number of buckets in tandem with sophisticated modulo arithmetic. This removes the need for "mixing" the result of the user's hash function as was used for release 1.79.0. -== Open-addresing containers +== Open-addresing Containers The C++ standard specification of unordered associative containers impose severe limitations on permissible implementations, the most important being @@ -86,7 +86,7 @@ The design of `boost::unordered_flat_set`/`unordered_node_set` and `boost::unord guided by Peter Dimov's https://pdimov.github.io/articles/unordered_dev_plan.html[Development Plan for Boost.Unordered^]. We discuss here the most relevant principles. -=== Hash function +=== Hash Function Given its rich functionality and cross-platform interoperability, `boost::hash` remains the default hash function of open-addressing containers. @@ -105,7 +105,7 @@ whereas in 32 bits _C_ = 0xE817FB2Du has been obtained from https://arxiv.org/ab When using a hash function directly suitable for open addressing, post-mixing can be opted out by via a dedicated <>trait. `boost::hash` specializations for string types are marked as avalanching. -=== Platform interoperability +=== Platform Interoperability The observable behavior of `boost::unordered_flat_set`/`unordered_node_set` and `boost::unordered_flat_map`/`unordered_node_map` is deterministically identical across different compilers as long as their ``std::size_t``s are the same size and the user-provided @@ -118,7 +118,7 @@ and https://en.wikipedia.org/wiki/ARM_architecture_family#Advanced_SIMD_(NEON)[N this does not affect interoperatility. For instance, the behavior is the same for Visual Studio on an x64-mode Intel CPU with SSE2 and for GCC on an IBM s390x without any supported SIMD technology. -== Concurrent Hashmap +== Concurrent Containers The same data structure used by Boost.Unordered open-addressing containers has been chosen also as the foundation of `boost::concurrent_flat_map`: @@ -132,7 +132,7 @@ lookup that are lock-free up to the last step of actual element comparison. of all elements between `boost::concurrent_flat_map` and `boost::unordered_flat_map`. (This feature has not been implemented yet.) -=== Hash function and platform interoperability +=== Hash Function and Platform Interoperability `boost::concurrent_flat_map` makes the same decisions and provides the same guarantees as Boost.Unordered open-addressing containers with regards to diff --git a/doc/unordered/comparison.adoc b/doc/unordered/regular.adoc similarity index 56% rename from doc/unordered/comparison.adoc rename to doc/unordered/regular.adoc index 1d5dd97b..320c28c1 100644 --- a/doc/unordered/comparison.adoc +++ b/doc/unordered/regular.adoc @@ -1,8 +1,99 @@ +[#regular] += Regular Containers + +:idprefix: regular_ + +Boost.Unordered closed-addressing containers (`boost::unordered_set`, `boost::unordered_map`, +`boost::unordered_multiset` and `boost::unordered_multimap`) are fully conformant with the +C++ specification for unordered associative containers, so for those who know how to use +`std::unordered_set`, `std::unordered_map`, etc., their homonyms in Boost:Unordered are +drop-in replacements. The interface of open-addressing containers (`boost::unordered_node_set`, +`boost::unordered_node_map`, `boost::unordered_flat_set` and `boost::unordered_flat_map`) +is very similar, but they present some minor differences listed in the dedicated +xref:#compliance_open_addressing_containers[standard compliance section]. + + +For readers without previous experience with hash containers but familiar +with normal associatve containers (`std::set`, `std::map`, +`std::multiset` and `std::multimap`), Boost.Unordered containers are used in a similar manner: + +[source,cpp] +---- +typedef boost::unordered_map map; +map x; +x["one"] = 1; +x["two"] = 2; +x["three"] = 3; + +assert(x.at("one") == 1); +assert(x.find("missing") == x.end()); +---- + +But since the elements aren't ordered, the output of: + +[source,c++] +---- +for(const map::value_type& i: x) { + std::cout<> section. + +== Iterator Invalidation + +It is not specified how member functions other than `rehash` and `reserve` affect +the bucket count, although `insert` can only invalidate iterators +when the insertion causes the container's load to be greater than the maximum allowed. +For most implementations this means that `insert` will only +change the number of buckets when this happens. Iterators can be +invalidated by calls to `insert`, `rehash` and `reserve`. + +As for pointers and references, +they are never invalidated for node-based containers +(`boost::unordered_[multi]set`, `boost::unordered_[multi]map`, `boost::unordered_node_set`, `boost::unordered_node_map`), +but they will when rehashing occurs for +`boost::unordered_flat_set` and `boost::unordered_flat_map`: this is because +these containers store elements directly into their holding buckets, so +when allocating a new bucket array the elements must be transferred by means of move construction. + +In a similar manner to using `reserve` for ``vector``s, it can be a good idea +to call `reserve` before inserting a large number of elements. This will get +the expensive rehashing out of the way and let you store iterators, safe in +the knowledge that they won't be invalidated. If you are inserting `n` +elements into container `x`, you could first call: + +``` +x.reserve(n); +``` + +Note:: `reserve(n)` reserves space for at least `n` elements, allocating enough buckets +so as to not exceed the maximum load factor. ++ +Because the maximum load factor is defined as the number of elements divided by the total +number of available buckets, this function is logically equivalent to: ++ +``` +x.rehash(std::ceil(n / x.max_load_factor())) +``` ++ +See the <> on the `rehash` function. + [#comparison] :idprefix: comparison_ -= Comparison with Associative Containers +== Comparison with Associative Containers [caption=, title='Table {counter:table-counter} Interface differences'] [cols="1,1", frame=all, grid=rows] @@ -32,7 +123,7 @@ |`iterator`, `const_iterator` are of at least the forward category. |Iterators, pointers and references to the container's elements are never invalidated. -|<>. + +|<>. + **Node-based containers:** Pointers and references to the container's elements are never invalidated. + **Flat containers:** Pointers and references to the container's elements are invalidated when rehashing occurs. diff --git a/doc/unordered/structures.adoc b/doc/unordered/structures.adoc new file mode 100644 index 00000000..9859c39e --- /dev/null +++ b/doc/unordered/structures.adoc @@ -0,0 +1,179 @@ +[#structures] += Data Structures + +:idprefix: structures_ + +== Closed-addressing Containers + +++++ + +++++ + +Boost.Unordered sports one of the fastest implementations of closed addressing, also commonly known as https://en.wikipedia.org/wiki/Hash_table#Separate_chaining[separate chaining]. An example figure representing the data structure is below: + +[#img-bucket-groups,.text-center] +.A simple bucket group approach +image::bucket-groups.png[align=center] + +An array of "buckets" is allocated and each bucket in turn points to its own individual linked list. This makes meeting the standard requirements of bucket iteration straight-forward. Unfortunately, iteration of the entire container is often times slow using this layout as each bucket must be examined for occupancy, yielding a time complexity of `O(bucket_count() + size())` when the standard requires complexity to be `O(size())`. + +Canonical standard implementations will wind up looking like the diagram below: + +[.text-center] +.The canonical standard approach +image::singly-linked.png[align=center,link=../diagrams/singly-linked.png,window=_blank] + +It's worth noting that this approach is only used by pass:[libc++] and pass:[libstdc++]; the MSVC Dinkumware implementation uses a different one. A more detailed analysis of the standard containers can be found http://bannalia.blogspot.com/2013/10/implementation-of-c-unordered.html[here]. + +This unusually laid out data structure is chosen to make iteration of the entire container efficient by inter-connecting all of the nodes into a singly-linked list. One might also notice that buckets point to the node _before_ the start of the bucket's elements. This is done so that removing elements from the list can be done efficiently without introducing the need for a doubly-linked list. Unfortunately, this data structure introduces a guaranteed extra indirection. For example, to access the first element of a bucket, something like this must be done: + +```c++ +auto const idx = get_bucket_idx(hash_function(key)); +node* p = buckets[idx]; // first load +node* n = p->next; // second load +if (n && is_in_bucket(n, idx)) { + value_type const& v = *n; // third load + // ... +} +``` + +With a simple bucket group layout, this is all that must be done: +```c++ +auto const idx = get_bucket_idx(hash_function(key)); +node* n = buckets[idx]; // first load +if (n) { + value_type const& v = *n; // second load + // ... +} +``` + +In practice, the extra indirection can have a dramatic performance impact to common operations such as `insert`, `find` and `erase`. But to keep iteration of the container fast, Boost.Unordered introduces a novel data structure, a "bucket group". A bucket group is a fixed-width view of a subsection of the buckets array. It contains a bitmask (a `std::size_t`) which it uses to track occupancy of buckets and contains two pointers so that it can form a doubly-linked list with non-empty groups. An example diagram is below: + +[#img-fca-layout] +.The new layout used by Boost +image::fca.png[align=center] + +Thus container-wide iteration is turned into traversing the non-empty bucket groups (an operation with constant time complexity) which reduces the time complexity back to `O(size())`. In total, a bucket group is only 4 words in size and it views `sizeof(std::size_t) * CHAR_BIT` buckets meaning that for all common implementations, there's only 4 bits of space overhead per bucket introduced by the bucket groups. + +A more detailed description of Boost.Unordered's closed-addressing implementation is +given in an +https://bannalia.blogspot.com/2022/06/advancing-state-of-art-for.html[external article]. +For more information on implementation rationale, read the +xref:#rationale_closed_addressing_containers[corresponding section]. + +== Open-addressing Containers + +The diagram shows the basic internal layout of `boost::unordered_flat_map`/`unordered_node_map` and +`boost:unordered_flat_set`/`unordered_node_set`. + + +[#img-foa-layout] +.Open-addressing layout used by Boost.Unordered. +image::foa.png[align=center] + +As with all open-addressing containers, elements (or pointers to the element nodes in the case of +`boost::unordered_node_map` and `boost::unordered_node_set`) are stored directly in the bucket array. +This array is logically divided into 2^_n_^ _groups_ of 15 elements each. +In addition to the bucket array, there is an associated _metadata array_ with 2^_n_^ +16-byte words. + +[#img-foa-metadata] +.Breakdown of a metadata word. +image::foa-metadata.png[align=center] + +A metadata word is divided into 15 _h_~_i_~ bytes (one for each associated +bucket), and an _overflow byte_ (_ofw_ in the diagram). The value of _h_~_i_~ is: + + - 0 if the corresponding bucket is empty. + - 1 to encode a special empty bucket called a _sentinel_, which is used internally to + stop iteration when the container has been fully traversed. + - If the bucket is occupied, a _reduced hash value_ obtained from the hash value of + the element. + +When looking for an element with hash value _h_, SIMD technologies such as +https://en.wikipedia.org/wiki/SSE2[SSE2] and +https://en.wikipedia.org/wiki/ARM_architecture_family#Advanced_SIMD_(Neon)[Neon] allow us +to very quickly inspect the full metadata word and look for the reduced value of _h_ among all the +15 buckets with just a handful of CPU instructions: non-matching buckets can be +readily discarded, and those whose reduced hash value matches need be inspected via full +comparison with the corresponding element. If the looked-for element is not present, +the overflow byte is inspected: + +- If the bit in the position _h_ mod 8 is zero, lookup terminates (and the +element is not present). +- If the bit is set to 1 (the group has been _overflowed_), further groups are +checked using https://en.wikipedia.org/wiki/Quadratic_probing[_quadratic probing_], and +the process is repeated. + +Insertion is algorithmically similar: empty buckets are located using SIMD, +and when going past a full group its corresponding overflow bit is set to 1. + +In architectures without SIMD support, the logical layout stays the same, but the metadata +word is codified using a technique we call _bit interleaving_: this layout allows us +to emulate SIMD with reasonably good performance using only standard arithmetic and +logical operations. + +[#img-foa-metadata-interleaving] +.Bit-interleaved metadata word. +image::foa-metadata-interleaving.png[align=center] + +A more detailed description of Boost.Unordered's open-addressing implementation is +given in an +https://bannalia.blogspot.com/2022/11/inside-boostunorderedflatmap.html[external article]. +For more information on implementation rationale, read the +xref:#rationale_open_addresing_containers[corresponding section]. + +== Concurrent Containers + +`boost::concurrent_flat_map` uses the basic +xref:#structures_open_addressing_containers[open-addressing layout] described above +augmented with synchronization mechanisms. + + +[#img-cfoa-layout] +.Concurrent open-addressing layout used by Boost.Unordered. +image::cfoa.png[align=center] + +Two levels of synchronization are used: + +* Container level: A read-write mutex is used to control access from any operation +to the container. Typically, such access is in read mode (that is, concurrent) even +for modifying operations, so for most practical purposes there is no thread +contention at this level. Access is only in write mode (blocking) when rehashing or +performing container-wide operations such as swapping or assignment. +* Group level: Each 15-slot group is equipped with an 8-byte word containing: + ** A read-write spinlock for synchronized access to any element in the group. + ** An atomic _insertion counter_ used for optimistic insertion as described + below. + +By using atomic operations to access the group metadata, lookup is (group-level) +lock-free up to the point where an actual comparison needs to be done with an element +that has been previously SIMD-matched: only then it's the group's spinlock used. + +Insertion uses the following _optimistic algorithm_: + +* The value of the insertion counter for the initial group in the probe +sequence is locally recorded (let's call this value `c0`). +* Lookup is as described above. If lookup finds no equivalent element, +search for an available slot for insertion successively locks/unlocks +each group in the probing sequence. +* When an available slot is located, it is preemptively occupied (its +reduced hash value is set) and the insertion counter is atomically +incremented: if no other thread has incremented the counter during the +whole operation (which is checked by comparing with `c0`), then we're +good to go and complete the insertion, otherwise we roll back and start +over. + +This algorithm has very low contention both at the lookup and actual +insertion phases in exchange for the possibility that computations have +to be started over if some other thread interferes in the process by +performing a succesful insertion beginning at the same group. In +practice, the start-over frequency is extremely small, measured in the range +of parts per million for some of our benchmarks. + +For more information on implementation rationale, read the +xref:#rationale_concurrent_containers[corresponding section]. From f1bc948be8283c7f0deb7de6e163483628546f48 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 18 May 2023 13:14:58 -0700 Subject: [PATCH 257/327] Update table formatting in intro to use monospaced font --- doc/unordered/intro.adoc | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/doc/unordered/intro.adoc b/doc/unordered/intro.adoc index 74540002..511968b2 100644 --- a/doc/unordered/intro.adoc +++ b/doc/unordered/intro.adoc @@ -22,27 +22,28 @@ Boost.Unordered offers a catalog of hash containers with different standards com performances and intented usage scenarios: [caption=, title='Table {counter:table-counter}. Boost.Unordered containers'] -[cols="1,1,.^1", frame=all, grid=rows] +[cols="1,1,.^1", frame=all, grid=all] |=== ^h| ^h|*Node-based* ^h|*Flat* ^.^h|*Closed addressing* -^| `boost::unordered_set` + -`boost::unordered_map` + -`boost::unordered_multiset` + -`boost::unordered_multimap` -^| +^m| +boost::unordered_set + +boost::unordered_map + +boost::unordered_multiset + +boost::unordered_multimap +^| N/A ^.^h|*Open addressing* -^| `boost::unordered_node_set` + -`boost::unordered_node_map` -^| `boost::unordered_flat_set` + -`boost::unordered_flat_map` +^m| boost::unordered_node_set + +boost::unordered_node_map +^m| boost::unordered_flat_set + +boost::unordered_flat_map ^.^h|*Concurrent* -^| +^| N/A ^| `boost::concurrent_flat_map` |=== From a140de425421bef25b4f14b63e8e071b55a938b5 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 19 May 2023 10:51:00 +0200 Subject: [PATCH 258/327] typos/editorial --- doc/unordered/buckets.adoc | 8 ++------ doc/unordered/compliance.adoc | 2 +- doc/unordered/hash_equality.adoc | 6 +++--- doc/unordered/intro.adoc | 2 +- doc/unordered/regular.adoc | 4 ++-- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/doc/unordered/buckets.adoc b/doc/unordered/buckets.adoc index 7028ddce..e8b5a62f 100644 --- a/doc/unordered/buckets.adoc +++ b/doc/unordered/buckets.adoc @@ -52,8 +52,7 @@ h|*Method* h|*Description* |`size_type bucket_count() const` |The number of buckets. -2+^h| *Closed-addressing containers only* + -`boost::unordered_[multi]set`, `boost::unordered_[multi]map` +2+^h| *Closed-addressing containers only* h|*Method* h|*Description* |`size_type max_bucket_count() const` @@ -132,10 +131,7 @@ h|*Method* h|*Description* |`void rehash(size_type n)` |Changes the number of buckets so that there at least `n` buckets, and so that the load factor is less than the maximum load factor. -2+^h| *Open-addressing and concurrent containers only* + -`boost::unordered_flat_set`, `boost::unordered_flat_map` + -`boost::unordered_node_set`, `boost::unordered_node_map` + -`boost::concurrent_flat_map` +2+^h| *Open-addressing and concurrent containers only* h|*Method* h|*Description* |`size_type max_load() const` diff --git a/doc/unordered/compliance.adoc b/doc/unordered/compliance.adoc index 4a24801d..d5d84e9c 100644 --- a/doc/unordered/compliance.adoc +++ b/doc/unordered/compliance.adoc @@ -161,7 +161,7 @@ In a non-concurrent unordered container, iterators serve two main purposes: * Access to an element previously located via lookup. * Container traversal. -In place of iterators, `boost::unordered_flat_map` uses _internal visitation_ +In place of iterators, `boost::concurrent_flat_map` uses _internal visitation_ facilities as a thread-safe substitute. Classical operations returning an iterator to an element already existing in the container, like for instance: diff --git a/doc/unordered/hash_equality.adoc b/doc/unordered/hash_equality.adoc index dd6b2844..583e1173 100644 --- a/doc/unordered/hash_equality.adoc +++ b/doc/unordered/hash_equality.adoc @@ -20,14 +20,14 @@ class unordered_map; The hash function comes first as you might want to change the hash function but not the equality predicate. For example, if you wanted to use the -http://www.isthe.com/chongo/tech/comp/fnv/[FNV-1 hash^] you could write: +https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash[FNV-1a hash^] you could write: ``` -boost::unordered_map +boost::unordered_map dictionary; ``` -There is an link:../../examples/fnv1.hpp[implementation of FNV-1^] in the examples directory. +There is an link:../../examples/fnv1.hpp[implementation of FNV-1a^] in the examples directory. If you wish to use a different equality function, you will also need to use a matching hash function. For example, to implement a case insensitive dictionary you need to define a case insensitive equality predicate and hash function: diff --git a/doc/unordered/intro.adoc b/doc/unordered/intro.adoc index 511968b2..0a8a14a9 100644 --- a/doc/unordered/intro.adoc +++ b/doc/unordered/intro.adoc @@ -43,7 +43,7 @@ boost::unordered_node_map boost::unordered_flat_map ^.^h|*Concurrent* -^| N/A +^| ^| `boost::concurrent_flat_map` |=== diff --git a/doc/unordered/regular.adoc b/doc/unordered/regular.adoc index 320c28c1..206ffb47 100644 --- a/doc/unordered/regular.adoc +++ b/doc/unordered/regular.adoc @@ -6,7 +6,7 @@ Boost.Unordered closed-addressing containers (`boost::unordered_set`, `boost::unordered_map`, `boost::unordered_multiset` and `boost::unordered_multimap`) are fully conformant with the C++ specification for unordered associative containers, so for those who know how to use -`std::unordered_set`, `std::unordered_map`, etc., their homonyms in Boost:Unordered are +`std::unordered_set`, `std::unordered_map`, etc., their homonyms in Boost.Unordered are drop-in replacements. The interface of open-addressing containers (`boost::unordered_node_set`, `boost::unordered_node_map`, `boost::unordered_flat_set` and `boost::unordered_flat_map`) is very similar, but they present some minor differences listed in the dedicated @@ -62,7 +62,7 @@ invalidated by calls to `insert`, `rehash` and `reserve`. As for pointers and references, they are never invalidated for node-based containers (`boost::unordered_[multi]set`, `boost::unordered_[multi]map`, `boost::unordered_node_set`, `boost::unordered_node_map`), -but they will when rehashing occurs for +but they will be when rehashing occurs for `boost::unordered_flat_set` and `boost::unordered_flat_map`: this is because these containers store elements directly into their holding buckets, so when allocating a new bucket array the elements must be transferred by means of move construction. From 528f7d4b127c64712970d87060f67c78ae11178c Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 19 May 2023 11:17:56 +0200 Subject: [PATCH 259/327] title cased some sections --- doc/unordered/concurrent_flat_map.adoc | 2 +- doc/unordered/unordered_flat_map.adoc | 2 +- doc/unordered/unordered_flat_set.adoc | 2 +- doc/unordered/unordered_map.adoc | 2 +- doc/unordered/unordered_multimap.adoc | 2 +- doc/unordered/unordered_multiset.adoc | 2 +- doc/unordered/unordered_node_map.adoc | 2 +- doc/unordered/unordered_node_set.adoc | 2 +- doc/unordered/unordered_set.adoc | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc index a83dc635..a5e458f2 100644 --- a/doc/unordered/concurrent_flat_map.adoc +++ b/doc/unordered/concurrent_flat_map.adoc @@ -1,5 +1,5 @@ [#concurrent_flat_map] -== Class template concurrent_flat_map +== Class Template concurrent_flat_map :idprefix: concurrent_flat_map_ diff --git a/doc/unordered/unordered_flat_map.adoc b/doc/unordered/unordered_flat_map.adoc index e1b27499..de45d9df 100644 --- a/doc/unordered/unordered_flat_map.adoc +++ b/doc/unordered/unordered_flat_map.adoc @@ -1,5 +1,5 @@ [#unordered_flat_map] -== Class template unordered_flat_map +== Class Template unordered_flat_map :idprefix: unordered_flat_map_ diff --git a/doc/unordered/unordered_flat_set.adoc b/doc/unordered/unordered_flat_set.adoc index 7d3d69f8..8a4bb419 100644 --- a/doc/unordered/unordered_flat_set.adoc +++ b/doc/unordered/unordered_flat_set.adoc @@ -1,5 +1,5 @@ [#unordered_flat_set] -== Class template unordered_flat_set +== Class Template unordered_flat_set :idprefix: unordered_flat_set_ diff --git a/doc/unordered/unordered_map.adoc b/doc/unordered/unordered_map.adoc index 2cc08369..aec65561 100644 --- a/doc/unordered/unordered_map.adoc +++ b/doc/unordered/unordered_map.adoc @@ -1,5 +1,5 @@ [#unordered_map] -== Class template unordered_map +== Class Template unordered_map :idprefix: unordered_map_ diff --git a/doc/unordered/unordered_multimap.adoc b/doc/unordered/unordered_multimap.adoc index ab5ccbbb..d04a990e 100644 --- a/doc/unordered/unordered_multimap.adoc +++ b/doc/unordered/unordered_multimap.adoc @@ -1,5 +1,5 @@ [#unordered_multimap] -== Class template unordered_multimap +== Class Template unordered_multimap :idprefix: unordered_multimap_ diff --git a/doc/unordered/unordered_multiset.adoc b/doc/unordered/unordered_multiset.adoc index 2495e570..deb09821 100644 --- a/doc/unordered/unordered_multiset.adoc +++ b/doc/unordered/unordered_multiset.adoc @@ -1,5 +1,5 @@ [#unordered_multiset] -== Class template unordered_multiset +== Class Template unordered_multiset :idprefix: unordered_multiset_ diff --git a/doc/unordered/unordered_node_map.adoc b/doc/unordered/unordered_node_map.adoc index 6a43bb92..3bae34bc 100644 --- a/doc/unordered/unordered_node_map.adoc +++ b/doc/unordered/unordered_node_map.adoc @@ -1,5 +1,5 @@ [#unordered_node_map] -== Class template unordered_node_map +== Class Template unordered_node_map :idprefix: unordered_node_map_ diff --git a/doc/unordered/unordered_node_set.adoc b/doc/unordered/unordered_node_set.adoc index a15de04f..0484e4a4 100644 --- a/doc/unordered/unordered_node_set.adoc +++ b/doc/unordered/unordered_node_set.adoc @@ -1,5 +1,5 @@ [#unordered_node_set] -== Class template unordered_node_set +== Class Template unordered_node_set :idprefix: unordered_node_set_ diff --git a/doc/unordered/unordered_set.adoc b/doc/unordered/unordered_set.adoc index 8530b4d4..f039a5f6 100644 --- a/doc/unordered/unordered_set.adoc +++ b/doc/unordered/unordered_set.adoc @@ -1,5 +1,5 @@ [#unordered_set] -== Class template unordered_set +== Class Template unordered_set :idprefix: unordered_set_ From f28527c4d88ab7932fa7e4c7e784e8f6864079d5 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 19 May 2023 11:28:08 +0200 Subject: [PATCH 260/327] removed double separating line --- doc/unordered/concurrent_flat_map.adoc | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc index a5e458f2..b3c594d6 100644 --- a/doc/unordered/concurrent_flat_map.adoc +++ b/doc/unordered/concurrent_flat_map.adoc @@ -1436,5 +1436,3 @@ Equivalent to ----- c.xref:#concurrent_flat_map_erase_if[erase_if](pred); ----- - ---- From ffcae204eea4df7d052fd0810e0ffd4d2e62cfc2 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 19 May 2023 12:33:14 -0700 Subject: [PATCH 261/327] Add insert_exception_tests --- test/Jamfile.v2 | 1 + test/cfoa/exception_helpers.hpp | 448 +++++++++++++++++++++ test/cfoa/exception_insert_tests.cpp | 566 +++++++++++++++++++++++++++ 3 files changed, 1015 insertions(+) create mode 100644 test/cfoa/exception_helpers.hpp create mode 100644 test/cfoa/exception_insert_tests.cpp diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index a84bc78f..8cf8e40c 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -190,6 +190,7 @@ local CFOA_TESTS = rehash_tests equality_tests fwd_tests + exception_insert_tests ; for local test in $(CFOA_TESTS) diff --git a/test/cfoa/exception_helpers.hpp b/test/cfoa/exception_helpers.hpp new file mode 100644 index 00000000..9c82e506 --- /dev/null +++ b/test/cfoa/exception_helpers.hpp @@ -0,0 +1,448 @@ +// 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 "latch.hpp" + +#include "../helpers/generators.hpp" +#include "../helpers/test.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static std::size_t const num_threads = + std::max(2u, std::thread::hardware_concurrency()); + +std::atomic_bool should_throw{false}; + +constexpr std::uint32_t threshold = 2500; + +void enable_exceptions() { should_throw = true; } +void disable_exceptions() { should_throw = false; } + +struct exception_tag +{ +}; + +struct stateful_hash +{ + int x_ = -1; + + static std::atomic c; + + void throw_helper() const + { + ++c; + if (should_throw && (c % threshold == 0)) { + throw exception_tag{}; + } + } + + stateful_hash() {} + stateful_hash(stateful_hash const& rhs) : x_(rhs.x_) {} + stateful_hash(stateful_hash&& rhs) noexcept + { + auto tmp = x_; + x_ = rhs.x_; + rhs.x_ = tmp; + } + + stateful_hash(int const x) : x_{x} {} + + template std::size_t operator()(T const& t) const + { + throw_helper(); + std::size_t h = static_cast(x_); + boost::hash_combine(h, t); + return h; + } + + bool operator==(stateful_hash const& rhs) const { return x_ == rhs.x_; } + + friend std::ostream& operator<<(std::ostream& os, stateful_hash const& rhs) + { + os << "{ x_: " << rhs.x_ << " }"; + return os; + } + + friend void swap(stateful_hash& lhs, stateful_hash& rhs) noexcept + { + if (&lhs != &rhs) { + std::swap(lhs.x_, rhs.x_); + } + } +}; + +std::atomic stateful_hash::c{0}; + +struct stateful_key_equal +{ + int x_ = -1; + static std::atomic c; + + void throw_helper() const + { + ++c; + if (should_throw && (c % threshold == 0)) { + throw exception_tag{}; + } + } + + stateful_key_equal() = default; + stateful_key_equal(stateful_key_equal const&) = default; + stateful_key_equal(stateful_key_equal&& rhs) noexcept + { + auto tmp = x_; + x_ = rhs.x_; + rhs.x_ = tmp; + } + + stateful_key_equal(int const x) : x_{x} {} + + template bool operator()(T const& t, U const& u) const + { + throw_helper(); + return t == u; + } + + bool operator==(stateful_key_equal const& rhs) const { return x_ == rhs.x_; } + + friend std::ostream& operator<<( + std::ostream& os, stateful_key_equal const& rhs) + { + os << "{ x_: " << rhs.x_ << " }"; + return os; + } + + friend void swap(stateful_key_equal& lhs, stateful_key_equal& rhs) noexcept + { + if (&lhs != &rhs) { + std::swap(lhs.x_, rhs.x_); + } + } +}; +std::atomic stateful_key_equal::c{0}; + +template struct stateful_allocator +{ + int x_ = -1; + static std::atomic c; + + void throw_helper() const + { + ++c; + if (should_throw && (c % threshold == 0)) { + throw exception_tag{}; + } + } + + using value_type = T; + + stateful_allocator() = default; + stateful_allocator(stateful_allocator const&) = default; + stateful_allocator(stateful_allocator&&) = default; + + stateful_allocator(int const x) : x_{x} {} + + template + stateful_allocator(stateful_allocator const& rhs) : x_{rhs.x_} + { + } + + T* allocate(std::size_t n) + { + throw_helper(); + return static_cast(::operator new(n * sizeof(T))); + } + + void deallocate(T* p, std::size_t) { ::operator delete(p); } + + bool operator==(stateful_allocator const& rhs) const { return x_ == rhs.x_; } + bool operator!=(stateful_allocator const& rhs) const { return x_ != rhs.x_; } +}; + +template std::atomic stateful_allocator::c{0}; + +struct raii +{ + static std::atomic default_constructor; + static std::atomic copy_constructor; + static std::atomic move_constructor; + static std::atomic destructor; + + static std::atomic copy_assignment; + static std::atomic move_assignment; + + static std::atomic c; + void throw_helper() const + { + ++c; + if (should_throw && (c % threshold == 0)) { + throw exception_tag{}; + } + } + + int x_ = -1; + + raii() + { + throw_helper(); + ++default_constructor; + } + + raii(int const x) : x_{x} + { + throw_helper(); + ++default_constructor; + } + + raii(raii const& rhs) : x_{rhs.x_} + { + throw_helper(); + ++copy_constructor; + } + raii(raii&& rhs) noexcept : x_{rhs.x_} + { + rhs.x_ = -1; + ++move_constructor; + } + ~raii() { ++destructor; } + + raii& operator=(raii const& rhs) + { + throw_helper(); + ++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; + } + + 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); + } + + friend bool operator==(raii const& lhs, int const x) { return lhs.x_ == x; } + friend bool operator!=(raii const& lhs, int const x) + { + return !(lhs.x_ == x); + } + + friend bool operator==(int const x, raii const& rhs) { return rhs.x_ == x; } + + friend bool operator!=(int const x, raii const& rhs) + { + return !(rhs.x_ == x); + } + + 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; + } + + static void reset_counts() + { + default_constructor = 0; + copy_constructor = 0; + move_constructor = 0; + destructor = 0; + copy_assignment = 0; + move_assignment = 0; + c = 0; + + stateful_hash::c = 0; + stateful_key_equal::c = 0; + stateful_allocator::c = 0; + } + + friend void swap(raii& lhs, raii& rhs) { std::swap(lhs.x_, rhs.x_); } +}; + +std::atomic raii::default_constructor{0}; +std::atomic raii::copy_constructor{0}; +std::atomic raii::move_constructor{0}; +std::atomic raii::destructor{0}; +std::atomic raii::copy_assignment{0}; +std::atomic raii::move_assignment{0}; +std::atomic raii::c{0}; + +std::size_t hash_value(raii const& r) noexcept +{ + boost::hash hasher; + return hasher(r.x_); +} + +struct exception_value_type_generator_type +{ + std::pair operator()(test::random_generator rg) + { + int* p = nullptr; + int a = generate(p, rg); + int b = generate(p, rg); + return std::make_pair(raii{a}, raii{b}); + } +} exception_value_type_generator; + +struct exception_init_type_generator_type +{ + std::pair operator()(test::random_generator rg) + { + int* p = nullptr; + int a = generate(p, rg); + int b = generate(p, rg); + return std::make_pair(raii{a}, raii{b}); + } +} exception_init_type_generator; + +template +std::vector > split( + boost::span s, std::size_t const nt /* num threads*/) +{ + std::vector > subslices; + subslices.reserve(nt); + + auto a = s.size() / nt; + auto b = a; + if (s.size() % nt != 0) { + ++b; + } + + auto num_a = nt; + auto num_b = std::size_t{0}; + + if (nt * b > s.size()) { + num_a = nt * b - s.size(); + num_b = nt - num_a; + } + + auto sub_b = s.subspan(0, num_b * b); + auto sub_a = s.subspan(num_b * b); + + for (std::size_t i = 0; i < num_b; ++i) { + subslices.push_back(sub_b.subspan(i * b, b)); + } + + for (std::size_t i = 0; i < num_a; ++i) { + auto const is_last = i == (num_a - 1); + subslices.push_back( + sub_a.subspan(i * a, is_last ? boost::dynamic_extent : a)); + } + + return subslices; +} + +template void thread_runner(std::vector& values, F f) +{ + boost::latch latch(static_cast(num_threads)); + + std::vector threads; + auto subslices = split(values, num_threads); + + for (std::size_t i = 0; i < num_threads; ++i) { + threads.emplace_back([&f, &subslices, i, &latch] { + latch.arrive_and_wait(); + + auto s = subslices[i]; + f(s); + }); + } + + for (auto& t : threads) { + t.join(); + } +} + +template +void test_matches_reference(X const& x, Y const& reference_map) +{ + using value_type = typename X::value_type; + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second); + })); +} + +template +void test_fuzzy_matches_reference( + X const& x, Y const& reference_map, test::random_generator rg) +{ + using value_type = typename X::value_type; + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + if (rg == test::sequential) { + BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second); + } + })); +} + +template using span_value_type = typename T::value_type; + +void check_raii_counts() +{ + BOOST_TEST_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); + + BOOST_TEST_EQ( + raii::default_constructor + raii::copy_constructor + raii::move_constructor, + raii::destructor); +} + +template void shuffle_values(std::vector& v) +{ + std::random_device rd; + std::mt19937 g(rd()); + + std::shuffle(v.begin(), v.end(), g); +} + +template +auto make_random_values(std::size_t count, F f) -> std::vector +{ + using vector_type = std::vector; + + vector_type v; + v.reserve(count); + for (std::size_t i = 0; i < count; ++i) { + v.emplace_back(f()); + } + return v; +} diff --git a/test/cfoa/exception_insert_tests.cpp b/test/cfoa/exception_insert_tests.cpp new file mode 100644 index 00000000..533243bf --- /dev/null +++ b/test/cfoa/exception_insert_tests.cpp @@ -0,0 +1,566 @@ +// 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 "exception_helpers.hpp" + +#include + +#include + +namespace { + test::seed_t initialize_seed(73987); + + struct lvalue_inserter_type + { + template void operator()(std::vector& values, X& x) + { + enable_exceptions(); + + std::atomic num_inserts{0}; + thread_runner(values, [&x, &num_inserts](boost::span s) { + for (auto const& r : s) { + try { + bool b = x.insert(r); + if (b) { + ++num_inserts; + } + } catch (...) { + } + } + }); + + disable_exceptions(); + + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } lvalue_inserter; + + struct norehash_lvalue_inserter_type : public lvalue_inserter_type + { + template void operator()(std::vector& values, X& x) + { + x.reserve(values.size()); + lvalue_inserter_type::operator()(values, x); + BOOST_TEST_GT(raii::copy_constructor, 0u); + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + } norehash_lvalue_inserter; + + struct rvalue_inserter_type + { + template void operator()(std::vector& values, X& x) + { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + + enable_exceptions(); + + std::atomic num_inserts{0}; + thread_runner(values, [&x, &num_inserts](boost::span s) { + for (auto& r : s) { + try { + bool b = x.insert(std::move(r)); + if (b) { + ++num_inserts; + } + } catch (...) { + } + } + }); + + disable_exceptions(); + + if (!std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + } + + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } rvalue_inserter; + + struct norehash_rvalue_inserter_type : public rvalue_inserter_type + { + template void operator()(std::vector& values, X& x) + { + x.reserve(values.size()); + + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_EQ(raii::move_constructor, 0u); + + rvalue_inserter_type::operator()(values, x); + + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_EQ(raii::move_constructor, x.size()); + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_EQ(raii::move_constructor, 2 * x.size()); + } + } + } norehash_rvalue_inserter; + + struct iterator_range_inserter_type + { + template void operator()(std::vector& values, X& x) + { + enable_exceptions(); + thread_runner(values, [&x](boost::span s) { + try { + x.insert(s.begin(), s.end()); + } catch (...) { + } + }); + disable_exceptions(); + + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } iterator_range_inserter; + + struct lvalue_insert_or_assign_copy_assign_type + { + template void operator()(std::vector& values, X& x) + { + enable_exceptions(); + thread_runner(values, [&x](boost::span s) { + for (auto& r : s) { + try { + x.insert_or_assign(r.first, r.second); + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_GT(raii::copy_constructor, 0u); + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } lvalue_insert_or_assign_copy_assign; + + struct lvalue_insert_or_assign_move_assign_type + { + template void operator()(std::vector& values, X& x) + { + enable_exceptions(); + thread_runner(values, [&x](boost::span s) { + for (auto& r : s) { + try { + + x.insert_or_assign(r.first, std::move(r.second)); + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_GT(raii::copy_constructor, 0u); + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + } + } lvalue_insert_or_assign_move_assign; + + struct rvalue_insert_or_assign_copy_assign_type + { + template void operator()(std::vector& values, X& x) + { + enable_exceptions(); + thread_runner(values, [&x](boost::span s) { + for (auto& r : s) { + try { + x.insert_or_assign(std::move(r.first), r.second); + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_GT(raii::copy_constructor, 0u); + BOOST_TEST_GT(raii::move_constructor, x.size()); // rehashing + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } rvalue_insert_or_assign_copy_assign; + + struct rvalue_insert_or_assign_move_assign_type + { + template void operator()(std::vector& values, X& x) + { + enable_exceptions(); + thread_runner(values, [&x](boost::span s) { + for (auto& r : s) { + try { + x.insert_or_assign(std::move(r.first), std::move(r.second)); + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + } + } rvalue_insert_or_assign_move_assign; + + struct lvalue_insert_or_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + + enable_exceptions(); + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + try { + bool b = x.insert_or_cvisit( + r, [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_GT(num_inserts, 0u); + BOOST_TEST_EQ(raii::default_constructor, 0u); + // don't check move construction count here because of rehashing + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } lvalue_insert_or_cvisit; + + struct lvalue_insert_or_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + + enable_exceptions(); + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + try { + bool b = + x.insert_or_visit(r, [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_GT(num_inserts, 0u); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + + // don't check move construction count here because of rehashing + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } lvalue_insert_or_visit; + + struct rvalue_insert_or_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + + enable_exceptions(); + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + try { + bool b = x.insert_or_cvisit( + std::move(r), [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_GT(num_inserts, 0u); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + } + } rvalue_insert_or_cvisit; + + struct rvalue_insert_or_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + + enable_exceptions(); + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + try { + bool b = x.insert_or_visit( + std::move(r), [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_GT(num_inserts, 0u); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + if (!std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + } + } + } rvalue_insert_or_visit; + + struct iterator_range_insert_or_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_invokes{0}; + + enable_exceptions(); + thread_runner(values, [&x, &num_invokes](boost::span s) { + try { + x.insert_or_cvisit(s.begin(), s.end(), + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + } catch (...) { + } + }); + disable_exceptions(); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_GT(raii::move_constructor, 0u); + } + } iterator_range_insert_or_cvisit; + + struct iterator_range_insert_or_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_invokes{0}; + + enable_exceptions(); + thread_runner(values, [&x, &num_invokes](boost::span s) { + try { + x.insert_or_visit(s.begin(), s.end(), + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + } catch (...) { + } + }); + disable_exceptions(); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_GT(raii::move_constructor, 0u); + } + } iterator_range_insert_or_visit; + + template + void insert(X*, G gen, F inserter, test::random_generator rg) + { + disable_exceptions(); + + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + X x; + + inserter(values, x); + + test_fuzzy_matches_reference(x, reference_map, rg); + } + + BOOST_TEST_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); + + BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + + raii::move_constructor, + raii::destructor); + } + + template void insert_initializer_list(X*) + { + using value_type = typename X::value_type; + + std::initializer_list values{ + value_type{raii{0}, raii{0}}, + value_type{raii{1}, raii{1}}, + value_type{raii{2}, raii{2}}, + value_type{raii{3}, raii{3}}, + value_type{raii{4}, raii{4}}, + value_type{raii{5}, raii{5}}, + value_type{raii{6}, raii{6}}, + value_type{raii{6}, raii{6}}, + value_type{raii{7}, raii{7}}, + value_type{raii{8}, raii{8}}, + value_type{raii{9}, raii{9}}, + value_type{raii{10}, raii{10}}, + value_type{raii{9}, raii{9}}, + value_type{raii{8}, raii{8}}, + value_type{raii{7}, raii{7}}, + value_type{raii{6}, raii{6}}, + value_type{raii{5}, raii{5}}, + value_type{raii{4}, raii{4}}, + value_type{raii{3}, raii{3}}, + value_type{raii{2}, raii{2}}, + value_type{raii{1}, raii{1}}, + value_type{raii{0}, raii{0}}, + }; + + std::vector dummy; + + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + { + X x; + + thread_runner( + dummy, [&x, &values](boost::span) { x.insert(values); }); + + BOOST_TEST_EQ(x.size(), reference_map.size()); + + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map[kv.first]); + })); + } + + BOOST_TEST_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); + + BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + + raii::move_constructor, + raii::destructor); + + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + + { + { + std::atomic num_invokes{0}; + + X x; + + thread_runner(dummy, [&x, &values, &num_invokes](boost::span) { + x.insert_or_visit(values, [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }); + + x.insert_or_cvisit( + values, [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + }); + + BOOST_TEST_EQ(num_invokes, (values.size() - x.size()) + + (num_threads - 1) * values.size() + + num_threads * values.size()); + BOOST_TEST_EQ(x.size(), reference_map.size()); + + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map[kv.first]); + })); + } + + BOOST_TEST_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); + + BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + + raii::move_constructor, + raii::destructor); + + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } + + boost::unordered::concurrent_flat_map > >* map; + +} // namespace + +using test::default_generator; +using test::limited_range; +using test::sequential; + +// clang-format off +UNORDERED_TEST( + insert_initializer_list, + ((map))) + +UNORDERED_TEST( + insert, + ((map)) + ((exception_value_type_generator)(exception_init_type_generator)) + ((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter) + (norehash_lvalue_inserter)(norehash_rvalue_inserter) + (lvalue_insert_or_cvisit)(lvalue_insert_or_visit) + (rvalue_insert_or_cvisit)(rvalue_insert_or_visit) + (iterator_range_insert_or_cvisit)(iterator_range_insert_or_visit)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + insert, + ((map)) + ((exception_init_type_generator)) + ((lvalue_insert_or_assign_copy_assign)(lvalue_insert_or_assign_move_assign) + (rvalue_insert_or_assign_copy_assign)(rvalue_insert_or_assign_move_assign)) + ((default_generator)(sequential)(limited_range))) + +// clang-format on + +RUN_TESTS() From ddb1148a3113871c88952f2097f5a97ea2fca1f1 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 20 May 2023 12:16:30 +0200 Subject: [PATCH 262/327] reformulated static member initialization to appease VS2015 --- test/cfoa/exception_helpers.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cfoa/exception_helpers.hpp b/test/cfoa/exception_helpers.hpp index 9c82e506..0fd63833 100644 --- a/test/cfoa/exception_helpers.hpp +++ b/test/cfoa/exception_helpers.hpp @@ -174,7 +174,7 @@ template struct stateful_allocator bool operator!=(stateful_allocator const& rhs) const { return x_ != rhs.x_; } }; -template std::atomic stateful_allocator::c{0}; +template std::atomic stateful_allocator::c = {}; struct raii { From 96f5983f888aeebc818f8202e92bd85d9275dfff Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 21 May 2023 12:39:02 +0200 Subject: [PATCH 263/327] fixed initializer_list insert requirements --- doc/unordered/unordered_flat_map.adoc | 2 +- doc/unordered/unordered_flat_set.adoc | 2 +- doc/unordered/unordered_map.adoc | 2 +- doc/unordered/unordered_multimap.adoc | 2 +- doc/unordered/unordered_multiset.adoc | 2 +- doc/unordered/unordered_node_map.adoc | 2 +- doc/unordered/unordered_node_set.adoc | 2 +- doc/unordered/unordered_set.adoc | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/unordered/unordered_flat_map.adoc b/doc/unordered/unordered_flat_map.adoc index de45d9df..4ea094ab 100644 --- a/doc/unordered/unordered_flat_map.adoc +++ b/doc/unordered/unordered_flat_map.adoc @@ -859,7 +859,7 @@ void insert(std::initializer_list); Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key. [horizontal] -Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into the container from `*first`. +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container. Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect. Notes:;; Can invalidate iterators, pointers and references, but only if the insert causes the load to be greater than the maximum load. diff --git a/doc/unordered/unordered_flat_set.adoc b/doc/unordered/unordered_flat_set.adoc index 8a4bb419..1e14fdf0 100644 --- a/doc/unordered/unordered_flat_set.adoc +++ b/doc/unordered/unordered_flat_set.adoc @@ -837,7 +837,7 @@ void insert(std::initializer_list); Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key. [horizontal] -Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into the container from `*first`. +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container. Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect. Notes:;; Can invalidate iterators, pointers and references, but only if the insert causes the load to be greater than the maximum load. diff --git a/doc/unordered/unordered_map.adoc b/doc/unordered/unordered_map.adoc index aec65561..f3723cbc 100644 --- a/doc/unordered/unordered_map.adoc +++ b/doc/unordered/unordered_map.adoc @@ -995,7 +995,7 @@ void insert(std::initializer_list); Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key. [horizontal] -Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into `X` from `*first`. +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container. Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect. Notes:;; Can invalidate iterators, but only if the insert causes the load factor to be greater to or equal to the maximum load factor. + + diff --git a/doc/unordered/unordered_multimap.adoc b/doc/unordered/unordered_multimap.adoc index d04a990e..7adcf659 100644 --- a/doc/unordered/unordered_multimap.adoc +++ b/doc/unordered/unordered_multimap.adoc @@ -941,7 +941,7 @@ void insert(std::initializer_list il); Inserts a range of elements into the container. [horizontal] -Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into `X` from `*first`. +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container. Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect. Notes:;; Can invalidate iterators, but only if the insert causes the load factor to be greater to or equal to the maximum load factor. + + diff --git a/doc/unordered/unordered_multiset.adoc b/doc/unordered/unordered_multiset.adoc index deb09821..94af7526 100644 --- a/doc/unordered/unordered_multiset.adoc +++ b/doc/unordered/unordered_multiset.adoc @@ -899,7 +899,7 @@ void insert(std::initializer_list il); Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key. [horizontal] -Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into `X` from `*first`. +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container. Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect. Notes:;; Can invalidate iterators, but only if the insert causes the load factor to be greater to or equal to the maximum load factor. + + diff --git a/doc/unordered/unordered_node_map.adoc b/doc/unordered/unordered_node_map.adoc index 3bae34bc..36bd588f 100644 --- a/doc/unordered/unordered_node_map.adoc +++ b/doc/unordered/unordered_node_map.adoc @@ -893,7 +893,7 @@ void insert(std::initializer_list); Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key. [horizontal] -Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into the container from `*first`. +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container. Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect. Notes:;; Can invalidate iterators, but only if the insert causes the load to be greater than the maximum load. diff --git a/doc/unordered/unordered_node_set.adoc b/doc/unordered/unordered_node_set.adoc index 0484e4a4..94cd5f92 100644 --- a/doc/unordered/unordered_node_set.adoc +++ b/doc/unordered/unordered_node_set.adoc @@ -874,7 +874,7 @@ void insert(std::initializer_list); Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key. [horizontal] -Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into the container from `*first`. +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container. Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect. Notes:;; Can invalidate iterators, but only if the insert causes the load to be greater than the maximum load. diff --git a/doc/unordered/unordered_set.adoc b/doc/unordered/unordered_set.adoc index f039a5f6..20a72cd1 100644 --- a/doc/unordered/unordered_set.adoc +++ b/doc/unordered/unordered_set.adoc @@ -959,7 +959,7 @@ void insert(std::initializer_list); Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key. [horizontal] -Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into `X` from `*first`. +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container. Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect. Notes:;; Can invalidate iterators, but only if the insert causes the load factor to be greater to or equal to the maximum load factor. + + From 2a28698c8c56e7d9f5dd1bb2957bd562de34e747 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 21 May 2023 12:43:45 +0200 Subject: [PATCH 264/327] editorial --- doc/unordered/unordered_flat_map.adoc | 4 ++-- doc/unordered/unordered_map.adoc | 4 ++-- doc/unordered/unordered_node_map.adoc | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/unordered/unordered_flat_map.adoc b/doc/unordered/unordered_flat_map.adoc index 4ea094ab..3dc7ba41 100644 --- a/doc/unordered/unordered_flat_map.adoc +++ b/doc/unordered/unordered_flat_map.adoc @@ -875,7 +875,7 @@ template std::pair try_emplace(K&& k, Args&&... args); ``` -Inserts a new node into the container if there is no existing element with key `k` contained within it. +Inserts a new element into the container if there is no existing element with key `k` contained within it. If there is an existing element with key `k` this function does nothing. @@ -920,7 +920,7 @@ template iterator try_emplace(const_iterator hint, K&& k, Args&&... args); ``` -Inserts a new node into the container if there is no existing element with key `k` contained within it. +Inserts a new element into the container if there is no existing element with key `k` contained within it. If there is an existing element with key `k` this function does nothing. diff --git a/doc/unordered/unordered_map.adoc b/doc/unordered/unordered_map.adoc index f3723cbc..1332cebe 100644 --- a/doc/unordered/unordered_map.adoc +++ b/doc/unordered/unordered_map.adoc @@ -1013,7 +1013,7 @@ template std::pair try_emplace(K&& k, Args&&... args) ``` -Inserts a new node into the container if there is no existing element with key `k` contained within it. +Inserts a new element into the container if there is no existing element with key `k` contained within it. If there is an existing element with key `k` this function does nothing. @@ -1062,7 +1062,7 @@ template iterator try_emplace(const_iterator hint, K&& k, Args&&... args); ``` -Inserts a new node into the container if there is no existing element with key `k` contained within it. +Inserts a new element into the container if there is no existing element with key `k` contained within it. If there is an existing element with key `k` this function does nothing. diff --git a/doc/unordered/unordered_node_map.adoc b/doc/unordered/unordered_node_map.adoc index 36bd588f..38fb1c00 100644 --- a/doc/unordered/unordered_node_map.adoc +++ b/doc/unordered/unordered_node_map.adoc @@ -945,7 +945,7 @@ template std::pair try_emplace(K&& k, Args&&... args); ``` -Inserts a new node into the container if there is no existing element with key `k` contained within it. +Inserts a new element into the container if there is no existing element with key `k` contained within it. If there is an existing element with key `k` this function does nothing. @@ -990,7 +990,7 @@ template iterator try_emplace(const_iterator hint, K&& k, Args&&... args); ``` -Inserts a new node into the container if there is no existing element with key `k` contained within it. +Inserts a new element into the container if there is no existing element with key `k` contained within it. If there is an existing element with key `k` this function does nothing. From 8865a940fc28b9c3f280d7d6e15f534dda198516 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 21 May 2023 12:55:23 +0200 Subject: [PATCH 265/327] editorial --- doc/unordered/concurrent_flat_map.adoc | 10 +++++----- doc/unordered/unordered_flat_map.adoc | 12 ++++++------ doc/unordered/unordered_flat_set.adoc | 8 ++++---- doc/unordered/unordered_map.adoc | 14 +++++++------- doc/unordered/unordered_multimap.adoc | 8 ++++---- doc/unordered/unordered_multiset.adoc | 8 ++++---- doc/unordered/unordered_node_map.adoc | 12 ++++++------ doc/unordered/unordered_node_set.adoc | 8 ++++---- doc/unordered/unordered_set.adoc | 8 ++++---- 9 files changed, 44 insertions(+), 44 deletions(-) diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc index b3c594d6..fcdc8662 100644 --- a/doc/unordered/concurrent_flat_map.adoc +++ b/doc/unordered/concurrent_flat_map.adoc @@ -679,7 +679,7 @@ Such reference is const iff `*this` is const. [horizontal] Returns:;; The number of elements visited (0 or 1). -Notes:;; The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -975,7 +975,7 @@ unlike xref:#concurrent_flat_map_emplace[emplace], which simply forwards all arg Invalidates pointers and references to elements if a rehashing is issued. -The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. -- @@ -1023,7 +1023,7 @@ Invalidates pointers and references to elements if a rehashing is issued. The interface is exposition only, as C++ does not allow to declare a parameter `f` after a variadic parameter pack. -The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. -- @@ -1214,7 +1214,7 @@ template [horizontal] Returns:;; The number of elements with key equivalent to `k` (0 or 1). -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + + In the presence of concurrent insertion operations, the value returned may not accurately reflect the true state of the table right after execution. @@ -1230,7 +1230,7 @@ template [horizontal] Returns:;; A boolean indicating whether or not there is an element with key equal to `k` in the table. -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + + In the presence of concurrent insertion operations, the value returned may not accurately reflect the true state of the table right after execution. diff --git a/doc/unordered/unordered_flat_map.adoc b/doc/unordered/unordered_flat_map.adoc index 3dc7ba41..cda66533 100644 --- a/doc/unordered/unordered_flat_map.adoc +++ b/doc/unordered/unordered_flat_map.adoc @@ -904,7 +904,7 @@ unlike xref:#unordered_flat_map_emplace[emplace], which simply forwards all argu Can invalidate iterators pointers and references, but only if the insert causes the load to be greater than the maximum load. -The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. -- @@ -949,7 +949,7 @@ unlike xref:#unordered_flat_map_emplace_hint[emplace_hint], which simply forward Can invalidate iterators pointers and references, but only if the insert causes the load to be greater than the maximum load. -The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. -- @@ -1160,7 +1160,7 @@ template [horizontal] Returns:;; An iterator pointing to an element with key equivalent to `k`, or `end()` if no such element exists. -Notes:;; The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1173,7 +1173,7 @@ template [horizontal] Returns:;; The number of elements with key equivalent to `k`. -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1186,7 +1186,7 @@ template [horizontal] Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1202,7 +1202,7 @@ template [horizontal] Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`. -Notes:;; The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- diff --git a/doc/unordered/unordered_flat_set.adoc b/doc/unordered/unordered_flat_set.adoc index 1e14fdf0..c560873a 100644 --- a/doc/unordered/unordered_flat_set.adoc +++ b/doc/unordered/unordered_flat_set.adoc @@ -971,7 +971,7 @@ template [horizontal] Returns:;; An iterator pointing to an element with key equivalent to `k`, or `end()` if no such element exists. -Notes:;; The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -984,7 +984,7 @@ template [horizontal] Returns:;; The number of elements with key equivalent to `k`. -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -997,7 +997,7 @@ template [horizontal] Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1013,7 +1013,7 @@ template [horizontal] Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`. -Notes:;; The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- diff --git a/doc/unordered/unordered_map.adoc b/doc/unordered/unordered_map.adoc index 1332cebe..fdba467c 100644 --- a/doc/unordered/unordered_map.adoc +++ b/doc/unordered/unordered_map.adoc @@ -1009,7 +1009,7 @@ template std::pair try_emplace(const key_type& k, Args&&... args); template std::pair try_emplace(key_type&& k, Args&&... args); -template +template std::pair try_emplace(K&& k, Args&&... args) ``` @@ -1043,7 +1043,7 @@ Can invalidate iterators, but only if the insert causes the load factor to be gr Pointers and references to elements are never invalidated. -The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. If the compiler doesn't support variadic template arguments or rvalue references, this is emulated for up to `10` arguments, with no support for rvalue references or move semantics. @@ -1094,7 +1094,7 @@ Can invalidate iterators, but only if the insert causes the load factor to be gr Pointers and references to elements are never invalidated. -The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. If the compiler doesn't support variadic template arguments or rvalue references, this is emulated for up to `10` arguments, with no support for rvalue references or move semantics. @@ -1466,7 +1466,7 @@ template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1479,7 +1479,7 @@ template [horizontal] Returns:;; The number of elements with key equivalent to `k`. -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1492,7 +1492,7 @@ template [horizontal] Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1508,7 +1508,7 @@ template [horizontal] Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`. -Notes:;; The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- diff --git a/doc/unordered/unordered_multimap.adoc b/doc/unordered/unordered_multimap.adoc index 7adcf659..980a8dc8 100644 --- a/doc/unordered/unordered_multimap.adoc +++ b/doc/unordered/unordered_multimap.adoc @@ -1223,7 +1223,7 @@ template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1236,7 +1236,7 @@ template [horizontal] Returns:;; The number of elements with key equivalent to `k`. -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1249,7 +1249,7 @@ template [horizontal] Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1265,7 +1265,7 @@ template [horizontal] Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`. -Notes:;; The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- diff --git a/doc/unordered/unordered_multiset.adoc b/doc/unordered/unordered_multiset.adoc index 94af7526..87aac43e 100644 --- a/doc/unordered/unordered_multiset.adoc +++ b/doc/unordered/unordered_multiset.adoc @@ -1181,7 +1181,7 @@ template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1194,7 +1194,7 @@ template [horizontal] Returns:;; The number of elements with key equivalent to `k`. -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1207,7 +1207,7 @@ template [horizontal] Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1223,7 +1223,7 @@ template [horizontal] Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`. -Notes:;; The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- diff --git a/doc/unordered/unordered_node_map.adoc b/doc/unordered/unordered_node_map.adoc index 38fb1c00..257e5efa 100644 --- a/doc/unordered/unordered_node_map.adoc +++ b/doc/unordered/unordered_node_map.adoc @@ -974,7 +974,7 @@ unlike xref:#unordered_node_map_emplace[emplace], which simply forwards all argu Can invalidate iterators, but only if the insert causes the load to be greater than the maximum load. -The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. -- @@ -1019,7 +1019,7 @@ unlike xref:#unordered_node_map_emplace_hint[emplace_hint], which simply forward Can invalidate iterators, but only if the insert causes the load to be greater than the maximum load. -The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. -- @@ -1258,7 +1258,7 @@ template [horizontal] Returns:;; An iterator pointing to an element with key equivalent to `k`, or `end()` if no such element exists. -Notes:;; The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1271,7 +1271,7 @@ template [horizontal] Returns:;; The number of elements with key equivalent to `k`. -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1284,7 +1284,7 @@ template [horizontal] Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1300,7 +1300,7 @@ template [horizontal] Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`. -Notes:;; The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- diff --git a/doc/unordered/unordered_node_set.adoc b/doc/unordered/unordered_node_set.adoc index 94cd5f92..5ac22398 100644 --- a/doc/unordered/unordered_node_set.adoc +++ b/doc/unordered/unordered_node_set.adoc @@ -1072,7 +1072,7 @@ template [horizontal] Returns:;; An iterator pointing to an element with key equivalent to `k`, or `end()` if no such element exists. -Notes:;; The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1085,7 +1085,7 @@ template [horizontal] Returns:;; The number of elements with key equivalent to `k`. -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1098,7 +1098,7 @@ template [horizontal] Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1114,7 +1114,7 @@ template [horizontal] Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`. -Notes:;; The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- diff --git a/doc/unordered/unordered_set.adoc b/doc/unordered/unordered_set.adoc index 20a72cd1..e6c2c67c 100644 --- a/doc/unordered/unordered_set.adoc +++ b/doc/unordered/unordered_set.adoc @@ -1248,7 +1248,7 @@ template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1261,7 +1261,7 @@ template [horizontal] Returns:;; The number of elements with key equivalent to `k`. -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1274,7 +1274,7 @@ template [horizontal] Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container -Notes:;; The `template ` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- @@ -1290,7 +1290,7 @@ template [horizontal] Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`. -Notes:;; The `template ` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. +Notes:;; The `template` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. --- From a696bdecf6167d224ab6ebf8796d5c0a4c378d47 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 21 May 2023 13:10:46 +0200 Subject: [PATCH 266/327] editorial --- doc/unordered/unordered_flat_map.adoc | 1 + doc/unordered/unordered_flat_set.adoc | 1 + doc/unordered/unordered_map.adoc | 1 + doc/unordered/unordered_multimap.adoc | 1 + doc/unordered/unordered_multiset.adoc | 1 + doc/unordered/unordered_node_map.adoc | 1 + doc/unordered/unordered_node_set.adoc | 1 + doc/unordered/unordered_set.adoc | 1 + 8 files changed, 8 insertions(+) diff --git a/doc/unordered/unordered_flat_map.adoc b/doc/unordered/unordered_flat_map.adoc index cda66533..ba95eb73 100644 --- a/doc/unordered/unordered_flat_map.adoc +++ b/doc/unordered/unordered_flat_map.adoc @@ -280,6 +280,7 @@ namespace boost { unordered_flat_map& y) noexcept(noexcept(x.swap(y))); + // Erasure template typename unordered_flat_map::size_type xref:#unordered_flat_map_erase_if[erase_if](unordered_flat_map& c, Predicate pred); diff --git a/doc/unordered/unordered_flat_set.adoc b/doc/unordered/unordered_flat_set.adoc index c560873a..770ce797 100644 --- a/doc/unordered/unordered_flat_set.adoc +++ b/doc/unordered/unordered_flat_set.adoc @@ -234,6 +234,7 @@ namespace boost { unordered_flat_set& y) noexcept(noexcept(x.swap(y))); + // Erasure template typename unordered_flat_set::size_type xref:#unordered_flat_set_erase_if[erase_if](unordered_flat_set& c, Predicate pred); diff --git a/doc/unordered/unordered_map.adoc b/doc/unordered/unordered_map.adoc index fdba467c..02d95ac2 100644 --- a/doc/unordered/unordered_map.adoc +++ b/doc/unordered/unordered_map.adoc @@ -286,6 +286,7 @@ namespace boost { unordered_map& y) noexcept(noexcept(x.swap(y))); + // Erasure template typename unordered_map::size_type xref:#unordered_map_erase_if[erase_if](unordered_map& c, Predicate pred); diff --git a/doc/unordered/unordered_multimap.adoc b/doc/unordered/unordered_multimap.adoc index 980a8dc8..7a6a09d7 100644 --- a/doc/unordered/unordered_multimap.adoc +++ b/doc/unordered/unordered_multimap.adoc @@ -253,6 +253,7 @@ namespace boost { unordered_multimap& y) noexcept(noexcept(x.swap(y))); + // Erasure template typename unordered_multimap::size_type xref:#unordered_multimap_erase_if[erase_if](unordered_multimap& c, Predicate pred); diff --git a/doc/unordered/unordered_multiset.adoc b/doc/unordered/unordered_multiset.adoc index 87aac43e..017ef5c2 100644 --- a/doc/unordered/unordered_multiset.adoc +++ b/doc/unordered/unordered_multiset.adoc @@ -244,6 +244,7 @@ namespace boost { unordered_multiset& y) noexcept(noexcept(x.swap(y))); + // Erasure template typename unordered_multiset::size_type xref:#unordered_multiset_erase_if[erase_if](unordered_multiset& c, Predicate pred); diff --git a/doc/unordered/unordered_node_map.adoc b/doc/unordered/unordered_node_map.adoc index 257e5efa..4821414a 100644 --- a/doc/unordered/unordered_node_map.adoc +++ b/doc/unordered/unordered_node_map.adoc @@ -284,6 +284,7 @@ namespace boost { unordered_node_map& y) noexcept(noexcept(x.swap(y))); + // Erasure template typename unordered_node_map::size_type xref:#unordered_node_map_erase_if[erase_if](unordered_node_map& c, Predicate pred); diff --git a/doc/unordered/unordered_node_set.adoc b/doc/unordered/unordered_node_set.adoc index 5ac22398..b5a5ff97 100644 --- a/doc/unordered/unordered_node_set.adoc +++ b/doc/unordered/unordered_node_set.adoc @@ -238,6 +238,7 @@ namespace boost { unordered_node_set& y) noexcept(noexcept(x.swap(y))); + // Erasure template typename unordered_node_set::size_type xref:#unordered_node_set_erase_if[erase_if](unordered_node_set& c, Predicate pred); diff --git a/doc/unordered/unordered_set.adoc b/doc/unordered/unordered_set.adoc index e6c2c67c..57b6e6f4 100644 --- a/doc/unordered/unordered_set.adoc +++ b/doc/unordered/unordered_set.adoc @@ -245,6 +245,7 @@ namespace boost { unordered_set& y) noexcept(noexcept(x.swap(y))); + // Erasure template typename unordered_set::size_type xref:#unordered_set_erase_if[erase_if](unordered_set& c, Predicate pred); From b4c75abca95469dc20b3f4a33e92f64f54d436e1 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 22 May 2023 09:49:00 +0200 Subject: [PATCH 267/327] typo --- doc/unordered/regular.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/unordered/regular.adoc b/doc/unordered/regular.adoc index 206ffb47..9ad36258 100644 --- a/doc/unordered/regular.adoc +++ b/doc/unordered/regular.adoc @@ -14,7 +14,7 @@ xref:#compliance_open_addressing_containers[standard compliance section]. For readers without previous experience with hash containers but familiar -with normal associatve containers (`std::set`, `std::map`, +with normal associative containers (`std::set`, `std::map`, `std::multiset` and `std::multimap`), Boost.Unordered containers are used in a similar manner: [source,cpp] From 16550ded0c821a4d4ec57ffbfd123f76c38a8d4d Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 22 May 2023 10:11:37 -0700 Subject: [PATCH 268/327] Add exceptional erase tests --- test/Jamfile.v2 | 1 + test/cfoa/exception_erase_tests.cpp | 294 ++++++++++++++++++++++++++++ test/cfoa/exception_helpers.hpp | 18 +- 3 files changed, 304 insertions(+), 9 deletions(-) create mode 100644 test/cfoa/exception_erase_tests.cpp diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 8cf8e40c..8d985b65 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -191,6 +191,7 @@ local CFOA_TESTS = equality_tests fwd_tests exception_insert_tests + exception_erase_tests ; for local test in $(CFOA_TESTS) diff --git a/test/cfoa/exception_erase_tests.cpp b/test/cfoa/exception_erase_tests.cpp new file mode 100644 index 00000000..ac4e8c3a --- /dev/null +++ b/test/cfoa/exception_erase_tests.cpp @@ -0,0 +1,294 @@ +// 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 "exception_helpers.hpp" + +#include + +#include + +namespace { + test::seed_t initialize_seed(3202923); + + struct lvalue_eraser_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_erased{0}; + auto const old_size = x.size(); + + auto const old_dc = +raii::default_constructor; + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + auto const old_d = +raii::destructor; + + BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + + raii::move_constructor, + raii::destructor + 2 * x.size()); + + enable_exceptions(); + thread_runner(values, [&values, &num_erased, &x](boost::span) { + for (auto const& k : values) { + try { + auto count = x.erase(k.first); + num_erased += count; + BOOST_TEST_LE(count, 1u); + BOOST_TEST_GE(count, 0u); + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_EQ(raii::default_constructor, old_dc); + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + + BOOST_TEST_EQ(raii::destructor, old_d + 2 * old_size); + + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST(x.empty()); + BOOST_TEST_EQ(num_erased, old_size); + } + } lvalue_eraser; + + struct lvalue_eraser_if_type + { + template void operator()(std::vector& values, X& x) + { + using value_type = typename X::value_type; + + std::atomic num_erased{0}; + + auto const old_size = x.size(); + + auto const old_dc = +raii::default_constructor; + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + auto const old_d = +raii::destructor; + + auto max = 0; + x.visit_all([&max](value_type const& v) { + if (v.second.x_ > max) { + max = v.second.x_; + } + }); + + auto threshold = max / 2; + + auto expected_erasures = 0u; + x.visit_all([&expected_erasures, threshold](value_type const& v) { + if (v.second.x_ > threshold) { + ++expected_erasures; + } + }); + + enable_exceptions(); + thread_runner(values, [&num_erased, &x, threshold](boost::span s) { + for (auto const& k : s) { + try { + auto count = x.erase_if(k.first, + [threshold](value_type& v) { return v.second.x_ > threshold; }); + num_erased += count; + BOOST_TEST_LE(count, 1u); + BOOST_TEST_GE(count, 0u); + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_LE(num_erased, expected_erasures); + BOOST_TEST_EQ(x.size(), old_size - num_erased); + + BOOST_TEST_EQ(raii::default_constructor, old_dc); + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + + BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased); + } + } lvalue_eraser_if; + + struct erase_if_type + { + template void operator()(std::vector& values, X& x) + { + using value_type = typename X::value_type; + + std::atomic num_erased{0}; + + auto const old_size = x.size(); + + auto const old_dc = +raii::default_constructor; + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + auto const old_d = +raii::destructor; + + auto max = 0; + x.visit_all([&max](value_type const& v) { + if (v.second.x_ > max) { + max = v.second.x_; + } + }); + + auto threshold = max / 2; + + auto expected_erasures = 0u; + x.visit_all([&expected_erasures, threshold](value_type const& v) { + if (v.second.x_ > threshold) { + ++expected_erasures; + } + }); + + enable_exceptions(); + thread_runner( + values, [&num_erased, &x, threshold](boost::span /* s */) { + for (std::size_t i = 0; i < 128; ++i) { + try { + auto count = x.erase_if([threshold](value_type& v) { + static std::atomic c{0}; + auto t = ++c; + if (should_throw && (t % throw_threshold == 0)) { + throw exception_tag{}; + } + + return v.second.x_ > threshold; + }); + + num_erased += count; + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_GT(num_erased, 0u); + + BOOST_TEST_EQ(raii::default_constructor, old_dc); + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + + BOOST_TEST_EQ(raii::destructor, old_d + 2 * (old_size - x.size())); + } + } erase_if; + + struct free_fn_erase_if_type + { + template void operator()(std::vector& values, X& x) + { + using value_type = typename X::value_type; + + std::atomic num_erased{0}; + + auto const old_size = x.size(); + + auto const old_dc = +raii::default_constructor; + auto const old_cc = +raii::copy_constructor; + auto const old_mc = +raii::move_constructor; + + auto const old_d = +raii::destructor; + + auto max = 0; + x.visit_all([&max](value_type const& v) { + if (v.second.x_ > max) { + max = v.second.x_; + } + }); + + auto threshold = max / 2; + + auto expected_erasures = 0u; + x.visit_all([&expected_erasures, threshold](value_type const& v) { + if (v.second.x_ > threshold) { + ++expected_erasures; + } + }); + + enable_exceptions(); + thread_runner( + values, [&num_erased, &x, threshold](boost::span /* s */) { + for (std::size_t i = 0; i < 128; ++i) { + try { + auto count = + boost::unordered::erase_if(x, [threshold](value_type& v) { + static std::atomic c{0}; + auto t = ++c; + if (should_throw && (t % throw_threshold == 0)) { + throw exception_tag{}; + } + + return v.second.x_ > threshold; + }); + + num_erased += count; + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_GT(num_erased, 0u); + BOOST_TEST_LE(num_erased, expected_erasures); + + BOOST_TEST_EQ(raii::default_constructor, old_dc); + BOOST_TEST_EQ(raii::copy_constructor, old_cc); + BOOST_TEST_EQ(raii::move_constructor, old_mc); + + BOOST_TEST_EQ(raii::destructor, old_d + 2 * (old_size - x.size())); + } + } free_fn_erase_if; + + template + void erase(X*, G gen, F eraser, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + X x; + + x.insert(values.begin(), values.end()); + + BOOST_TEST_EQ(x.size(), reference_map.size()); + + using value_type = typename X::value_type; + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + if (rg == test::sequential) { + BOOST_TEST_EQ(kv.second, reference_map[kv.first]); + } + })); + + eraser(values, x); + test_fuzzy_matches_reference(x, reference_map, rg); + } + + check_raii_counts(); + } + + boost::unordered::concurrent_flat_map > >* map; + +} // namespace + +using test::default_generator; +using test::limited_range; +using test::sequential; + +// clang-format off +UNORDERED_TEST( + erase, + ((map)) + ((exception_value_type_generator)(exception_init_type_generator)) + ((lvalue_eraser)(lvalue_eraser_if)(erase_if)(free_fn_erase_if)) + ((default_generator)(sequential)(limited_range))) + +// clang-format on + +RUN_TESTS() diff --git a/test/cfoa/exception_helpers.hpp b/test/cfoa/exception_helpers.hpp index 0fd63833..0645ff21 100644 --- a/test/cfoa/exception_helpers.hpp +++ b/test/cfoa/exception_helpers.hpp @@ -28,7 +28,7 @@ static std::size_t const num_threads = std::atomic_bool should_throw{false}; -constexpr std::uint32_t threshold = 2500; +constexpr std::uint32_t throw_threshold = 2500; void enable_exceptions() { should_throw = true; } void disable_exceptions() { should_throw = false; } @@ -45,8 +45,8 @@ struct stateful_hash void throw_helper() const { - ++c; - if (should_throw && (c % threshold == 0)) { + auto n = ++c; + if (should_throw && (n % throw_threshold == 0)) { throw exception_tag{}; } } @@ -95,8 +95,8 @@ struct stateful_key_equal void throw_helper() const { - ++c; - if (should_throw && (c % threshold == 0)) { + auto n = ++c; + if (should_throw && (n % throw_threshold == 0)) { throw exception_tag{}; } } @@ -143,8 +143,8 @@ template struct stateful_allocator void throw_helper() const { - ++c; - if (should_throw && (c % threshold == 0)) { + auto n = ++c; + if (should_throw && (n % 10 == 0)) { throw exception_tag{}; } } @@ -189,8 +189,8 @@ struct raii static std::atomic c; void throw_helper() const { - ++c; - if (should_throw && (c % threshold == 0)) { + auto n = ++c; + if (should_throw && (n % throw_threshold == 0)) { throw exception_tag{}; } } From a9203ed93c8b4aa14cd8408a7fd7db972b826c00 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 22 May 2023 10:11:44 -0700 Subject: [PATCH 269/327] Clean up erase_tests --- test/cfoa/erase_tests.cpp | 42 +++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/test/cfoa/erase_tests.cpp b/test/cfoa/erase_tests.cpp index 75b3c73e..dfa936c5 100644 --- a/test/cfoa/erase_tests.cpp +++ b/test/cfoa/erase_tests.cpp @@ -225,14 +225,14 @@ namespace { } }); - thread_runner(values, [&num_erased, &x, threshold](boost::span s) { - for (auto const& k : s) { - (void)k; - auto count = x.erase_if( - [threshold](value_type& v) { return v.second.x_ > threshold; }); - num_erased += count; - } - }); + thread_runner( + values, [&num_erased, &x, threshold](boost::span /* s */) { + for (std::size_t i = 0; i < 128; ++i) { + auto count = x.erase_if( + [threshold](value_type& v) { return v.second.x_ > threshold; }); + num_erased += count; + } + }); BOOST_TEST_EQ(num_erased, expected_erasures); BOOST_TEST_EQ(x.size(), old_size - num_erased); @@ -277,14 +277,14 @@ namespace { } }); - thread_runner(values, [&num_erased, &x, threshold](boost::span s) { - for (auto const& k : s) { - (void)k; - auto count = boost::unordered::erase_if( - x, [threshold](value_type& v) { return v.second.x_ > threshold; }); - num_erased += count; - } - }); + thread_runner( + values, [&num_erased, &x, threshold](boost::span /* s */) { + for (std::size_t i = 0; i < 128; ++i) { + auto count = boost::unordered::erase_if(x, + [threshold](value_type& v) { return v.second.x_ > threshold; }); + num_erased += count; + } + }); BOOST_TEST_EQ(num_erased, expected_erasures); BOOST_TEST_EQ(x.size(), old_size - num_erased); @@ -378,16 +378,10 @@ namespace { })); eraser(values, x); + test_fuzzy_matches_reference(x, reference_map, rg); } - BOOST_TEST_GE(raii::default_constructor, 0u); - BOOST_TEST_GE(raii::copy_constructor, 0u); - BOOST_TEST_GE(raii::move_constructor, 0u); - BOOST_TEST_GT(raii::destructor, 0u); - - BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + - raii::move_constructor, - raii::destructor); + check_raii_counts(); } boost::unordered::concurrent_flat_map* map; From 3ad164267a48b18f7f39b20447d057e160f3d344 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 22 May 2023 11:37:07 -0700 Subject: [PATCH 270/327] Update duration of erase operations to trigger successful erasures when only 2 threads are available --- test/cfoa/exception_erase_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cfoa/exception_erase_tests.cpp b/test/cfoa/exception_erase_tests.cpp index ac4e8c3a..883ef710 100644 --- a/test/cfoa/exception_erase_tests.cpp +++ b/test/cfoa/exception_erase_tests.cpp @@ -147,7 +147,7 @@ namespace { enable_exceptions(); thread_runner( values, [&num_erased, &x, threshold](boost::span /* s */) { - for (std::size_t i = 0; i < 128; ++i) { + for (std::size_t i = 0; i < 256; ++i) { try { auto count = x.erase_if([threshold](value_type& v) { static std::atomic c{0}; @@ -211,7 +211,7 @@ namespace { enable_exceptions(); thread_runner( values, [&num_erased, &x, threshold](boost::span /* s */) { - for (std::size_t i = 0; i < 128; ++i) { + for (std::size_t i = 0; i < 256; ++i) { try { auto count = boost::unordered::erase_if(x, [threshold](value_type& v) { From c63a88032dbb558b1ff21120a35f353e7a9b4813 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 22 May 2023 12:10:10 -0700 Subject: [PATCH 271/327] Loosen restrictions on erase exceptions tests to accomodate runs where there are no successful erasures --- test/cfoa/exception_erase_tests.cpp | 82 +++++++++++------------------ 1 file changed, 30 insertions(+), 52 deletions(-) diff --git a/test/cfoa/exception_erase_tests.cpp b/test/cfoa/exception_erase_tests.cpp index 883ef710..bd3d8228 100644 --- a/test/cfoa/exception_erase_tests.cpp +++ b/test/cfoa/exception_erase_tests.cpp @@ -118,8 +118,6 @@ namespace { { using value_type = typename X::value_type; - std::atomic num_erased{0}; - auto const old_size = x.size(); auto const old_dc = +raii::default_constructor; @@ -145,29 +143,24 @@ namespace { }); enable_exceptions(); - thread_runner( - values, [&num_erased, &x, threshold](boost::span /* s */) { - for (std::size_t i = 0; i < 256; ++i) { - try { - auto count = x.erase_if([threshold](value_type& v) { - static std::atomic c{0}; - auto t = ++c; - if (should_throw && (t % throw_threshold == 0)) { - throw exception_tag{}; - } + thread_runner(values, [&x, threshold](boost::span /* s */) { + for (std::size_t i = 0; i < 256; ++i) { + try { + x.erase_if([threshold](value_type& v) { + static std::atomic c{0}; + auto t = ++c; + if (should_throw && (t % throw_threshold == 0)) { + throw exception_tag{}; + } - return v.second.x_ > threshold; - }); - - num_erased += count; - } catch (...) { - } + return v.second.x_ > threshold; + }); + } catch (...) { } - }); + } + }); disable_exceptions(); - BOOST_TEST_GT(num_erased, 0u); - BOOST_TEST_EQ(raii::default_constructor, old_dc); BOOST_TEST_EQ(raii::copy_constructor, old_cc); BOOST_TEST_EQ(raii::move_constructor, old_mc); @@ -182,8 +175,6 @@ namespace { { using value_type = typename X::value_type; - std::atomic num_erased{0}; - auto const old_size = x.size(); auto const old_dc = +raii::default_constructor; @@ -201,39 +192,26 @@ namespace { auto threshold = max / 2; - auto expected_erasures = 0u; - x.visit_all([&expected_erasures, threshold](value_type const& v) { - if (v.second.x_ > threshold) { - ++expected_erasures; + enable_exceptions(); + thread_runner(values, [&x, threshold](boost::span /* s */) { + for (std::size_t i = 0; i < 256; ++i) { + try { + boost::unordered::erase_if(x, [threshold](value_type& v) { + static std::atomic c{0}; + auto t = ++c; + if (should_throw && (t % throw_threshold == 0)) { + throw exception_tag{}; + } + + return v.second.x_ > threshold; + }); + + } catch (...) { + } } }); - - enable_exceptions(); - thread_runner( - values, [&num_erased, &x, threshold](boost::span /* s */) { - for (std::size_t i = 0; i < 256; ++i) { - try { - auto count = - boost::unordered::erase_if(x, [threshold](value_type& v) { - static std::atomic c{0}; - auto t = ++c; - if (should_throw && (t % throw_threshold == 0)) { - throw exception_tag{}; - } - - return v.second.x_ > threshold; - }); - - num_erased += count; - } catch (...) { - } - } - }); disable_exceptions(); - BOOST_TEST_GT(num_erased, 0u); - BOOST_TEST_LE(num_erased, expected_erasures); - BOOST_TEST_EQ(raii::default_constructor, old_dc); BOOST_TEST_EQ(raii::copy_constructor, old_cc); BOOST_TEST_EQ(raii::move_constructor, old_mc); From 146c5cb6be71d8d4014edd9f280c8b02db6a459e Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 22 May 2023 14:19:21 -0700 Subject: [PATCH 272/327] Clean up exception tests --- test/cfoa/exception_helpers.hpp | 8 +- test/cfoa/exception_insert_tests.cpp | 175 +++------------------------ 2 files changed, 18 insertions(+), 165 deletions(-) diff --git a/test/cfoa/exception_helpers.hpp b/test/cfoa/exception_helpers.hpp index 0645ff21..554c3f2b 100644 --- a/test/cfoa/exception_helpers.hpp +++ b/test/cfoa/exception_helpers.hpp @@ -46,7 +46,7 @@ struct stateful_hash void throw_helper() const { auto n = ++c; - if (should_throw && (n % throw_threshold == 0)) { + if (should_throw && ((n + 1) % throw_threshold == 0)) { throw exception_tag{}; } } @@ -96,7 +96,7 @@ struct stateful_key_equal void throw_helper() const { auto n = ++c; - if (should_throw && (n % throw_threshold == 0)) { + if (should_throw && ((n + 1) % throw_threshold == 0)) { throw exception_tag{}; } } @@ -144,7 +144,7 @@ template struct stateful_allocator void throw_helper() const { auto n = ++c; - if (should_throw && (n % 10 == 0)) { + if (should_throw && ((n + 1) % 10 == 0)) { throw exception_tag{}; } } @@ -190,7 +190,7 @@ struct raii void throw_helper() const { auto n = ++c; - if (should_throw && (n % throw_threshold == 0)) { + if (should_throw && ((n + 1) % throw_threshold == 0)) { throw exception_tag{}; } } diff --git a/test/cfoa/exception_insert_tests.cpp b/test/cfoa/exception_insert_tests.cpp index 533243bf..92c708a7 100644 --- a/test/cfoa/exception_insert_tests.cpp +++ b/test/cfoa/exception_insert_tests.cpp @@ -213,17 +213,13 @@ namespace { template void operator()(std::vector& values, X& x) { std::atomic num_inserts{0}; - std::atomic num_invokes{0}; enable_exceptions(); - thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + thread_runner(values, [&x, &num_inserts](boost::span s) { for (auto& r : s) { try { bool b = x.insert_or_cvisit( - r, [&num_invokes](typename X::value_type const& v) { - (void)v; - ++num_invokes; - }); + r, [](typename X::value_type const& v) { (void)v; }); if (b) { ++num_inserts; @@ -247,17 +243,13 @@ namespace { template void operator()(std::vector& values, X& x) { std::atomic num_inserts{0}; - std::atomic num_invokes{0}; enable_exceptions(); - thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + thread_runner(values, [&x, &num_inserts](boost::span s) { for (auto& r : s) { try { bool b = - x.insert_or_visit(r, [&num_invokes](typename X::value_type& v) { - (void)v; - ++num_invokes; - }); + x.insert_or_visit(r, [](typename X::value_type& v) { (void)v; }); if (b) { ++num_inserts; @@ -283,17 +275,13 @@ namespace { template void operator()(std::vector& values, X& x) { std::atomic num_inserts{0}; - std::atomic num_invokes{0}; enable_exceptions(); - thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + thread_runner(values, [&x, &num_inserts](boost::span s) { for (auto& r : s) { try { bool b = x.insert_or_cvisit( - std::move(r), [&num_invokes](typename X::value_type const& v) { - (void)v; - ++num_invokes; - }); + std::move(r), [](typename X::value_type const& v) { (void)v; }); if (b) { ++num_inserts; @@ -315,17 +303,13 @@ namespace { template void operator()(std::vector& values, X& x) { std::atomic num_inserts{0}; - std::atomic num_invokes{0}; enable_exceptions(); - thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + thread_runner(values, [&x, &num_inserts](boost::span s) { for (auto& r : s) { try { bool b = x.insert_or_visit( - std::move(r), [&num_invokes](typename X::value_type& v) { - (void)v; - ++num_invokes; - }); + std::move(r), [](typename X::value_type& v) { (void)v; }); if (b) { ++num_inserts; @@ -349,23 +333,17 @@ namespace { { template void operator()(std::vector& values, X& x) { - std::atomic num_invokes{0}; - enable_exceptions(); - thread_runner(values, [&x, &num_invokes](boost::span s) { + thread_runner(values, [&x](boost::span s) { try { x.insert_or_cvisit(s.begin(), s.end(), - [&num_invokes](typename X::value_type const& v) { - (void)v; - ++num_invokes; - }); + [](typename X::value_type const& v) { (void)v; }); } catch (...) { } }); disable_exceptions(); BOOST_TEST_EQ(raii::default_constructor, 0u); - BOOST_TEST_GT(raii::move_constructor, 0u); } } iterator_range_insert_or_cvisit; @@ -373,23 +351,17 @@ namespace { { template void operator()(std::vector& values, X& x) { - std::atomic num_invokes{0}; - enable_exceptions(); - thread_runner(values, [&x, &num_invokes](boost::span s) { + thread_runner(values, [&x](boost::span s) { try { x.insert_or_visit(s.begin(), s.end(), - [&num_invokes](typename X::value_type const& v) { - (void)v; - ++num_invokes; - }); + [](typename X::value_type const& v) { (void)v; }); } catch (...) { } }); disable_exceptions(); BOOST_TEST_EQ(raii::default_constructor, 0u); - BOOST_TEST_GT(raii::move_constructor, 0u); } } iterator_range_insert_or_visit; @@ -401,8 +373,8 @@ namespace { auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); auto reference_map = boost::unordered_flat_map(values.begin(), values.end()); - raii::reset_counts(); + raii::reset_counts(); { X x; @@ -410,122 +382,7 @@ namespace { test_fuzzy_matches_reference(x, reference_map, rg); } - - BOOST_TEST_GE(raii::default_constructor, 0u); - BOOST_TEST_GE(raii::copy_constructor, 0u); - BOOST_TEST_GE(raii::move_constructor, 0u); - BOOST_TEST_GT(raii::destructor, 0u); - - BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + - raii::move_constructor, - raii::destructor); - } - - template void insert_initializer_list(X*) - { - using value_type = typename X::value_type; - - std::initializer_list values{ - value_type{raii{0}, raii{0}}, - value_type{raii{1}, raii{1}}, - value_type{raii{2}, raii{2}}, - value_type{raii{3}, raii{3}}, - value_type{raii{4}, raii{4}}, - value_type{raii{5}, raii{5}}, - value_type{raii{6}, raii{6}}, - value_type{raii{6}, raii{6}}, - value_type{raii{7}, raii{7}}, - value_type{raii{8}, raii{8}}, - value_type{raii{9}, raii{9}}, - value_type{raii{10}, raii{10}}, - value_type{raii{9}, raii{9}}, - value_type{raii{8}, raii{8}}, - value_type{raii{7}, raii{7}}, - value_type{raii{6}, raii{6}}, - value_type{raii{5}, raii{5}}, - value_type{raii{4}, raii{4}}, - value_type{raii{3}, raii{3}}, - value_type{raii{2}, raii{2}}, - value_type{raii{1}, raii{1}}, - value_type{raii{0}, raii{0}}, - }; - - std::vector dummy; - - auto reference_map = - boost::unordered_flat_map(values.begin(), values.end()); - raii::reset_counts(); - - { - { - X x; - - thread_runner( - dummy, [&x, &values](boost::span) { x.insert(values); }); - - BOOST_TEST_EQ(x.size(), reference_map.size()); - - BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { - BOOST_TEST(reference_map.contains(kv.first)); - BOOST_TEST_EQ(kv.second, reference_map[kv.first]); - })); - } - - BOOST_TEST_GE(raii::default_constructor, 0u); - BOOST_TEST_GE(raii::copy_constructor, 0u); - BOOST_TEST_GE(raii::move_constructor, 0u); - BOOST_TEST_GT(raii::destructor, 0u); - - BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + - raii::move_constructor, - raii::destructor); - - BOOST_TEST_EQ(raii::copy_assignment, 0u); - BOOST_TEST_EQ(raii::move_assignment, 0u); - } - - { - { - std::atomic num_invokes{0}; - - X x; - - thread_runner(dummy, [&x, &values, &num_invokes](boost::span) { - x.insert_or_visit(values, [&num_invokes](typename X::value_type& v) { - (void)v; - ++num_invokes; - }); - - x.insert_or_cvisit( - values, [&num_invokes](typename X::value_type const& v) { - (void)v; - ++num_invokes; - }); - }); - - BOOST_TEST_EQ(num_invokes, (values.size() - x.size()) + - (num_threads - 1) * values.size() + - num_threads * values.size()); - BOOST_TEST_EQ(x.size(), reference_map.size()); - - BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { - BOOST_TEST(reference_map.contains(kv.first)); - BOOST_TEST_EQ(kv.second, reference_map[kv.first]); - })); - } - - BOOST_TEST_GE(raii::default_constructor, 0u); - BOOST_TEST_GE(raii::copy_constructor, 0u); - BOOST_TEST_GE(raii::move_constructor, 0u); - BOOST_TEST_GT(raii::destructor, 0u); - - BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + - raii::move_constructor, - raii::destructor); - - BOOST_TEST_EQ(raii::copy_assignment, 0u); - BOOST_TEST_EQ(raii::move_assignment, 0u); - } + check_raii_counts(); } boost::unordered::concurrent_flat_map Date: Mon, 22 May 2023 15:24:36 -0700 Subject: [PATCH 273/327] Add initial container population to insert iterator range tests --- test/cfoa/exception_insert_tests.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/cfoa/exception_insert_tests.cpp b/test/cfoa/exception_insert_tests.cpp index 92c708a7..4804caa6 100644 --- a/test/cfoa/exception_insert_tests.cpp +++ b/test/cfoa/exception_insert_tests.cpp @@ -105,6 +105,10 @@ namespace { { template void operator()(std::vector& values, X& x) { + for (std::size_t i = 0; i < 10; ++i) { + x.insert(values[i]); + } + enable_exceptions(); thread_runner(values, [&x](boost::span s) { try { @@ -333,6 +337,10 @@ namespace { { template void operator()(std::vector& values, X& x) { + for (std::size_t i = 0; i < 10; ++i) { + x.insert(values[i]); + } + enable_exceptions(); thread_runner(values, [&x](boost::span s) { try { @@ -351,6 +359,10 @@ namespace { { template void operator()(std::vector& values, X& x) { + for (std::size_t i = 0; i < 10; ++i) { + x.insert(values[i]); + } + enable_exceptions(); thread_runner(values, [&x](boost::span s) { try { From e78dc311e35e5921b340b35ba284b6d70242a707 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 23 May 2023 08:43:10 -0700 Subject: [PATCH 274/327] Clean up erase tests --- test/cfoa/erase_tests.cpp | 8 +------- test/cfoa/exception_erase_tests.cpp | 29 ++++++++++------------------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/test/cfoa/erase_tests.cpp b/test/cfoa/erase_tests.cpp index dfa936c5..0bf4041b 100644 --- a/test/cfoa/erase_tests.cpp +++ b/test/cfoa/erase_tests.cpp @@ -369,13 +369,7 @@ namespace { BOOST_TEST_EQ(x.size(), reference_map.size()); - using value_type = typename X::value_type; - BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { - BOOST_TEST(reference_map.contains(kv.first)); - if (rg == test::sequential) { - BOOST_TEST_EQ(kv.second, reference_map[kv.first]); - } - })); + test_fuzzy_matches_reference(x, reference_map, rg); eraser(values, x); test_fuzzy_matches_reference(x, reference_map, rg); diff --git a/test/cfoa/exception_erase_tests.cpp b/test/cfoa/exception_erase_tests.cpp index bd3d8228..39ff0212 100644 --- a/test/cfoa/exception_erase_tests.cpp +++ b/test/cfoa/exception_erase_tests.cpp @@ -16,6 +16,7 @@ namespace { template void operator()(std::vector& values, X& x) { std::atomic num_erased{0}; + auto const old_size = x.size(); auto const old_dc = +raii::default_constructor; @@ -24,33 +25,28 @@ namespace { auto const old_d = +raii::destructor; - BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + - raii::move_constructor, - raii::destructor + 2 * x.size()); - enable_exceptions(); thread_runner(values, [&values, &num_erased, &x](boost::span) { for (auto const& k : values) { try { auto count = x.erase(k.first); - num_erased += count; BOOST_TEST_LE(count, 1u); BOOST_TEST_GE(count, 0u); + + num_erased += count; } catch (...) { } } }); disable_exceptions(); + BOOST_TEST_EQ(x.size(), old_size - num_erased); + BOOST_TEST_EQ(raii::default_constructor, old_dc); BOOST_TEST_EQ(raii::copy_constructor, old_cc); BOOST_TEST_EQ(raii::move_constructor, old_mc); - BOOST_TEST_EQ(raii::destructor, old_d + 2 * old_size); - - BOOST_TEST_EQ(x.size(), 0u); - BOOST_TEST(x.empty()); - BOOST_TEST_EQ(num_erased, old_size); + BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased); } } lvalue_eraser; @@ -226,22 +222,17 @@ namespace { auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); auto reference_map = boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); { - X x; - + X x(values.size()); x.insert(values.begin(), values.end()); BOOST_TEST_EQ(x.size(), reference_map.size()); + BOOST_TEST_EQ(raii::destructor, 0u); - using value_type = typename X::value_type; - BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { - BOOST_TEST(reference_map.contains(kv.first)); - if (rg == test::sequential) { - BOOST_TEST_EQ(kv.second, reference_map[kv.first]); - } - })); + test_fuzzy_matches_reference(x, reference_map, rg); eraser(values, x); test_fuzzy_matches_reference(x, reference_map, rg); From 61f11a58ee491f260326c1ac827ae9dfdb9a1583 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 23 May 2023 15:28:14 -0700 Subject: [PATCH 275/327] Push up failing test case for code review purposes --- test/Jamfile.v2 | 1 + test/cfoa/exception_constructor_tests.cpp | 172 ++++++++++++++++++++++ test/cfoa/exception_helpers.hpp | 12 +- 3 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 test/cfoa/exception_constructor_tests.cpp diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 8d985b65..1a4a0627 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -192,6 +192,7 @@ local CFOA_TESTS = fwd_tests exception_insert_tests exception_erase_tests + exception_constructor_tests ; for local test in $(CFOA_TESTS) diff --git a/test/cfoa/exception_constructor_tests.cpp b/test/cfoa/exception_constructor_tests.cpp new file mode 100644 index 00000000..1a9bc653 --- /dev/null +++ b/test/cfoa/exception_constructor_tests.cpp @@ -0,0 +1,172 @@ +// 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 "exception_helpers.hpp" + +#include + +using allocator_type = stateful_allocator >; + +using map_type = boost::unordered::concurrent_flat_map; + +namespace { + test::seed_t initialize_seed(795610904); + + UNORDERED_AUTO_TEST (bucket_constructor) { + raii::reset_counts(); + + bool was_thrown = false; + + enable_exceptions(); + for (std::size_t i = 0; i < alloc_throw_threshold; ++i) { + try { + map_type m(128); + } catch (...) { + was_thrown = true; + } + } + disable_exceptions(); + + BOOST_TEST(was_thrown); + } + + template + void iterator_bucket_count_constructor(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + + raii::reset_counts(); + + bool was_thrown = false; + + enable_exceptions(); + try { + map_type x(values.begin(), values.end(), 0, stateful_hash(1), + stateful_key_equal(2), allocator_type(3)); + } catch (...) { + was_thrown = true; + } + disable_exceptions(); + + BOOST_TEST(was_thrown); + check_raii_counts(); + } + + template void copy_constructor(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + + { + raii::reset_counts(); + + bool was_thrown = false; + + try { + map_type x(values.begin(), values.end(), 0); + + enable_exceptions(); + map_type y(x); + } catch (...) { + was_thrown = true; + } + disable_exceptions(); + + BOOST_TEST(was_thrown); + check_raii_counts(); + } + + { + raii::reset_counts(); + + bool was_thrown = false; + + try { + map_type x(values.begin(), values.end(), 0); + + enable_exceptions(); + map_type y(x, allocator_type(4)); + } catch (...) { + was_thrown = true; + } + disable_exceptions(); + + BOOST_TEST(was_thrown); + check_raii_counts(); + } + } + + template + void iterator_range_allocator_constructor(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + + raii::reset_counts(); + + bool was_thrown = false; + + enable_exceptions(); + try { + map_type x(values.begin(), values.end(), allocator_type(3)); + } catch (...) { + was_thrown = true; + } + disable_exceptions(); + + BOOST_TEST(was_thrown); + check_raii_counts(); + } + + template void move_constructor(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + + { + raii::reset_counts(); + + bool was_thrown = false; + + try { + map_type x(values.begin(), values.end(), 0); + + enable_exceptions(); + map_type y(std::move(x), allocator_type(4)); + } catch (...) { + was_thrown = true; + } + disable_exceptions(); + + BOOST_TEST(was_thrown); + check_raii_counts(); + } + } +} // namespace + +using test::default_generator; +using test::limited_range; +using test::sequential; + +// clang-format off +UNORDERED_TEST( + iterator_bucket_count_constructor, + ((exception_value_type_generator)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + copy_constructor, + ((exception_value_type_generator)) + ((default_generator)(sequential))) + +UNORDERED_TEST( + iterator_range_allocator_constructor, + ((exception_value_type_generator)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + move_constructor, + ((exception_value_type_generator)) + ((default_generator)(sequential))) +// clang-format on + +RUN_TESTS() diff --git a/test/cfoa/exception_helpers.hpp b/test/cfoa/exception_helpers.hpp index 554c3f2b..79bee58e 100644 --- a/test/cfoa/exception_helpers.hpp +++ b/test/cfoa/exception_helpers.hpp @@ -29,6 +29,7 @@ static std::size_t const num_threads = std::atomic_bool should_throw{false}; constexpr std::uint32_t throw_threshold = 2500; +constexpr std::uint32_t alloc_throw_threshold = 10; void enable_exceptions() { should_throw = true; } void disable_exceptions() { should_throw = false; } @@ -136,15 +137,16 @@ struct stateful_key_equal }; std::atomic stateful_key_equal::c{0}; +static std::atomic allocator_c = {}; + template struct stateful_allocator { int x_ = -1; - static std::atomic c; void throw_helper() const { - auto n = ++c; - if (should_throw && ((n + 1) % 10 == 0)) { + auto n = ++allocator_c; + if (should_throw && ((n + 1) % alloc_throw_threshold == 0)) { throw exception_tag{}; } } @@ -174,8 +176,6 @@ template struct stateful_allocator bool operator!=(stateful_allocator const& rhs) const { return x_ != rhs.x_; } }; -template std::atomic stateful_allocator::c = {}; - struct raii { static std::atomic default_constructor; @@ -289,7 +289,7 @@ struct raii stateful_hash::c = 0; stateful_key_equal::c = 0; - stateful_allocator::c = 0; + allocator_c = 0; } friend void swap(raii& lhs, raii& rhs) { std::swap(lhs.x_, rhs.x_); } From 253a9bccf607f84b1df756326c81e812fb8f08fd Mon Sep 17 00:00:00 2001 From: joaquintides Date: Wed, 24 May 2023 09:19:31 +0200 Subject: [PATCH 276/327] fixed leak in throwing allocator-extended move ctor --- include/boost/unordered/detail/foa/core.hpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 2e58403e..bdf5032c 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1280,10 +1280,7 @@ public: } table_core(table_core&& x,const Allocator& al_): - hash_base{empty_init,std::move(x.h())}, - pred_base{empty_init,std::move(x.pred())}, - allocator_base{empty_init,al_},arrays(new_arrays(0)), - ml{initial_max_load()},size_{0} + table_core{std::move(x.h()),std::move(x.pred()),al_} { if(al()==x.al()){ std::swap(arrays,x.arrays); @@ -1798,6 +1795,15 @@ private: using pred_base=empty_value; using allocator_base=empty_value; + /* used by allocator-extended move ctor */ + + table_core(Hash&& h_,Pred&& pred_,const Allocator& al_): + hash_base{empty_init,std::move(h_)}, + pred_base{empty_init,std::move(pred_)}, + allocator_base{empty_init,al_},arrays(new_arrays(0)), + ml{initial_max_load()},size_{0} + {} + arrays_type new_arrays(std::size_t n) { return arrays_type::new_(al(),n); From f5d5299b88f30c43c2abd8a64195205ea93ee844 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 24 May 2023 11:06:45 -0700 Subject: [PATCH 277/327] Flesh out constructor exception tests --- test/cfoa/exception_constructor_tests.cpp | 195 +++++++++++++++++++--- 1 file changed, 174 insertions(+), 21 deletions(-) diff --git a/test/cfoa/exception_constructor_tests.cpp b/test/cfoa/exception_constructor_tests.cpp index 1a9bc653..e6c2f993 100644 --- a/test/cfoa/exception_constructor_tests.cpp +++ b/test/cfoa/exception_constructor_tests.cpp @@ -8,8 +8,11 @@ using allocator_type = stateful_allocator >; -using map_type = boost::unordered::concurrent_flat_map; +using hasher = stateful_hash; +using key_equal = stateful_key_equal; + +using map_type = boost::unordered::concurrent_flat_map; namespace { test::seed_t initialize_seed(795610904); @@ -32,26 +35,80 @@ namespace { BOOST_TEST(was_thrown); } - template - void iterator_bucket_count_constructor(G gen, test::random_generator rg) + template void iterator_range(G gen, test::random_generator rg) { auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); - raii::reset_counts(); + { + raii::reset_counts(); - bool was_thrown = false; + bool was_thrown = false; - enable_exceptions(); - try { - map_type x(values.begin(), values.end(), 0, stateful_hash(1), - stateful_key_equal(2), allocator_type(3)); - } catch (...) { - was_thrown = true; + enable_exceptions(); + try { + map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2), + allocator_type(3)); + } catch (...) { + was_thrown = true; + } + disable_exceptions(); + + BOOST_TEST(was_thrown); + check_raii_counts(); } - disable_exceptions(); - BOOST_TEST(was_thrown); - check_raii_counts(); + { + raii::reset_counts(); + + bool was_thrown = false; + + enable_exceptions(); + try { + map_type x(values.begin(), values.end(), allocator_type(3)); + } catch (...) { + was_thrown = true; + } + disable_exceptions(); + + BOOST_TEST(was_thrown); + check_raii_counts(); + } + + { + raii::reset_counts(); + + bool was_thrown = false; + + enable_exceptions(); + try { + map_type x( + values.begin(), values.end(), values.size(), allocator_type(3)); + } catch (...) { + was_thrown = true; + } + disable_exceptions(); + + BOOST_TEST(was_thrown); + check_raii_counts(); + } + + { + raii::reset_counts(); + + bool was_thrown = false; + + enable_exceptions(); + try { + map_type x(values.begin(), values.end(), values.size(), hasher(1), + allocator_type(3)); + } catch (...) { + was_thrown = true; + } + disable_exceptions(); + + BOOST_TEST(was_thrown); + check_raii_counts(); + } } template void copy_constructor(G gen, test::random_generator rg) @@ -141,6 +198,107 @@ namespace { check_raii_counts(); } } + + UNORDERED_AUTO_TEST (initializer_list_bucket_count) { + using value_type = typename map_type::value_type; + + std::initializer_list values{ + value_type{raii{0}, raii{0}}, + value_type{raii{1}, raii{1}}, + value_type{raii{2}, raii{2}}, + value_type{raii{3}, raii{3}}, + value_type{raii{4}, raii{4}}, + value_type{raii{5}, raii{5}}, + value_type{raii{6}, raii{6}}, + value_type{raii{6}, raii{6}}, + value_type{raii{7}, raii{7}}, + value_type{raii{8}, raii{8}}, + value_type{raii{9}, raii{9}}, + value_type{raii{10}, raii{10}}, + value_type{raii{9}, raii{9}}, + value_type{raii{8}, raii{8}}, + value_type{raii{7}, raii{7}}, + value_type{raii{6}, raii{6}}, + value_type{raii{5}, raii{5}}, + value_type{raii{4}, raii{4}}, + value_type{raii{3}, raii{3}}, + value_type{raii{2}, raii{2}}, + value_type{raii{1}, raii{1}}, + value_type{raii{0}, raii{0}}, + }; + + { + raii::reset_counts(); + unsigned num_throws = 0; + + enable_exceptions(); + for (std::size_t i = 0; i < throw_threshold; ++i) { + try { + map_type x(values, 0, hasher(1), key_equal(2), allocator_type(3)); + } catch (...) { + ++num_throws; + } + } + disable_exceptions(); + + BOOST_TEST_GT(num_throws, 0u); + check_raii_counts(); + } + + { + raii::reset_counts(); + unsigned num_throws = 0; + + enable_exceptions(); + for (std::size_t i = 0; i < alloc_throw_threshold * 2; ++i) { + try { + map_type x(values, allocator_type(3)); + } catch (...) { + ++num_throws; + } + } + disable_exceptions(); + + BOOST_TEST_GT(num_throws, 0u); + check_raii_counts(); + } + + { + raii::reset_counts(); + unsigned num_throws = 0; + + enable_exceptions(); + for (std::size_t i = 0; i < alloc_throw_threshold * 2; ++i) { + try { + map_type x(values, values.size() * 2, allocator_type(3)); + } catch (...) { + ++num_throws; + } + } + disable_exceptions(); + + BOOST_TEST_GT(num_throws, 0u); + check_raii_counts(); + } + + { + raii::reset_counts(); + unsigned num_throws = 0; + + enable_exceptions(); + for (std::size_t i = 0; i < throw_threshold; ++i) { + try { + map_type x(values, values.size() * 2, hasher(1), allocator_type(3)); + } catch (...) { + ++num_throws; + } + } + disable_exceptions(); + + BOOST_TEST_GT(num_throws, 0u); + check_raii_counts(); + } + } } // namespace using test::default_generator; @@ -149,7 +307,7 @@ using test::sequential; // clang-format off UNORDERED_TEST( - iterator_bucket_count_constructor, + iterator_range, ((exception_value_type_generator)) ((default_generator)(sequential)(limited_range))) @@ -158,11 +316,6 @@ UNORDERED_TEST( ((exception_value_type_generator)) ((default_generator)(sequential))) -UNORDERED_TEST( - iterator_range_allocator_constructor, - ((exception_value_type_generator)) - ((default_generator)(sequential)(limited_range))) - UNORDERED_TEST( move_constructor, ((exception_value_type_generator)) From 55d79204be815a064217568bb9fd823c236f467f Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 24 May 2023 15:23:09 -0700 Subject: [PATCH 278/327] Add exceptions tests for cfoa assign ops --- test/Jamfile.v2 | 1 + test/cfoa/exception_assign_tests.cpp | 172 +++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 test/cfoa/exception_assign_tests.cpp diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 1a4a0627..08f60bab 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -193,6 +193,7 @@ local CFOA_TESTS = exception_insert_tests exception_erase_tests exception_constructor_tests + exception_assign_tests ; for local test in $(CFOA_TESTS) diff --git a/test/cfoa/exception_assign_tests.cpp b/test/cfoa/exception_assign_tests.cpp new file mode 100644 index 00000000..36e94367 --- /dev/null +++ b/test/cfoa/exception_assign_tests.cpp @@ -0,0 +1,172 @@ +// 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 "exception_helpers.hpp" + +#include + +using allocator_type = stateful_allocator >; + +using hasher = stateful_hash; +using key_equal = stateful_key_equal; + +using map_type = boost::unordered::concurrent_flat_map; + +namespace { + test::seed_t initialize_seed(1794114520); + + template void copy_assign(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + + { + raii::reset_counts(); + + unsigned num_throws = 0; + + auto begin = values.begin(); + auto mid = + values.begin() + static_cast(values.size() / 2); + auto end = values.end(); + + auto reference_map = boost::unordered_flat_map(begin, mid); + + map_type x( + begin, mid, values.size(), hasher(1), key_equal(2), allocator_type(3)); + + map_type y( + mid, end, values.size(), hasher(2), key_equal(1), allocator_type(4)); + + BOOST_TEST(!y.empty()); + + enable_exceptions(); + for (std::size_t i = 0; i < 2 * alloc_throw_threshold; ++i) { + try { + y = x; + } catch (...) { + ++num_throws; + } + } + + disable_exceptions(); + + BOOST_TEST_GT(num_throws, 0u); + test_fuzzy_matches_reference(y, reference_map, rg); + } + check_raii_counts(); + } + + template void move_assign(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + + { + raii::reset_counts(); + + unsigned num_throws = 0; + + auto begin = values.begin(); + auto mid = + values.begin() + static_cast(values.size() / 2); + auto end = values.end(); + + auto reference_map = boost::unordered_flat_map(begin, mid); + + BOOST_TEST( + !boost::allocator_is_always_equal::type::value); + + BOOST_TEST(!boost::allocator_propagate_on_container_move_assignment< + allocator_type>::type::value); + + for (std::size_t i = 0; i < 2 * alloc_throw_threshold; ++i) { + disable_exceptions(); + + map_type x(begin, mid, values.size(), hasher(1), key_equal(2), + allocator_type(3)); + + map_type y( + mid, end, values.size(), hasher(2), key_equal(1), allocator_type(4)); + + enable_exceptions(); + try { + y = std::move(x); + } catch (...) { + ++num_throws; + } + disable_exceptions(); + test_fuzzy_matches_reference(y, reference_map, rg); + } + + BOOST_TEST_GT(num_throws, 0u); + } + check_raii_counts(); + } + + UNORDERED_AUTO_TEST (intializer_list_assign) { + using value_type = typename map_type::value_type; + + std::initializer_list values{ + value_type{raii{0}, raii{0}}, + value_type{raii{1}, raii{1}}, + value_type{raii{2}, raii{2}}, + value_type{raii{3}, raii{3}}, + value_type{raii{4}, raii{4}}, + value_type{raii{5}, raii{5}}, + value_type{raii{6}, raii{6}}, + value_type{raii{6}, raii{6}}, + value_type{raii{7}, raii{7}}, + value_type{raii{8}, raii{8}}, + value_type{raii{9}, raii{9}}, + value_type{raii{10}, raii{10}}, + value_type{raii{9}, raii{9}}, + value_type{raii{8}, raii{8}}, + value_type{raii{7}, raii{7}}, + value_type{raii{6}, raii{6}}, + value_type{raii{5}, raii{5}}, + value_type{raii{4}, raii{4}}, + value_type{raii{3}, raii{3}}, + value_type{raii{2}, raii{2}}, + value_type{raii{1}, raii{1}}, + value_type{raii{0}, raii{0}}, + }; + + { + raii::reset_counts(); + unsigned num_throws = 0; + + for (std::size_t i = 0; i < throw_threshold; ++i) { + map_type x(0, hasher(1), key_equal(2), allocator_type(3)); + enable_exceptions(); + try { + x = values; + } catch (...) { + ++num_throws; + } + disable_exceptions(); + } + + BOOST_TEST_GT(num_throws, 0u); + check_raii_counts(); + } + } +} // namespace + +using test::default_generator; +using test::limited_range; +using test::sequential; + +// clang-format off +UNORDERED_TEST( + copy_assign, + ((exception_value_type_generator)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + move_assign, + ((exception_value_type_generator)) + ((default_generator)(sequential))) +// clang-format on + +RUN_TESTS() From c5df4ec06903de0b9082a13c88f117303f296058 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 25 May 2023 08:41:26 -0700 Subject: [PATCH 279/327] Remove unused test --- test/cfoa/exception_constructor_tests.cpp | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/test/cfoa/exception_constructor_tests.cpp b/test/cfoa/exception_constructor_tests.cpp index e6c2f993..998a6411 100644 --- a/test/cfoa/exception_constructor_tests.cpp +++ b/test/cfoa/exception_constructor_tests.cpp @@ -154,27 +154,6 @@ namespace { } } - template - void iterator_range_allocator_constructor(G gen, test::random_generator rg) - { - auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); - - raii::reset_counts(); - - bool was_thrown = false; - - enable_exceptions(); - try { - map_type x(values.begin(), values.end(), allocator_type(3)); - } catch (...) { - was_thrown = true; - } - disable_exceptions(); - - BOOST_TEST(was_thrown); - check_raii_counts(); - } - template void move_constructor(G gen, test::random_generator rg) { auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); From 2ab42254732a3f8905ff7777a0d586f0e1d52be7 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 26 May 2023 08:16:02 -0700 Subject: [PATCH 280/327] Add workaround for gcc-12 and above where the prefetch call is ignored --- include/boost/unordered/detail/foa/core.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index bdf5032c..b219b018 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1040,7 +1040,12 @@ void swap_if(T&,T&){} inline void prefetch(const void* p) { (void) p; -#if defined(BOOST_GCC)||defined(BOOST_CLANG) +#if BOOST_WORKAROUND(BOOST_GCC, >= 120000) && defined(BOOST_UNORDERED_SSE2) + // gcc-12 and above seem to remove the `__bulitin_prefetch` call below so we + // manually insert the instruction via an asm declaration. + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109985 + asm("prefetcht0 %[ptr]"::[ptr]"m"(*(char const*)p):); +#elif defined(BOOST_GCC)||defined(BOOST_CLANG) __builtin_prefetch((const char*)p); #elif defined(BOOST_UNORDERED_SSE2) _mm_prefetch((const char*)p,_MM_HINT_T0); From 7874625c088ccde1f5f60b10b2dd56097e3460a9 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 26 May 2023 20:56:52 -0700 Subject: [PATCH 281/327] Replace prefetch_elements() with macro so builtins aren't optimized away by DSE --- include/boost/unordered/detail/foa/core.hpp | 68 +++++++++------------ 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index b219b018..52df266c 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -85,6 +85,35 @@ }while(0) #endif +#if defined(BOOST_GCC)||defined(BOOST_CLANG) +#define BOOST_UNORDERED_PREFETCH(p) __builtin_prefetch((const char*)p) +#elif defined(BOOST_UNORDERED_SSE2) +#define BOOST_UNORDERED_PREFETCH(p) _mm_prefetch((const char*)p,_MM_HINT_T0) +#else +#define BOOST_UNORDERED_PREFETCH(p) +#endif + +/* We have experimentally confirmed that ARM architectures get a higher + * speedup when around the first half of the element slots in a group are + * prefetched, whereas for Intel just the first cache line is best. + * Please report back if you find better tunings for some particular + * architectures. + */ +#if BOOST_ARCH_ARM +/* Cache line size can't be known at compile time, so we settle on + * the very frequent value of 64B. + */ +#define BOOST_UNORDERED_PREFETCH_ELEMENTS(p) \ + do{ \ + constexpr int cache_line=64; \ + const char *p0=reinterpret_cast(p), \ + *p1=p0+sizeof(value_type)*N/2; \ + for(;p0::type* =nullptr> void swap_if(T&,T&){} -inline void prefetch(const void* p) -{ - (void) p; -#if BOOST_WORKAROUND(BOOST_GCC, >= 120000) && defined(BOOST_UNORDERED_SSE2) - // gcc-12 and above seem to remove the `__bulitin_prefetch` call below so we - // manually insert the instruction via an asm declaration. - // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109985 - asm("prefetcht0 %[ptr]"::[ptr]"m"(*(char const*)p):); -#elif defined(BOOST_GCC)||defined(BOOST_CLANG) - __builtin_prefetch((const char*)p); -#elif defined(BOOST_UNORDERED_SSE2) - _mm_prefetch((const char*)p,_MM_HINT_T0); -#endif -} - template struct is_std_allocator:std::false_type{}; @@ -1457,7 +1471,7 @@ public: if(mask){ BOOST_UNORDERED_ASSUME(arrays.elements!=nullptr); auto p=arrays.elements+pos*N; - prefetch_elements(p); + BOOST_UNORDERED_PREFETCH_ELEMENTS(p); do{ auto n=unchecked_countr_zero(mask); if(BOOST_LIKELY(bool(pred()(x,key_from(p[n]))))){ @@ -1650,28 +1664,6 @@ public: return pg->match_occupied()&~(int(pg==last-1)<<(N-1)); } - static inline void prefetch_elements(const element_type* p) - { - /* We have experimentally confirmed that ARM architectures get a higher - * speedup when around the first half of the element slots in a group are - * prefetched, whereas for Intel just the first cache line is best. - * Please report back if you find better tunings for some particular - * architectures. - */ - -#if BOOST_ARCH_ARM - /* Cache line size can't be known at compile time, so we settle on - * the very frequent value of 64B. - */ - constexpr int cache_line=64; - const char *p0=reinterpret_cast(p), - *p1=p0+sizeof(value_type)*N/2; - for(;p0 locator unchecked_emplace_at( std::size_t pos0,std::size_t hash,Args&&... args) From 950e640fcfb86c62e5e7aac4562cf9b7f2dda442 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 26 May 2023 21:06:01 -0700 Subject: [PATCH 282/327] Update concurrent_table to use macro-based prefetching --- include/boost/unordered/detail/foa/concurrent_table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index d66f090d..000a0c56 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -949,7 +949,7 @@ private: auto mask=pg->match(hash); if(mask){ auto p=this->arrays.elements+pos*N; - this->prefetch_elements(p); + BOOST_UNORDERED_PREFETCH_ELEMENTS(p); auto lck=access(access_mode,pos); do{ auto n=unchecked_countr_zero(mask); From 7aaa2e9452f672c7fd752227c812d486e6619c01 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 28 May 2023 19:09:14 +0200 Subject: [PATCH 283/327] polished BOOST_UNORDERED_PREFETCH[_ELEMENTS] --- .../unordered/detail/foa/concurrent_table.hpp | 2 +- include/boost/unordered/detail/foa/core.hpp | 43 +++++++++++-------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 000a0c56..ad341e64 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -949,7 +949,7 @@ private: auto mask=pg->match(hash); if(mask){ auto p=this->arrays.elements+pos*N; - BOOST_UNORDERED_PREFETCH_ELEMENTS(p); + BOOST_UNORDERED_PREFETCH_ELEMENTS(p,N); auto lck=access(access_mode,pos); do{ auto n=unchecked_countr_zero(mask); diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 52df266c..dcf107b9 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -85,33 +85,40 @@ }while(0) #endif +/* We use BOOST_UNORDERED_PREFETCH[_ELEMENTS] macros rather than proper + * functions because of https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109985 + */ + #if defined(BOOST_GCC)||defined(BOOST_CLANG) -#define BOOST_UNORDERED_PREFETCH(p) __builtin_prefetch((const char*)p) +#define BOOST_UNORDERED_PREFETCH(p) __builtin_prefetch((const char*)(p)) #elif defined(BOOST_UNORDERED_SSE2) -#define BOOST_UNORDERED_PREFETCH(p) _mm_prefetch((const char*)p,_MM_HINT_T0) +#define BOOST_UNORDERED_PREFETCH(p) _mm_prefetch((const char*)(p),_MM_HINT_T0) #else -#define BOOST_UNORDERED_PREFETCH(p) +#define BOOST_UNORDERED_PREFETCH(p) ((void)0) #endif /* We have experimentally confirmed that ARM architectures get a higher - * speedup when around the first half of the element slots in a group are - * prefetched, whereas for Intel just the first cache line is best. - * Please report back if you find better tunings for some particular - * architectures. - */ + * speedup when around the first half of the element slots in a group are + * prefetched, whereas for Intel just the first cache line is best. + * Please report back if you find better tunings for some particular + * architectures. + */ + #if BOOST_ARCH_ARM /* Cache line size can't be known at compile time, so we settle on - * the very frequent value of 64B. - */ -#define BOOST_UNORDERED_PREFETCH_ELEMENTS(p) \ - do{ \ - constexpr int cache_line=64; \ - const char *p0=reinterpret_cast(p), \ - *p1=p0+sizeof(value_type)*N/2; \ - for(;p0(BOOST_UNORDERED_P), \ + *p1=p0+sizeof(*BOOST_UNORDERED_P)*(N)/2; \ + for(;p0 Date: Tue, 30 May 2023 09:19:50 -0700 Subject: [PATCH 284/327] Add merge exceptions tests for cfoa --- test/Jamfile.v2 | 1 + test/cfoa/exception_merge_tests.cpp | 78 +++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 test/cfoa/exception_merge_tests.cpp diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 08f60bab..4e256e2a 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -194,6 +194,7 @@ local CFOA_TESTS = exception_erase_tests exception_constructor_tests exception_assign_tests + exception_merge_tests ; for local test in $(CFOA_TESTS) diff --git a/test/cfoa/exception_merge_tests.cpp b/test/cfoa/exception_merge_tests.cpp new file mode 100644 index 00000000..0f54eb27 --- /dev/null +++ b/test/cfoa/exception_merge_tests.cpp @@ -0,0 +1,78 @@ +// 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 "exception_helpers.hpp" + +#include + +#include + +using allocator_type = stateful_allocator >; + +using hasher = stateful_hash; +using key_equal = stateful_key_equal; + +using map_type = boost::unordered::concurrent_flat_map; + +namespace { + test::seed_t initialize_seed(223333016); + + template void merge(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + + raii::reset_counts(); + + auto begin = values.begin(); + auto mid = begin + static_cast(values.size() / 2); + auto end = values.end(); + + { + unsigned num_throws = 0; + + for (unsigned i = 0; i < 5 * alloc_throw_threshold; ++i) { + disable_exceptions(); + + map_type x1(0, hasher(1), key_equal(2), allocator_type(3)); + x1.insert(begin, mid); + + map_type x2(0, hasher(2), key_equal(1), allocator_type(3)); + x2.insert(mid, end); + + enable_exceptions(); + try { + x1.merge(x2); + } catch (...) { + ++num_throws; + } + + disable_exceptions(); + test_fuzzy_matches_reference(x1, reference_map, rg); + test_fuzzy_matches_reference(x2, reference_map, rg); + } + + BOOST_TEST_GT(num_throws, 0u); + } + + check_raii_counts(); + } + +} // namespace + +using test::default_generator; +using test::limited_range; +using test::sequential; + +// clang-format off +UNORDERED_TEST( + merge, + ((exception_value_type_generator)) + ((default_generator)(sequential)(limited_range))) + +// clang-format on + +RUN_TESTS() From 332540c85728093cb4cc525b62211fe98d81ac9e Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 30 May 2023 12:05:10 -0700 Subject: [PATCH 285/327] Attempt to fix CMake tests --- CMakeLists.txt | 2 + test/CMakeLists.txt | 147 ++++++++++++++++++++++++++-- test/unordered/scoped_allocator.cpp | 4 +- 3 files changed, 145 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f1477e65..97874088 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,8 @@ target_link_libraries(boost_unordered Boost::mp11 Boost::predef Boost::preprocessor + Boost::smart_ptr + Boost::static_assert Boost::throw_exception Boost::tuple Boost::type_traits diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 51758339..e3484408 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,12 +6,145 @@ include(BoostTestJamfile OPTIONAL RESULT_VARIABLE HAVE_BOOST_TEST) if(HAVE_BOOST_TEST) -boost_test_jamfile( - FILE Jamfile.v2 - LINK_LIBRARIES - Boost::unordered - Boost::core - Boost::concept_check -) +add_library(boost_unordered_test_deps INTERFACE) +target_link_libraries(boost_unordered_test_deps INTERFACE Boost::unordered Boost::core Boost::concept_check) + +# FCA tests +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/prime_fmod_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/fwd_set_test.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/fwd_map_test.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/allocator_traits.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/minimal_allocator.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/compile_set.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/compile_map.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/noexcept_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/link_test_1.cpp unordered/link_test_2.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/incomplete_test.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/simple_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/equivalent_keys_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/constructor_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/copy_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/move_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/post_move_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/assign_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/insert_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/insert_stable_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/insert_hint_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/emplace_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/unnecessary_copy_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/erase_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_SUPPRESS_DEPRECATED) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/erase_equiv_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/extract_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/node_handle_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/merge_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/find_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/at_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/bucket_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/load_factor_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/rehash_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/equality_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/swap_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/deduction_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/scoped_allocator.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/transparent_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/reserve_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/contains_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/erase_if.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/scary_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/constructor_exception_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/copy_exception_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/assign_exception_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/move_assign_exception_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/insert_exception_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/erase_exception_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/rehash_exception_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/swap_exception_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_SWAP_METHOD=2) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/merge_exception_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/less_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/narrow_cast_tests.cpp) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/compile_set.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_compile_set) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/compile_map.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_compile_map) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/copy_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_copy) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/move_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_move) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/assign_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_assign) +boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES quick.cpp) + +boost_test(PREFIX unordered TYPE compile-fail LINK_LIBRARIES boost_unordered_test_deps NAME insert_node_type_fail_map COMPILE_DEFINITIONS UNORDERED_TEST_MAP SOURCES unordered/insert_node_type_fail.cpp) +boost_test(PREFIX unordered TYPE compile-fail LINK_LIBRARIES boost_unordered_test_deps NAME insert_node_type_fail_multimap COMPILE_DEFINITIONS UNORDERED_TEST_MULTIMAP SOURCES unordered/insert_node_type_fail.cpp) +boost_test(PREFIX unordered TYPE compile-fail LINK_LIBRARIES boost_unordered_test_deps NAME insert_node_type_fail_set COMPILE_DEFINITIONS UNORDERED_TEST_SET SOURCES unordered/insert_node_type_fail.cpp) +boost_test(PREFIX unordered TYPE compile-fail LINK_LIBRARIES boost_unordered_test_deps NAME insert_node_type_fail_multiset COMPILE_DEFINITIONS UNORDERED_TEST_MULTISET SOURCES unordered/insert_node_type_fail.cpp) + + +# FOA tests + +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/fwd_set_test.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/fwd_map_test.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/compile_set.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/compile_map.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/noexcept_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/incomplete_test.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/simple_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/equivalent_keys_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/constructor_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/copy_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/move_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/post_move_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/assign_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/insert_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/insert_hint_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/emplace_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/erase_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/merge_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/find_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/at_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/load_factor_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/rehash_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/equality_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/swap_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/transparent_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/reserve_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/contains_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/erase_if.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/scary_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/init_type_insert_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/max_load_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/extract_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/node_handle_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/uses_allocator.cpp) + +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/link_test_1.cpp unordered/link_test_2.cpp ) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/scoped_allocator.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/hash_is_avalanching_test.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/constructor_exception_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/copy_exception_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/assign_exception_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/move_assign_exception_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/insert_exception_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/erase_exception_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/rehash_exception_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/swap_exception_tests.cpp) +boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/merge_exception_tests.cpp) + +# CFOA tests + +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/latch_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/insert_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/erase_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/try_emplace_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/emplace_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/visit_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/constructor_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/assign_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/clear_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/swap_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/merge_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/rehash_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/equality_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/fwd_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/exception_insert_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/exception_erase_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/exception_constructor_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/exception_assign_tests.cpp) +boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/exception_merge_tests.cpp) endif() diff --git a/test/unordered/scoped_allocator.cpp b/test/unordered/scoped_allocator.cpp index 01aa1202..86e901ba 100644 --- a/test/unordered/scoped_allocator.cpp +++ b/test/unordered/scoped_allocator.cpp @@ -7,7 +7,9 @@ #include #include -#if BOOST_CXX_VERSION <= 199711L || BOOST_WORKAROUND(BOOST_GCC_VERSION, < 40800) +#if BOOST_CXX_VERSION <= 199711L || \ + BOOST_WORKAROUND(BOOST_GCC_VERSION, < 40800) || \ + BOOST_WORKAROUND(BOOST_MSVC, == 1900) BOOST_PRAGMA_MESSAGE( "scoped allocator adaptor tests only work under C++11 and above") From 06aa4b5c194e1ae52f2687c513d681aba8c04d73 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 30 May 2023 13:24:21 -0700 Subject: [PATCH 286/327] Cleanup test CML --- test/CMakeLists.txt | 270 +++++++++++++++++++++++--------------------- 1 file changed, 140 insertions(+), 130 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e3484408..5c97f692 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,145 +6,155 @@ include(BoostTestJamfile OPTIONAL RESULT_VARIABLE HAVE_BOOST_TEST) if(HAVE_BOOST_TEST) -add_library(boost_unordered_test_deps INTERFACE) -target_link_libraries(boost_unordered_test_deps INTERFACE Boost::unordered Boost::core Boost::concept_check) +set(BOOST_TEST_LINK_LIBRARIES Boost::unordered Boost::core Boost::concept_check) + +function(fca_tests) + boost_test(PREFIX boost_unordered ${ARGN}) +endfunction() + +function(foa_tests) + boost_test(PREFIX boost_unordered_foa COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS ${ARGN}) +endfunction() + +function(cfoa_tests) + boost_test(PREFIX boost_unordered_cfoa ${ARGN}) +endfunction() # FCA tests -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/prime_fmod_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/fwd_set_test.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/fwd_map_test.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/allocator_traits.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/minimal_allocator.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/compile_set.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/compile_map.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/noexcept_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/link_test_1.cpp unordered/link_test_2.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/incomplete_test.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/simple_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/equivalent_keys_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/constructor_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/copy_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/move_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/post_move_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/assign_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/insert_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/insert_stable_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/insert_hint_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/emplace_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/unnecessary_copy_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/erase_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_SUPPRESS_DEPRECATED) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/erase_equiv_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/extract_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/node_handle_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/merge_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/find_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/at_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/bucket_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/load_factor_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/rehash_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/equality_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/swap_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/deduction_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/scoped_allocator.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/transparent_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/reserve_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/contains_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/erase_if.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/scary_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/constructor_exception_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/copy_exception_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/assign_exception_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/move_assign_exception_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/insert_exception_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/erase_exception_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/rehash_exception_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/swap_exception_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_SWAP_METHOD=2) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/merge_exception_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES exception/less_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/narrow_cast_tests.cpp) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/compile_set.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_compile_set) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/compile_map.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_compile_map) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/copy_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_copy) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/move_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_move) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/assign_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_assign) -boost_test(PREFIX unordered LINK_LIBRARIES boost_unordered_test_deps SOURCES quick.cpp) -boost_test(PREFIX unordered TYPE compile-fail LINK_LIBRARIES boost_unordered_test_deps NAME insert_node_type_fail_map COMPILE_DEFINITIONS UNORDERED_TEST_MAP SOURCES unordered/insert_node_type_fail.cpp) -boost_test(PREFIX unordered TYPE compile-fail LINK_LIBRARIES boost_unordered_test_deps NAME insert_node_type_fail_multimap COMPILE_DEFINITIONS UNORDERED_TEST_MULTIMAP SOURCES unordered/insert_node_type_fail.cpp) -boost_test(PREFIX unordered TYPE compile-fail LINK_LIBRARIES boost_unordered_test_deps NAME insert_node_type_fail_set COMPILE_DEFINITIONS UNORDERED_TEST_SET SOURCES unordered/insert_node_type_fail.cpp) -boost_test(PREFIX unordered TYPE compile-fail LINK_LIBRARIES boost_unordered_test_deps NAME insert_node_type_fail_multiset COMPILE_DEFINITIONS UNORDERED_TEST_MULTISET SOURCES unordered/insert_node_type_fail.cpp) +fca_tests(SOURCES unordered/prime_fmod_tests.cpp) +fca_tests(SOURCES unordered/fwd_set_test.cpp) +fca_tests(SOURCES unordered/fwd_map_test.cpp) +fca_tests(SOURCES unordered/allocator_traits.cpp) +fca_tests(SOURCES unordered/minimal_allocator.cpp) +fca_tests(SOURCES unordered/compile_set.cpp) +fca_tests(SOURCES unordered/compile_map.cpp) +fca_tests(SOURCES unordered/noexcept_tests.cpp) +fca_tests(SOURCES unordered/link_test_1.cpp unordered/link_test_2.cpp) +fca_tests(SOURCES unordered/incomplete_test.cpp) +fca_tests(SOURCES unordered/simple_tests.cpp) +fca_tests(SOURCES unordered/equivalent_keys_tests.cpp) +fca_tests(SOURCES unordered/constructor_tests.cpp) +fca_tests(SOURCES unordered/copy_tests.cpp) +fca_tests(SOURCES unordered/move_tests.cpp) +fca_tests(SOURCES unordered/post_move_tests.cpp) +fca_tests(SOURCES unordered/assign_tests.cpp) +fca_tests(SOURCES unordered/insert_tests.cpp) +fca_tests(SOURCES unordered/insert_stable_tests.cpp) +fca_tests(SOURCES unordered/insert_hint_tests.cpp) +fca_tests(SOURCES unordered/emplace_tests.cpp) +fca_tests(SOURCES unordered/unnecessary_copy_tests.cpp) +fca_tests(SOURCES unordered/erase_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_SUPPRESS_DEPRECATED) +fca_tests(SOURCES unordered/erase_equiv_tests.cpp) +fca_tests(SOURCES unordered/extract_tests.cpp) +fca_tests(SOURCES unordered/node_handle_tests.cpp) +fca_tests(SOURCES unordered/merge_tests.cpp) +fca_tests(SOURCES unordered/find_tests.cpp) +fca_tests(SOURCES unordered/at_tests.cpp) +fca_tests(SOURCES unordered/bucket_tests.cpp) +fca_tests(SOURCES unordered/load_factor_tests.cpp) +fca_tests(SOURCES unordered/rehash_tests.cpp) +fca_tests(SOURCES unordered/equality_tests.cpp) +fca_tests(SOURCES unordered/swap_tests.cpp) +fca_tests(SOURCES unordered/deduction_tests.cpp) +fca_tests(SOURCES unordered/scoped_allocator.cpp) +fca_tests(SOURCES unordered/transparent_tests.cpp) +fca_tests(SOURCES unordered/reserve_tests.cpp) +fca_tests(SOURCES unordered/contains_tests.cpp) +fca_tests(SOURCES unordered/erase_if.cpp) +fca_tests(SOURCES unordered/scary_tests.cpp) +fca_tests(SOURCES exception/constructor_exception_tests.cpp) +fca_tests(SOURCES exception/copy_exception_tests.cpp) +fca_tests(SOURCES exception/assign_exception_tests.cpp) +fca_tests(SOURCES exception/move_assign_exception_tests.cpp) +fca_tests(SOURCES exception/insert_exception_tests.cpp) +fca_tests(SOURCES exception/erase_exception_tests.cpp) +fca_tests(SOURCES exception/rehash_exception_tests.cpp) +fca_tests(SOURCES exception/swap_exception_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_SWAP_METHOD=2) +fca_tests(SOURCES exception/merge_exception_tests.cpp) +fca_tests(SOURCES exception/less_tests.cpp) +fca_tests(SOURCES unordered/narrow_cast_tests.cpp) +fca_tests(SOURCES unordered/compile_set.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_compile_set) +fca_tests(SOURCES unordered/compile_map.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_compile_map) +fca_tests(SOURCES unordered/copy_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_copy) +fca_tests(SOURCES unordered/move_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_move) +fca_tests(SOURCES unordered/assign_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_assign) +fca_tests(SOURCES quick.cpp) +fca_tests(TYPE compile-fail NAME insert_node_type_fail_map COMPILE_DEFINITIONS UNORDERED_TEST_MAP SOURCES unordered/insert_node_type_fail.cpp) +fca_tests(TYPE compile-fail NAME insert_node_type_fail_multimap COMPILE_DEFINITIONS UNORDERED_TEST_MULTIMAP SOURCES unordered/insert_node_type_fail.cpp) +fca_tests(TYPE compile-fail NAME insert_node_type_fail_set COMPILE_DEFINITIONS UNORDERED_TEST_SET SOURCES unordered/insert_node_type_fail.cpp) +fca_tests(TYPE compile-fail NAME insert_node_type_fail_multiset COMPILE_DEFINITIONS UNORDERED_TEST_MULTISET SOURCES unordered/insert_node_type_fail.cpp) # FOA tests -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/fwd_set_test.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/fwd_map_test.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/compile_set.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/compile_map.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/noexcept_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/incomplete_test.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/simple_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/equivalent_keys_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/constructor_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/copy_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/move_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/post_move_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/assign_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/insert_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/insert_hint_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/emplace_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/erase_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/merge_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/find_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/at_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/load_factor_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/rehash_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/equality_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/swap_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/transparent_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/reserve_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/contains_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/erase_if.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/scary_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/init_type_insert_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/max_load_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/extract_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/node_handle_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/uses_allocator.cpp) - -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/link_test_1.cpp unordered/link_test_2.cpp ) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES unordered/scoped_allocator.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps SOURCES unordered/hash_is_avalanching_test.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/constructor_exception_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/copy_exception_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/assign_exception_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/move_assign_exception_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/insert_exception_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/erase_exception_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/rehash_exception_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/swap_exception_tests.cpp) -boost_test(PREFIX unordered-foa LINK_LIBRARIES boost_unordered_test_deps COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS SOURCES exception/merge_exception_tests.cpp) +foa_tests(SOURCES unordered/fwd_set_test.cpp) +foa_tests(SOURCES unordered/fwd_map_test.cpp) +foa_tests(SOURCES unordered/compile_set.cpp) +foa_tests(SOURCES unordered/compile_map.cpp) +foa_tests(SOURCES unordered/noexcept_tests.cpp) +foa_tests(SOURCES unordered/incomplete_test.cpp) +foa_tests(SOURCES unordered/simple_tests.cpp) +foa_tests(SOURCES unordered/equivalent_keys_tests.cpp) +foa_tests(SOURCES unordered/constructor_tests.cpp) +foa_tests(SOURCES unordered/copy_tests.cpp) +foa_tests(SOURCES unordered/move_tests.cpp) +foa_tests(SOURCES unordered/post_move_tests.cpp) +foa_tests(SOURCES unordered/assign_tests.cpp) +foa_tests(SOURCES unordered/insert_tests.cpp) +foa_tests(SOURCES unordered/insert_hint_tests.cpp) +foa_tests(SOURCES unordered/emplace_tests.cpp) +foa_tests(SOURCES unordered/erase_tests.cpp) +foa_tests(SOURCES unordered/merge_tests.cpp) +foa_tests(SOURCES unordered/find_tests.cpp) +foa_tests(SOURCES unordered/at_tests.cpp) +foa_tests(SOURCES unordered/load_factor_tests.cpp) +foa_tests(SOURCES unordered/rehash_tests.cpp) +foa_tests(SOURCES unordered/equality_tests.cpp) +foa_tests(SOURCES unordered/swap_tests.cpp) +foa_tests(SOURCES unordered/transparent_tests.cpp) +foa_tests(SOURCES unordered/reserve_tests.cpp) +foa_tests(SOURCES unordered/contains_tests.cpp) +foa_tests(SOURCES unordered/erase_if.cpp) +foa_tests(SOURCES unordered/scary_tests.cpp) +foa_tests(SOURCES unordered/init_type_insert_tests.cpp) +foa_tests(SOURCES unordered/max_load_tests.cpp) +foa_tests(SOURCES unordered/extract_tests.cpp) +foa_tests(SOURCES unordered/node_handle_tests.cpp) +foa_tests(SOURCES unordered/uses_allocator.cpp) +foa_tests(SOURCES unordered/link_test_1.cpp unordered/link_test_2.cpp ) +foa_tests(SOURCES unordered/scoped_allocator.cpp) +foa_tests(SOURCES unordered/hash_is_avalanching_test.cpp) +foa_tests(SOURCES exception/constructor_exception_tests.cpp) +foa_tests(SOURCES exception/copy_exception_tests.cpp) +foa_tests(SOURCES exception/assign_exception_tests.cpp) +foa_tests(SOURCES exception/move_assign_exception_tests.cpp) +foa_tests(SOURCES exception/insert_exception_tests.cpp) +foa_tests(SOURCES exception/erase_exception_tests.cpp) +foa_tests(SOURCES exception/rehash_exception_tests.cpp) +foa_tests(SOURCES exception/swap_exception_tests.cpp) +foa_tests(SOURCES exception/merge_exception_tests.cpp) # CFOA tests -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/latch_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/insert_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/erase_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/try_emplace_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/emplace_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/visit_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/constructor_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/assign_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/clear_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/swap_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/merge_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/rehash_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/equality_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/fwd_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/exception_insert_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/exception_erase_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/exception_constructor_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/exception_assign_tests.cpp) -boost_test(PREFIX unordered-cfoa LINK_LIBRARIES boost_unordered_test_deps SOURCES cfoa/exception_merge_tests.cpp) +cfoa_tests(SOURCES cfoa/latch_tests.cpp) +cfoa_tests(SOURCES cfoa/insert_tests.cpp) +cfoa_tests(SOURCES cfoa/erase_tests.cpp) +cfoa_tests(SOURCES cfoa/try_emplace_tests.cpp) +cfoa_tests(SOURCES cfoa/emplace_tests.cpp) +cfoa_tests(SOURCES cfoa/visit_tests.cpp) +cfoa_tests(SOURCES cfoa/constructor_tests.cpp) +cfoa_tests(SOURCES cfoa/assign_tests.cpp) +cfoa_tests(SOURCES cfoa/clear_tests.cpp) +cfoa_tests(SOURCES cfoa/swap_tests.cpp) +cfoa_tests(SOURCES cfoa/merge_tests.cpp) +cfoa_tests(SOURCES cfoa/rehash_tests.cpp) +cfoa_tests(SOURCES cfoa/equality_tests.cpp) +cfoa_tests(SOURCES cfoa/fwd_tests.cpp) +cfoa_tests(SOURCES cfoa/exception_insert_tests.cpp) +cfoa_tests(SOURCES cfoa/exception_erase_tests.cpp) +cfoa_tests(SOURCES cfoa/exception_constructor_tests.cpp) +cfoa_tests(SOURCES cfoa/exception_assign_tests.cpp) +cfoa_tests(SOURCES cfoa/exception_merge_tests.cpp) endif() From 6e0f76f4c2133d58fae4842529b0c284e5596770 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 30 May 2023 14:52:45 -0700 Subject: [PATCH 287/327] Add missing FindThreads to CML --- test/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5c97f692..f9dd0e45 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,6 +6,9 @@ include(BoostTestJamfile OPTIONAL RESULT_VARIABLE HAVE_BOOST_TEST) if(HAVE_BOOST_TEST) +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + set(BOOST_TEST_LINK_LIBRARIES Boost::unordered Boost::core Boost::concept_check) function(fca_tests) @@ -17,7 +20,7 @@ function(foa_tests) endfunction() function(cfoa_tests) - boost_test(PREFIX boost_unordered_cfoa ${ARGN}) + boost_test(PREFIX boost_unordered_cfoa LINK_LIBRARIES Threads::Threads ${ARGN}) endfunction() # FCA tests From 51520de04b3ba32013657b1fe83fbf6c653fc6ec Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 31 May 2023 07:30:37 -0700 Subject: [PATCH 288/327] Add allocator using fancy pointers to insert_tests --- test/cfoa/helpers.hpp | 192 +++++++++++++++++++++++++++++++++++++ test/cfoa/insert_tests.cpp | 5 +- 2 files changed, 196 insertions(+), 1 deletion(-) diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index bd773e8d..2ba3f326 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -403,4 +403,196 @@ template void shuffle_values(std::vector& v) std::shuffle(v.begin(), v.end(), g); } +template class ptr; +template class const_ptr; +template class fancy_allocator; + +struct void_ptr +{ + template friend class ptr; + +private: + void* ptr_; + +public: + void_ptr() : ptr_(0) {} + + template explicit void_ptr(ptr const& x) : ptr_(x.ptr_) {} + + // I'm not using the safe bool idiom because the containers should be + // able to cope with bool conversions. + operator bool() const { return !!ptr_; } + + bool operator==(void_ptr const& x) const { return ptr_ == x.ptr_; } + bool operator!=(void_ptr const& x) const { return ptr_ != x.ptr_; } +}; + +class void_const_ptr +{ + template friend class const_ptr; + +private: + void* ptr_; + +public: + void_const_ptr() : ptr_(0) {} + + template + explicit void_const_ptr(const_ptr const& x) : ptr_(x.ptr_) + { + } + + // I'm not using the safe bool idiom because the containers should be + // able to cope with bool conversions. + operator bool() const { return !!ptr_; } + + bool operator==(void_const_ptr const& x) const { return ptr_ == x.ptr_; } + bool operator!=(void_const_ptr const& x) const { return ptr_ != x.ptr_; } +}; + +template class ptr +{ + friend class fancy_allocator; + friend class const_ptr; + friend struct void_ptr; + + T* ptr_; + + ptr(T* x) : ptr_(x) {} + +public: + ptr() : ptr_(0) {} + explicit ptr(void_ptr const& x) : ptr_((T*)x.ptr_) {} + + T& operator*() const { return *ptr_; } + T* operator->() const { return ptr_; } + ptr& operator++() + { + ++ptr_; + return *this; + } + ptr operator++(int) + { + ptr tmp(*this); + ++ptr_; + return tmp; + } + ptr operator+(std::ptrdiff_t s) const { return ptr(ptr_ + s); } + friend ptr operator+(std::ptrdiff_t s, ptr p) { return ptr(s + p.ptr_); } + + std::ptrdiff_t operator-(ptr p) const { return ptr_ - p.ptr_; } + ptr operator-(std::ptrdiff_t s) const { return ptr(ptr_ - s); } + T& operator[](std::ptrdiff_t s) const { return ptr_[s]; } + bool operator!() const { return !ptr_; } + + static ptr pointer_to(T& p) { return ptr(boost::addressof(p)); } + + // I'm not using the safe bool idiom because the containers should be + // able to cope with bool conversions. + operator bool() const { return !!ptr_; } + + bool operator==(ptr const& x) const { return ptr_ == x.ptr_; } + bool operator!=(ptr const& x) const { return ptr_ != x.ptr_; } + bool operator<(ptr const& x) const { return ptr_ < x.ptr_; } + bool operator>(ptr const& x) const { return ptr_ > x.ptr_; } + bool operator<=(ptr const& x) const { return ptr_ <= x.ptr_; } + bool operator>=(ptr const& x) const { return ptr_ >= x.ptr_; } +}; + +template class const_ptr +{ + friend class fancy_allocator; + friend struct const_void_ptr; + + T const* ptr_; + + const_ptr(T const* ptr) : ptr_(ptr) {} + +public: + const_ptr() : ptr_(0) {} + const_ptr(ptr const& x) : ptr_(x.ptr_) {} + explicit const_ptr(void_const_ptr const& x) : ptr_((T const*)x.ptr_) {} + + T const& operator*() const { return *ptr_; } + T const* operator->() const { return ptr_; } + const_ptr& operator++() + { + ++ptr_; + return *this; + } + const_ptr operator++(int) + { + const_ptr tmp(*this); + ++ptr_; + return tmp; + } + const_ptr operator+(std::ptrdiff_t s) const { return const_ptr(ptr_ + s); } + friend const_ptr operator+(std::ptrdiff_t s, const_ptr p) + { + return ptr(s + p.ptr_); + } + T const& operator[](int s) const { return ptr_[s]; } + bool operator!() const { return !ptr_; } + operator bool() const { return !!ptr_; } + + bool operator==(const_ptr const& x) const { return ptr_ == x.ptr_; } + bool operator!=(const_ptr const& x) const { return ptr_ != x.ptr_; } + bool operator<(const_ptr const& x) const { return ptr_ < x.ptr_; } + bool operator>(const_ptr const& x) const { return ptr_ > x.ptr_; } + bool operator<=(const_ptr const& x) const { return ptr_ <= x.ptr_; } + bool operator>=(const_ptr const& x) const { return ptr_ >= x.ptr_; } +}; + +template class fancy_allocator +{ +public: + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef void_ptr void_pointer; + typedef void_const_ptr const_void_pointer; + typedef ptr pointer; + typedef const_ptr const_pointer; + typedef T& reference; + typedef T const& const_reference; + typedef T value_type; + + template struct rebind + { + typedef fancy_allocator other; + }; + + fancy_allocator() {} + template fancy_allocator(fancy_allocator const&) {} + fancy_allocator(fancy_allocator const&) {} + ~fancy_allocator() {} + + pointer address(reference r) { return pointer(&r); } + const_pointer address(const_reference r) { return const_pointer(&r); } + + pointer allocate(size_type n) + { + return pointer(static_cast(::operator new(n * sizeof(T)))); + } + + template pointer allocate(size_type n, const_ptr) + { + return pointer(static_cast(::operator new(n * sizeof(T)))); + } + + void deallocate(pointer p, size_type) { ::operator delete((void*)p.ptr_); } + + template + void construct(U* p, Args&&... args) + { + new ((void*)p) U(std::forward(args)...); + } + + template void destroy(U* p) { p->~U(); } + + size_type max_size() const { return 1000; } + +public: + fancy_allocator& operator=(fancy_allocator const&) { return *this; } +}; + #endif // BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP \ No newline at end of file diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index 01b628ef..8566d43d 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -543,6 +543,9 @@ namespace { boost::unordered::concurrent_flat_map* map; boost::unordered::concurrent_flat_map* trans_map; + boost::unordered::concurrent_flat_map, + std::equal_to, fancy_allocator > >* + fancy_map; } // namespace @@ -557,7 +560,7 @@ UNORDERED_TEST( UNORDERED_TEST( insert, - ((map)) + ((map)(fancy_map)) ((value_type_generator)(init_type_generator)) ((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter) (norehash_lvalue_inserter)(norehash_rvalue_inserter) From 9a22f8fbee43a022db50ae57c3e318b4d98847bc Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 31 May 2023 09:14:44 -0700 Subject: [PATCH 289/327] Add missing dependency on SmartPtr --- test/cmake_subdir_test/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/cmake_subdir_test/CMakeLists.txt b/test/cmake_subdir_test/CMakeLists.txt index 745cd109..d356029b 100644 --- a/test/cmake_subdir_test/CMakeLists.txt +++ b/test/cmake_subdir_test/CMakeLists.txt @@ -22,6 +22,7 @@ move mp11 predef preprocessor +smart_ptr static_assert throw_exception tuple From e7c1e1a56e4dab661225f568847f87732beb9d47 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 1 Jun 2023 14:18:54 -0700 Subject: [PATCH 290/327] Clean up raii count checkers to avoid extraneous assertions --- test/cfoa/exception_helpers.hpp | 3 --- test/cfoa/helpers.hpp | 6 +----- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/test/cfoa/exception_helpers.hpp b/test/cfoa/exception_helpers.hpp index 79bee58e..691936f6 100644 --- a/test/cfoa/exception_helpers.hpp +++ b/test/cfoa/exception_helpers.hpp @@ -416,9 +416,6 @@ template using span_value_type = typename T::value_type; void check_raii_counts() { - BOOST_TEST_GE(raii::default_constructor, 0u); - BOOST_TEST_GE(raii::copy_constructor, 0u); - BOOST_TEST_GE(raii::move_constructor, 0u); BOOST_TEST_GT(raii::destructor, 0u); BOOST_TEST_EQ( diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index 2ba3f326..517326bf 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -385,9 +385,6 @@ template using span_value_type = typename T::value_type; void check_raii_counts() { - BOOST_TEST_GE(raii::default_constructor, 0u); - BOOST_TEST_GE(raii::copy_constructor, 0u); - BOOST_TEST_GE(raii::move_constructor, 0u); BOOST_TEST_GT(raii::destructor, 0u); BOOST_TEST_EQ( @@ -581,8 +578,7 @@ public: void deallocate(pointer p, size_type) { ::operator delete((void*)p.ptr_); } - template - void construct(U* p, Args&&... args) + template void construct(U* p, Args&&... args) { new ((void*)p) U(std::forward(args)...); } From 44c50cd2eaa1f85caab0737323020053b1412ee3 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 1 Jun 2023 14:19:14 -0700 Subject: [PATCH 291/327] Resolve potential ambiguities during insertion by introducing a member function template --- .../boost/unordered/concurrent_flat_map.hpp | 57 ++++--------------- test/cfoa/insert_tests.cpp | 12 ++++ 2 files changed, 24 insertions(+), 45 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index d3a0c565..585b4a87 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -409,19 +409,13 @@ namespace boost { /// Modifiers /// - BOOST_FORCEINLINE bool insert(value_type const& obj) + template + BOOST_FORCEINLINE auto insert(Ty&& value) + -> decltype(table_.insert(std::forward(value))) { - return table_.insert(obj); - } - BOOST_FORCEINLINE bool insert(value_type&& obj) - { - return table_.insert(std::move(obj)); + return table_.insert(std::forward(value)); } - BOOST_FORCEINLINE bool insert(init_type const& obj) - { - return table_.insert(obj); - } BOOST_FORCEINLINE bool insert(init_type&& obj) { return table_.insert(std::move(obj)); @@ -464,25 +458,11 @@ namespace boost { [&](value_type& m) { m.second = std::forward(obj); }); } - template - BOOST_FORCEINLINE bool insert_or_visit(value_type const& obj, F f) + template + BOOST_FORCEINLINE auto insert_or_visit(Ty&& value, F f) + -> decltype(table_.insert_or_visit(std::forward(value), f)) { - BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) - return table_.insert_or_visit(obj, f); - } - - template - BOOST_FORCEINLINE bool insert_or_visit(value_type&& obj, F f) - { - BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) - return table_.insert_or_visit(std::move(obj), f); - } - - template - BOOST_FORCEINLINE bool insert_or_visit(init_type const& obj, F f) - { - BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) - return table_.insert_or_visit(obj, f); + return table_.insert_or_visit(std::forward(value), f); } template @@ -508,25 +488,12 @@ namespace boost { this->insert_or_visit(ilist.begin(), ilist.end(), f); } - template - BOOST_FORCEINLINE bool insert_or_cvisit(value_type const& obj, F f) + template + BOOST_FORCEINLINE auto insert_or_cvisit(Ty&& value, F f) + -> decltype(table_.insert_or_cvisit(std::forward(value), f)) { BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) - return table_.insert_or_cvisit(obj, f); - } - - template - BOOST_FORCEINLINE bool insert_or_cvisit(value_type&& obj, F f) - { - BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) - return table_.insert_or_cvisit(std::move(obj), f); - } - - template - BOOST_FORCEINLINE bool insert_or_cvisit(init_type const& obj, F f) - { - BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) - return table_.insert_or_cvisit(obj, f); + return table_.insert_or_cvisit(std::forward(value), f); } template diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index 8566d43d..6aaaf497 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -540,6 +540,18 @@ namespace { } } + UNORDERED_AUTO_TEST (insert_sfinae_test) { + // mostly a compile-time tests to ensure that there's no ambiguity when a + // user does this + using value_type = + typename boost::unordered::concurrent_flat_map::value_type; + boost::unordered::concurrent_flat_map x; + x.insert({1, 2}); + + x.insert_or_visit({2, 3}, [](value_type&) {}); + x.insert_or_cvisit({3, 4}, [](value_type const&) {}); + } + boost::unordered::concurrent_flat_map* map; boost::unordered::concurrent_flat_map* trans_map; From 8877d2123736e71e6838a210a8c8f569614d35fe Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 2 Jun 2023 14:14:18 -0700 Subject: [PATCH 292/327] Replace dependency on SmartPtr with primitives in Core --- CMakeLists.txt | 1 - include/boost/unordered/detail/foa/rw_spinlock.hpp | 11 +++++------ test/cmake_subdir_test/CMakeLists.txt | 1 - 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 97874088..e7a1d024 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,6 @@ target_link_libraries(boost_unordered Boost::mp11 Boost::predef Boost::preprocessor - Boost::smart_ptr Boost::static_assert Boost::throw_exception Boost::tuple diff --git a/include/boost/unordered/detail/foa/rw_spinlock.hpp b/include/boost/unordered/detail/foa/rw_spinlock.hpp index a429e5e0..83e00255 100644 --- a/include/boost/unordered/detail/foa/rw_spinlock.hpp +++ b/include/boost/unordered/detail/foa/rw_spinlock.hpp @@ -5,8 +5,7 @@ // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt -#include -#include +#include #include #include @@ -64,10 +63,10 @@ public: if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return; } - boost::detail::sp_thread_pause(); + boost::core::sp_thread_pause(); } - boost::detail::sp_thread_sleep(); + boost::core::sp_thread_sleep(); } } @@ -132,7 +131,7 @@ public: state_.compare_exchange_weak( st, newst, std::memory_order_relaxed, std::memory_order_relaxed ); } - boost::detail::sp_thread_pause(); + boost::core::sp_thread_pause(); } // clear writer pending bit before going to sleep @@ -169,7 +168,7 @@ public: } } - boost::detail::sp_thread_sleep(); + boost::core::sp_thread_sleep(); } } diff --git a/test/cmake_subdir_test/CMakeLists.txt b/test/cmake_subdir_test/CMakeLists.txt index d356029b..745cd109 100644 --- a/test/cmake_subdir_test/CMakeLists.txt +++ b/test/cmake_subdir_test/CMakeLists.txt @@ -22,7 +22,6 @@ move mp11 predef preprocessor -smart_ptr static_assert throw_exception tuple From 4efb55146a47fe79336d2935be2144ed58b575e6 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 2 Jun 2023 15:08:29 -0700 Subject: [PATCH 293/327] Update gcc used for code coverage collection --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2aba3970..96dfa72a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: - { name: "gcc-12 w/ sanitizers (17,20,2b)", sanitize: yes, compiler: gcc-12, cxxstd: '17,20,2b', os: ubuntu-22.04, ccache_key: "san2" } - { name: Collect coverage, coverage: yes, - compiler: gcc-8, cxxstd: '03,11', os: ubuntu-20.04, install: 'g++-8-multilib', address-model: '32,64', ccache_key: "cov" } + compiler: gcc-12, cxxstd: '03,20', os: ubuntu-22.04, install: 'g++-12-multilib', address-model: '32,64', ccache_key: "cov" } - { name: "cfoa tsan (gcc)", cxxstd: '11,14,17,20,2b', os: ubuntu-22.04, compiler: gcc-12, targets: 'libs/unordered/test//cfoa_tests', thread-sanitize: yes } From e6b1ef9e1ecab33ac040c34ea6df4cf995486472 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 5 Jun 2023 09:32:11 -0700 Subject: [PATCH 294/327] Add ubsan gcc-12 runners to drone --- .drone.jsonnet | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.drone.jsonnet b/.drone.jsonnet index c8b03a84..c99a2629 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -300,6 +300,34 @@ local windows_pipeline(name, image, environment, arch = "amd64") = "g++-12-multilib", ), + linux_pipeline( + "Linux 22.04 GCC 12 64 UBSAN (03,11,14)", + "cppalliance/droneubuntu2204:1", + { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '03,11,14' } + ubsan, + "g++-12-multilib", + ), + + linux_pipeline( + "Linux 22.04 GCC 12 64 UBSAN (17)", + "cppalliance/droneubuntu2204:1", + { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17', ADDRMD: '64' } + ubsan, + "g++-12-multilib", + ), + + linux_pipeline( + "Linux 22.04 GCC 12 64 UBSAN (20)", + "cppalliance/droneubuntu2204:1", + { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '20', ADDRMD: '64' } + ubsan, + "g++-12-multilib", + ), + + linux_pipeline( + "Linux 22.04 GCC 12 64 UBSAN (2b)", + "cppalliance/droneubuntu2204:1", + { TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '2b', ADDRMD: '64' } + ubsan, + "g++-12-multilib", + ), + linux_pipeline( "Linux 16.04 Clang 3.5", "cppalliance/droneubuntu1604:1", From 1d2be664a0a7ce6386fd628418504a3226fe4638 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 5 Jun 2023 20:08:32 +0200 Subject: [PATCH 295/327] added boost::concurrent_flat_map benchmarks --- .../Parallel workload.xlsx.500k, 0.01.png | Bin 0 -> 27958 bytes .../Parallel workload.xlsx.500k, 0.5.png | Bin 0 -> 29126 bytes .../Parallel workload.xlsx.500k, 0.99.png | Bin 0 -> 25927 bytes .../Parallel workload.xlsx.5M, 0.01.png | Bin 0 -> 25054 bytes .../Parallel workload.xlsx.5M, 0.5.png | Bin 0 -> 25754 bytes .../Parallel workload.xlsx.5M, 0.99.png | Bin 0 -> 25754 bytes .../Parallel workload.xlsx.500k, 0.01.png | Bin 0 -> 27668 bytes .../Parallel workload.xlsx.500k, 0.5.png | Bin 0 -> 28193 bytes .../Parallel workload.xlsx.500k, 0.99.png | Bin 0 -> 26049 bytes .../Parallel workload.xlsx.5M, 0.01.png | Bin 0 -> 28238 bytes .../Parallel workload.xlsx.5M, 0.5.png | Bin 0 -> 27774 bytes .../Parallel workload.xlsx.5M, 0.99.png | Bin 0 -> 25383 bytes .../Parallel workload.xlsx.500k, 0.01.png | Bin 0 -> 26322 bytes .../Parallel workload.xlsx.500k, 0.5.png | Bin 0 -> 26898 bytes .../Parallel workload.xlsx.500k, 0.99.png | Bin 0 -> 27129 bytes .../Parallel workload.xlsx.5M, 0.01.png | Bin 0 -> 26012 bytes .../Parallel workload.xlsx.5M, 0.5.png | Bin 0 -> 26872 bytes .../Parallel workload.xlsx.5M, 0.99.png | Bin 0 -> 25002 bytes .../Parallel workload.xlsx.500k, 0.01.png | Bin 0 -> 26754 bytes .../Parallel workload.xlsx.500k, 0.5.png | Bin 0 -> 26057 bytes .../Parallel workload.xlsx.500k, 0.99.png | Bin 0 -> 25458 bytes .../Parallel workload.xlsx.5M, 0.01.png | Bin 0 -> 24966 bytes .../Parallel workload.xlsx.5M, 0.5.png | Bin 0 -> 25724 bytes .../Parallel workload.xlsx.5M, 0.99.png | Bin 0 -> 25655 bytes .../Parallel workload.xlsx.500k, 0.01.png | Bin 0 -> 27332 bytes .../Parallel workload.xlsx.500k, 0.5.png | Bin 0 -> 25948 bytes .../Parallel workload.xlsx.500k, 0.99.png | Bin 0 -> 26374 bytes .../Parallel workload.xlsx.5M, 0.01.png | Bin 0 -> 27247 bytes .../Parallel workload.xlsx.5M, 0.5.png | Bin 0 -> 25134 bytes .../Parallel workload.xlsx.5M, 0.99.png | Bin 0 -> 24876 bytes .../Parallel workload.xlsx.500k, 0.01.png | Bin 0 -> 27632 bytes .../Parallel workload.xlsx.500k, 0.5.png | Bin 0 -> 27193 bytes .../Parallel workload.xlsx.500k, 0.99.png | Bin 0 -> 25995 bytes .../Parallel workload.xlsx.5M, 0.01.png | Bin 0 -> 25026 bytes .../vs-x64/Parallel workload.xlsx.5M, 0.5.png | Bin 0 -> 25065 bytes .../Parallel workload.xlsx.5M, 0.99.png | Bin 0 -> 23242 bytes .../Parallel workload.xlsx.500k, 0.01.png | Bin 0 -> 26806 bytes .../Parallel workload.xlsx.500k, 0.5.png | Bin 0 -> 27648 bytes .../Parallel workload.xlsx.500k, 0.99.png | Bin 0 -> 25898 bytes .../Parallel workload.xlsx.5M, 0.01.png | Bin 0 -> 24393 bytes .../vs-x86/Parallel workload.xlsx.5M, 0.5.png | Bin 0 -> 24540 bytes .../Parallel workload.xlsx.5M, 0.99.png | Bin 0 -> 25013 bytes doc/unordered/benchmarks.adoc | 260 ++++++++++++++++++ 43 files changed, 260 insertions(+) create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.500k, 0.01.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.500k, 0.5.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.500k, 0.99.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.5M, 0.01.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.5M, 0.5.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.5M, 0.99.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.500k, 0.01.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.500k, 0.5.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.500k, 0.99.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.5M, 0.01.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.5M, 0.5.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.5M, 0.99.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-x86/Parallel workload.xlsx.500k, 0.01.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-x86/Parallel workload.xlsx.500k, 0.5.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-x86/Parallel workload.xlsx.500k, 0.99.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-x86/Parallel workload.xlsx.5M, 0.01.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-x86/Parallel workload.xlsx.5M, 0.5.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/clang-x86/Parallel workload.xlsx.5M, 0.99.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/gcc-x64/Parallel workload.xlsx.500k, 0.01.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/gcc-x64/Parallel workload.xlsx.500k, 0.5.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/gcc-x64/Parallel workload.xlsx.500k, 0.99.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/gcc-x64/Parallel workload.xlsx.5M, 0.01.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/gcc-x64/Parallel workload.xlsx.5M, 0.5.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/gcc-x64/Parallel workload.xlsx.5M, 0.99.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.500k, 0.01.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.500k, 0.5.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.500k, 0.99.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.5M, 0.01.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.5M, 0.5.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.5M, 0.99.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/vs-x64/Parallel workload.xlsx.500k, 0.01.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/vs-x64/Parallel workload.xlsx.500k, 0.5.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/vs-x64/Parallel workload.xlsx.500k, 0.99.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/vs-x64/Parallel workload.xlsx.5M, 0.01.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/vs-x64/Parallel workload.xlsx.5M, 0.5.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/vs-x64/Parallel workload.xlsx.5M, 0.99.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/vs-x86/Parallel workload.xlsx.500k, 0.01.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/vs-x86/Parallel workload.xlsx.500k, 0.5.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/vs-x86/Parallel workload.xlsx.500k, 0.99.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/vs-x86/Parallel workload.xlsx.5M, 0.01.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/vs-x86/Parallel workload.xlsx.5M, 0.5.png create mode 100644 doc/diagrams/benchmarks-concurrent_map/vs-x86/Parallel workload.xlsx.5M, 0.99.png diff --git a/doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.500k, 0.01.png b/doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.500k, 0.01.png new file mode 100644 index 0000000000000000000000000000000000000000..c220964dd5cf8abdad5c18a3a958802313836534 GIT binary patch literal 27958 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¥VSN!NjU0wOpDCO$C{SqxNUYY*=`my-S^_yGE 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 literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.500k, 0.5.png b/doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.500k, 0.5.png new file mode 100644 index 0000000000000000000000000000000000000000..72a673bcd563182e49b21b91be262b425fe7d5fc GIT binary patch literal 29126 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 a{-6D8R89FNf2r-Doa^c8=d#Wzp$P!N1Zc7V literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.500k, 0.99.png b/doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.500k, 0.99.png new file mode 100644 index 0000000000000000000000000000000000000000..113490362a4c093d470ff64a76e9dc8a5c331e55 GIT binary patch literal 25927 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 literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.5M, 0.01.png b/doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.5M, 0.01.png new file mode 100644 index 0000000000000000000000000000000000000000..a0f6241453e2f43d64d81c8d6dea8a3bd286b7ab GIT binary patch literal 25054 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 X&^ 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 literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.5M, 0.99.png b/doc/diagrams/benchmarks-concurrent_map/clang-arm64/Parallel workload.xlsx.5M, 0.99.png new file mode 100644 index 0000000000000000000000000000000000000000..bb6ea2ac21baf0ba40b0fe2b84e8c3a6d72c6d53 GIT binary patch literal 25754 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 literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.500k, 0.5.png b/doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.500k, 0.5.png new file mode 100644 index 0000000000000000000000000000000000000000..0544d8f3b6778075061dc01db1c34616d1711830 GIT binary patch literal 28193 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 Cq!=Us literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.500k, 0.99.png b/doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.500k, 0.99.png new file mode 100644 index 0000000000000000000000000000000000000000..99ccc225da84c584891b3dead429dd961bc4cd30 GIT binary patch literal 26049 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⩔-#~-0v5ocwU zf;I+1b0D;t@T=hk-^>?%y}hZBUEQ{~Dr8*DuY9-$JM?_l>vgAXmq8DI|FZ8+#p7OI z>({bX^B0Q7ZcI5jsn2Em2?Lqm1~r>~YvoUZ4(?WTmj7XYThZD2&EmOr7B>RA^UO_m zxeCO}@5lh1M4pn-Ub_W+W_kOS?}8U~4T5jK_>{N_EW#)G>%jk;e3G{k<=-moVm-8i z+cBs6(QV$t1#ecJ{P*K=|Lp!tDqs19K?~==hoeKL5EZ}5OxKUU_h)rE&(s%P6*94} zFC;msJHA@+%&IhOO~k}6?DIg&31tz>Mh=)3a=1bchv)0i1D*Qbqe3m(II86Mv16Me zH>ahZo~D~^$2?cyQ}9gi=>`uDJ`-Be{Om{Ej)I3FZCc%8x>N53sH%p7KyKea=K# z+o~-eD^5;Q^@wV3;lA+WN5yB!4^5vp8msKwc;}U2(8YPHesg@Ene{5=MC~%$P|2eC zGas7nddTvCDaHN1nz&AcL8)U?)((cRZ6_gT30(0gbNKjq_3Y;>m(TNRGhMN(zhM5# zWqEFQ(xWbGIlnXR&=Xf+KVjqce#dh^|DuXhiaG2 zvXU&EpXtAvXXkl^T`>(jM{<7&a9gih``~$!G)L6;7u-%Y#($>Q{}eA;<9M~U<+qRN zTcbyI!Tr0Q-}Ko7))M1z>5W2W*^kW2?B|aDQrJ~C!Nj`Mcuwd1T$kzG6S%i@Z*guj zn_z7Jq9;t=<+|V9o*i!6+IXUyUlcLE;;M}}XZd{2Gv$4m<%dB79lLM5Se~4vyt}>l zLi>l-#@9)<0K{n100|m*#Mbdgt7T)#Oi0ODGY!{+vZ534NcjucA>&drrPtLTp zB)z+{Gx)iZWmqsIYC_%aq_0|eYQpa`JnJvAGB7YSTx5mN6Ip0T7d(KjLf!oFw#Q%D@mH4hpxvh?3?NFFz`{n0q7%?D#Ob=Hv38 zMhkU>_Qx;yvG1edl!vOj-uAh^sC|^0xc_wD!X4*7emneMkYRzeKrDlVWzt1a$8{xZ zUvGbL%yie=x=HJmcctF$xbG~obYsW#7u&3-S%?en-g25tVyyRd^a%fv`S)SF{5+v`MkiMZ#r9tMaDbWL=kFW2 z)AKlPU)dCJi!)X7quue>?5kYNpDZYy8hEoXtaDvPxsKES<_~|KPLH2Ax9nDCaO9ES z3wq4*@6DN8_v>YE>DPOSwo@(g85;Z?OF$QkB&blE&g^)1zV6%RWAflDOae4SPQ93t c-T&wRq606d*qr&@3aS7+UHx3vIVCg!02aVACjbBd literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.5M, 0.01.png b/doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.5M, 0.01.png new file mode 100644 index 0000000000000000000000000000000000000000..74a6bbaaab9df6e24d077323e99f9f334ecc78f2 GIT binary patch literal 28238 zcmeAS@N?(olHy`uVBq!ia0y~yVA{pNz<7{@je&uo?Sn)c0|NtNage(c!@6@aFBupZ zSkfJR9T^xl_H+M9WMyDr;4JWnEM{QfI}E~%$MaXDFfimk_H=O!sfc^KH+oCR)w$cx zikin19_PJN_*kOW`Q5$M`#yY1t+}!B^U}$F_f*vOUA$qKWLK;(@tTXTgO%g$;MZn( zBIP_uQo<&?%(?Ph9aK1WF?Y=taFKHP*ut~<@r?L7*Rw_IwuXnUUG+aEbaU5p$z;B@ zQKb>Dk6YPPUyJ!~nVynz*fia`#ldr%aFXxxPL={&vRwu(eTBE9WFdZ_k_i`{()kxE(!V>n1MT3qpx=-1@$`wSmyL z`~UymFSTCHSL;7%k#^%*?_5#aU7C5-^VUXh-_>vTYsEhe-@M&#&Az|e{r;N#Qg+E( ziq6(4675%tRUowN^N+{n*Z+?%Rngq#I!Sw##NzH3#~2T8aBeF*ae=QXv7P7bghUyE zyGLBEcV|z0!WCb7usb)dW;f%-US^2wLRpr>e3G{m4_heg>g-&1#B|NV*S9yNo_@9d z#I=jPfl)j-ROZ<`lss-oxB+rd20O$?1HKNuMcpslm>^sv2gJIFU+iVUw6Q~Pku)d7 z0nXMOZL&sNoZAW=N(5tF%!Q!bG65dzl#F(s#|bK)G)fBtTx2JjQs@wM6i) z2b6ou(csO5g|bFH%!|5TDD9f)0O4+FhPYIM-9`MOYl(6rggc@g?rC}0{C?$8TZ6)_ z)mis*-yPY#{@Q$Ben@P9k^?w$RL=5A-tsg(d|6(u_

ZO}nfA#7pH~UYC9MU~=@` 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 >cb`}GTH{mY*2QoAHK(Feh#6?zPS1l?Z+CsV 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 Ze@6M3$k&^jOBR5#ucxb@%Q~loCICmfZo2>g literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.5M, 0.5.png b/doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.5M, 0.5.png new file mode 100644 index 0000000000000000000000000000000000000000..a4e3ff8e0f9471b9392ca7516a717ebf58a641f7 GIT binary patch literal 27774 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%0AZ>N<{!^PFMvP452`-XpRaY@~U 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 ZzkHtIqRlN9vnPP608dvxmvv4FO#trtQ2PJ? literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.5M, 0.99.png b/doc/diagrams/benchmarks-concurrent_map/clang-x64/Parallel workload.xlsx.5M, 0.99.png new file mode 100644 index 0000000000000000000000000000000000000000..38751f2e8b145f4d1cae44122183c18742a818af GIT binary patch literal 25383 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 literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/clang-x86/Parallel workload.xlsx.500k, 0.01.png b/doc/diagrams/benchmarks-concurrent_map/clang-x86/Parallel workload.xlsx.500k, 0.01.png new file mode 100644 index 0000000000000000000000000000000000000000..345ccb8f0ebf5703d4fc023fd98e49ab9af7d71c GIT binary patch literal 26322 zcmeAS@N?(olHy`uVBq!ia0y~yVA{pNz<7{@je&uo?Sn)c0|NtNage(c!@6@aFBupZ zSkfJR9T^xl_H+M9WMyDr;4JWnEM{QfI}E~%$MaXDFfgR0d%8G=RK&gA8@om1>dgP= zMa^RhpZDIWd>mDL_|D&TMGv2T_?@Wo@3WDip_-phkLQII6FYrOvYMUCRuF~dYEcgn5#=`ZXVKwxo;jQB;6yBj7lLunQ& z(JG!7Wu>K0r=6KGQ8#YSj-O{j#Pnirl*)pvwW#&t=nyQHoe)c^a_ z_ZlR?z%%uQpPye^`n<}tvKJQ?&T}pQ`RQqIGhgn>Nvb=KYnpwH`~N20UOU!p^|cch z?t#(riHrA6TnIw8pH3+ITECXBn!ixs?uR=+epI~MxYgZ!YqEaw-(O#E9+$7bQ!_;G$cXXRZF-AqHHG`8(5 zex7!`Up{|bB)`jbc9z3@U`-rtvPN5++w2r}Ijih)eR0v38&jsUGjDg?ZKIj4C6}2F zZg6fZJ8^-pDY2dB?SzH=9eGKMtYsB;*(peXge7k|8oZeRas%T<*Al^4bpamhl#F(s zw@~R{4KI{-b*vMHT3ZH{u5~C8jCHxriD`F--lEqmP|IYY(((eaF5(wUG2NoKsQZOg zW5NwkU}UgEoyzVae$lmLt^>qfTbd!#FPIl~zfjuc2X)t|v%zU#G_=8qcQlc-^We^s zon7y4+=wtsKGu`-@zK$<>2|6*+_^P1dv-1{7l@UI=4hl0uco$3-EYo`XJ=+k){WYt zG21-tff@3rppPrtM_B}3V@3(Pn z+}>S=VmospwLk^*%}uGNy{0d{04|pAow%@k={#l0Thfxhwp@{AzxY;RBe%HTnP=zb z-j-S)E&8{0NBR4EVV5@^?+?z&zP|2k+53C3yL6^*D|vY-=lQw0OHT)7q-5lS(2L7K z=O^;K?d7SBdeztbVw>RIIMwYvSNity?0dHbQmjh;no!jTDllt3?iLE%ZF-`LrE=WSvPye3qSUbb^Jr3+*N-ZmR)f0RPGJ|gh?mZ$3B^s+nGgd(>Z z$eE{TyzI^Qt4_M@FMT?g0j+Yo^k%}sxgth)zWmy2dGhYgeBWYEyTf*xKfW}bI8ndJl(cir1k|Y(K*b!{@&E;yZa8` zp6$I1){KDF$ex>>+sc+{AAZy#AXOY))*Knxc2xR$$>Lij(`20?6);Ljrdt{5_;D=M z;?mL$bKA|cwJs(<{Qv(SlNX;|+RTd0i;n3La|9Me$KLUid{wGB1-qdWB&Ydu6n?q! z@7blIU8xZ=OdFr+Z#SFH!-v*>0K1lLYU0fnfk{{LLQWR!I^Cy;B?TF;;JNL~aj|!m zmMm?!fw#S;atlQ9<8<}M% zF8J*?eq*NSV*V;+#Y4f>+v09lt=CF!u*H^cmd}?An<=nJy3}}1-=<`2iS~>3;UrU* zyB*iOU#^cc3enGtDf8L8=dTWSo9&I(7%RG%SDh`GfIX%^p4;%QMd0p+lpnwTeO>!E zV70Z~4lQiqZz)xpQoj_Rp_uSRzbzJx}#qfko1Cl36#JzSkXvHn&hR z=;Nu0g-#ryF&_>nAFi7Yi_$fHZNV@3<4k?c7JvJbo!o1G?R~`$>E8OQA)GF6#Gge+%07uloMUvj6`YpBe96ER=k7;?lXmZ`F#9`k_4k1M{6M|@e@{)<{=M{7 zNhh3p@8PVE<}S@yyUOnV{_Hu&SK(>ymsW-S9zl2g~z+Tc@Y@=lv~QJgNPN&wl1 zvT(PB;$eHIoif;$S7DZG?vY_rqu`N!weaU&3%@(B*N7d7_5CgM?R54Jm;H_7bQZ_e$|@FywZho#qe_#7&H(d;5`ws*p>N)3U#{`x&xHsKe@IQcK$vcali?R$I z>-fPHP_X*pIv);2l`XyZZhI7|?o&#s_;h3Ao1^dRw{PuhGml6OmtS)9@k`fR!S?(| zU#jeSs9pT^SF7Rty*06Nd18|qRv!PEOp?3X3fonWpcdx0v z@Ubl@dfH>{$$!-6*BboHR)ThGz~#(U9@W-g7r5k;gxG5fB44WPR66%z@?v+Zb+@_Y zQjZ7O^B>jd%KLFECVAcA@V$G#7`3fGwzc=d;t03pJdfqhfB0IfUnCo)@2t2gWkunt zThmf(Q^e-~FP+^Nc>983yFJIPr|NAFXLUe&bP%taI@eA(Dqk?oa4qB4OUE8XUGt7U zv%#mQfB)X~JGYp3CHH+j82dc?@|E+>6MyZzq3e7ySmN-F)pJ_q+`b-ble=PN|2)o! zXYc7rg>QG~o#@){y5jb!D$m2)wG{mL-4dwY9M%eSHWbt+G{dEQF% z6JA#ra->R5ySqh=ahpP7M}MGF;8cip5=K+Dv`WubY2ACpgLiMwuV1zzZ{E9}>Z(YW z_`a@+r}f8I;dss3eOZ%vvgN1L&R{+B+iiW~4mBmDCP&HXfW1y1+ZWnRSSz9$zV>d# z?J~(}C)c%^MQF%nhc0w4sdi6VUHs~Lf35BG+N)yU^SsX3M2D`j5sc>BANQU-yNt?R@3QB^f5kC3s9m`|$^M2Z?(I zcXJX!m5*U_n};U%X7Hc99Er=Iv?C#J&xaL3ffBWr{^^2$r>GBq5x zuYY#+vBa^aRqn>3j>0Sd+ZD_GYIuRQ#m{^C@MmFR;hSe>8gJH(-ew~AooUf)W^mS= zlB%(@Bmdl_nu?x69j|x$+;{Wyu9&ni#q!>|d&iY_3;o(L-6yi-SC#ld4fjR2eDk*Y zyuS1Ew)Dvh9M2&m5YiTvpHf!7@XAR)J8P<}Rmm$Q*=w7>yi(d_rvT0fhR&R^8}Cdz zF0n6m`(GvX#ns2;GxT2iT;)+c`|(@(y{h73&wP>M7b?FwjxG1dGfgx5=e+X9l+{nd z_U>VX4UeeplDcG2{_f73A0LCoUzEMR$_NX%M?9)UN9XzUukWj#8WX7QoNHbuzjr;W zY_&~|v$NExw){Egnikra^hG{Sx4x^Ry7TIci)GJ+K|Zf_C~1!=IeMs-JG=AUEYs{w zPft(ZKC92RZcbA4-m22Y*LPudN!qjyv%foiEG+)l$~iWFm%h0tYOi1GJNMfSe!cT2 zpT9c2KYmx-?lAMikAB;JJG|QK`s!QL*8V7&m9X``UDekqr9t-mS7Xi}{g`@lQ+wXI zy5BdR_I_45r=FMWU#I;0u5$P0-*S64*H{NyzcqT}tn560+OEe6yJ}xtyc*w8{rz38 zsciKhi=*4h-rmaj{_gJ5(?OZu)Ai1ly}J{+OQ+SX?VFj8q}{fsm!&scG26FiQ^{Ye z+~2X~&z5{%Iy+Azc75<`Rp-6_fA-z#-}H6Ww&&aQ&!=wwcm8zI>0o>Qt81P=imr~! zpCjC#_s>(WJE=P1aOzFt+s8J|&EFTXYi`-wk`o*5_wu~nx_{qR>+0IOx87`3pPvz< zyzTzK(>b3-xi`D(|9F0E|H^IY`@WuCHur1)LZR4WFP3q~@4l_*Y<;TW@9FaADMyo? zIMye!>`jZu$IC8JKLI(YB&wa`fMii|spp zDQ-9HST{HE^oQo_<)(GwsxyRqIhU`K{CMTwv~3>`aZBBvtdRX%C~fcjW&H15UiB5< zy4Pdo0d1H?$a8itF`G71RR7Nb8^xCAC3{>?^1eNOdi!^k-M_kw-?3g|&)m+x+b`pU zQ;gT^@cx=?4VK3T9&VR1?pvu=@aJ>?>^|)iYmciahB}tK{W2->h3VUCU)b}0 zCqL3z@AlTO)9bX$=Wa0h>z6%S>i_!h*S34!3YVSC3LRIx^kzb$ZODTC7T#Ca%l-5F zc5h<&?CdXX%b)bEpMU1$+n?q?POht}IArHl_BCexi?l5k?teEfSpO|J_w==G^XK{$ zt+v{+sy+Md)%T(<--EyG4i-6E;2e7A(Am(1YxjN7zS$iffAh!w={|nxmm{KOyN}&_ zRiS$O)S*w+(Z~)cHDTMVVP17^*1{^c$NZsRH@(Pv9;e~;?t9%<)9S3Zzm7@V{qa-# zc-)!3xfVBTLQA)-|9E}Tt?<+B%l7^1HSWG~oxGCMJPY(U ztuAcbIAito71!^jZ~S`pW6Uv9VD+_u-;%{pq#pH*G%!{+)f-rah|Tc718~w`cp-_AXRhxee4l zdU^l(%YU(adp8N0dfqLWw&K#xND<#qeNC>VEH=|l-2eOJ*PFL#>sLIjd8dB(!_Fo| z%OLZ>XOsMIHM^SymAh+7q2%j_e|@aRDJqXcu{gex1?Bp$nRs9#5Xm& z` zqsBRD>bAG5w580QSDH-SS7!g^Q~a!GKmK#h$ldZv6BS2i;iQvSFR=0^X1LGg`C6Kx zv-8!-Pm^MLFX}FiZPVXdX`#M5{?E;}oKFYZ&n~?hs2{Ckw$~@LC*j7&69K79OVaB! z3Pt(XuDtX`>aN66`-W5JUSC!`TxZNPUmMX2KJ0(-Z0gZViL2Ugtrg3<%A>mTqwJ$e zcjD3{FY~5IMN50H%QMM#U(T~PC46t~#t$pl*Yk0-&D|yG<)XS|;Uq_!XS<@goq1J` z2t8g^8t$%p_+^5GT{v{WAJkG>9x!u51rxKWZFsAeq~G)>U3rr4ZhfzFE=az4cjNN9 zN~Nz)jv1-^T+7vzxIDN`>iDXK%)gjjnxy{E*=73m)hp9qm029l>&<<-63-ckwmmFn zh0Ie-FtJ`<-L|SeN=J6t#~r&)@6)<#bY;fXMKK-{uNE0@aXnf4$UonF`nKg>ZLf~@ zriSm`^Xlzd@4zzCH4C{o+U8DJn0MjQlnuWkGiq}Fe137wWR2P#mSy%w_PpN6=iR1> z2%^GW-m}=37q>~3|Nii7b@8OtD{CKLN(_B5WuZ#=BZ1Q``}EdztAv~P9G$dU`qZib zQ|FWK!t>`EoXlH)ygT8FyY1?QpBhh9+Wvg1|Fr1+wUB?>SIVZ!@GqJZ_UgI|BEm0j zE#8&3qUrz9ONreB zT;US3_wimH+knd(BzB3&q`~$5oUimS2d7`fDh%O74p8)1}KEa#?*hsc{UNEwnOT*b<)3 zU9O*dIc4E0brJ9OzyE%o-gH?0-`1UO`BPFeb{c9wFwNUGoyRmfs?cUbTIlNLlge+CKvg~cCn4xFE4o{z=n)AKp3Yl)8F=t3a8szJg6_*kx-HIrhf8*QbbxRMg z3OEz+bXJn$OV=XREl0gL_D=aLv^VXBcWb*+sQPh<(1RDw#w6Zo{P*K!>J3MYr%OL; zeYrG^*VX|vo(3J-Dct1|Wp62KbZ2M&*(sM2_4j@aC|)_&!r3!dM|0BbT^>bOt6wYb zJTz%xO8Lc2xt2HgmfwB%>{6CT+2c<3E5eaFfu>wdij1M0>*Lg;11@i1?p=Dda>?7P zE33c_251(2R5D>po$)EjSI0Ji#@B>n?lvrwzG%ZKS>>@V&!}59JUO-0coysH%81fc z5B*k%n~T|h{d8=TwNKN+WX{8UC7ToPB*$*eT;Zhgcxmod=}Up&@etTB%VM3(=A;ng zKOY`D2Ya=@+&J&Mo1~xJxA~S6FYY+K>qXo82sPE6yA02C9j|<$xck+@E7w0LD&Jir z<`uQHNX~2D*7;eJ0=9-NZT8kX_qS*D7K58jRh6cFVYlCkgL-{y`quf*ig5FFo^{29 zcjrFFU!T7AoBZ?L8vE(5Hof{@xk4`3GwnrZ=w$&$ z&y2gCqPI+Qy@O=DDtH&^wKY7)xvX9|4n?Y~y*F(Q;bWW|Ss(O7QFEXwC z&!=tk&K-?*-yGq*jliN!Qjaza3x^qwnKD|Mr{t}G9KFX$5HyZ6AY z>0h<~P5AQu^9rvhfh~D&7k2L7#35#@^53>c@ZaA2GHU7f3)`(8)|&4xTd`3;{Qr~bYyWy}yu1I? zNw>PcEEio%VjW7}_BgezYL3>CT~@VIZt3Au7v_FAx_sNhUC|O@{<*npe zul?toYd0MI9Up#4bz))L->ii;(`U7bu{81;#cp*s`+EPm<3GLQkByzXtGS#n%ey%4 zx&m&kw(qaq`0@XbW!_QWmnAYQ9$tUvQ_M>%Io&<|ZQ3pK7q=O{NRRn??8X(@_SqNN z7#6)|UX-1F!}v;1+R;miv*Y$}-+4v8GjOB(M|=H$Rp0Jee@)}tT=zG=y>4FnFM*!^ z#p~XzT5fi3lOMOR+KNo;N!J6?!_H>+tU0FRbtB1BJ6mhF*pUUn+?Ot_y=p6O^;d8D z*RE+_8&;LCtB8r;TdMTkco}c}WqHwu&sS#q|EO#)KKQz>fdM+Ova77k)cI}%XiU=b z;9K{X8zekK0=KDzml{pkBcd8^tNwRV%*FSgFH|YdZELWpzV|0=nN+lPCGXSiSDcPA zZFO8QPrK8>e*dqpSqs_qYTm|eJ7=)HpyuwZ`ZS9t&1drO{(1X%RjN;4C%%{(-qzkFzQ{Mm!=gK_F~$?V{61*y@Cui*ed0|S=D}2IPY9L_qK0d zDbJ+n&Ul5Z_n%+5ynOxL_?owGpKZ>Z^vhK6;#cT|jzFyS;Rz4f<@Zis`}6c#%Y&gW z4Zgnn)}8dy;O@@#g^N;4P8IA@4d?k~Gv$p}!Qqc#6Yti|Z=V$1yFSD}?a$_p*-yeA zFAcR_@Fx4~mHPP;O{`O33k!IfmrPvPyL?{kOwiPj<;kl*?)2MR-ikRqebc4H+4}od z7;X-;zLmOSeNFcy?VD02mkZ^*a*}UOb@6JreX;*@Kz&Nan*#Nnp_gx5Dl3w126rwI zZPJzTQ%+ns_KsP4^ZhkHpU(j`OZPAP^!LHnSWp3f?tfnDs#8W&o`}7_F}=i>{c@?? zwTZFoKO3A_`^5Czz8hO&USHL0ZNp;b2J=&1?JL999oZhgqGU#!`jJViH-&|2q^;Qf zJ^AF+2P=;k?3!`z^M)+3X`cgZH{M#-Cfo8|GyB!e(EcjfoxP5rYIAPlj~GaUz4MB` zS4xCDucyW|zRMR_&(>YzFG-G!e7WKKw!K$k7F!p#mG0jp&vLm|F6`yqb=7jmidOeL z&ONSsVy(MvAgHl_OS0{}D5RU^@pq9++gA;3S+jlb+IweT{N)(=a)bE(|94LXA7XQA z)AU;B@z7?<8?o1yk8O(nY7-*L66)MhtY_BS9Tv)Zep3V!sO?yWXzvB>*O-`C8dvpt z;hYDnR?dmoZ&XwMd70PJ!>bnX%)h=#f=~NWEe~G3ap$l9x48cA$L;^ezI*+7ic0vB z61i=Y?>?$rv1`Mu0B7TZqpy$o7OlRXCG$bSuZO7{CzU|to`}0!7701r-&>(C6(`lW%OHQ78^VIzQORepv z!i}qZ-H)yOZ>J0z0E4Z8DS7J?-nRaI{QVV61iarZyZ(M|kkuLO!<#N8PP&)3^zf~P za<{dQ$IVen{QKwCp70;`Cio@)0Gz_r%mO%%$xDntGzO1MIz_k zX?)s?*56Z&b(P%xWO}C8I*+e5Q{HUT=3EvR`}JK`)$I^0UY#IW_v7QjHxJK-KSwIZ zW^}1K-;LPzdRzPxmT>b0FWt7p1z4_rbp4go?A;%pv2S*6?>D*b)qmx@Gw0e(e@^M1 zH~qK%<4(;pKyyYZo5}Fff z$`BfnQGVSX*&TNkh3^Wh`=zAMEe+e2dV1Qi$0tvoeA8omF2nX6WR-=VJ*-(9eP1SD zZ>{@Oo>2DiAZhQsyO$EBmVLNzBz`$h@P#M*A49gL7RVv3l6u?xqOZ3%_4D)d`F2*I z0nD?u>z$ISpUq59Yv-FQH91`5K6E8QMcRr>iPCX@fC!mA=%OJ8j-LUy#Q(?!uq zvAyT#T9@aQ{i)bn*p_{5&BdhZNDqe01xiv}EdsFJ^X;byQ z7tYm_)Qc|6F8dxFf6nt={{PRXa(*&@pSmu7`P9@OzvKT~MSs5fM<>1N&x6?VXV+d| z`SrZm7lY9Mh(^qTLbI#S?w$59x6stH_L&j8jo?d9Z@K}mpe}-}TxhW5M z_6lrUD(syxO~>%+UfJI8>wvjxXo{#O{Uzw2eohhHmtXP+f&pgUtd(>_0k|T z(&Ep<_}SS%1ky7!WtVN-mdLhlM`q5tQZ>DE&ek=lIgs^%E3MC6=*rt4khyWk_6TRC zwDa@szIyl;n)>u5SC!0o_3+rF-Ou#5U(=HfV@>^W@!{c{+U9pBOuo}A9v85)WX3AB zDZY1CYjLmImeBecY2o3!37zXSzKInZM@cD2l)Xw@A$hiI>0y)4k^3X|KjOc+EWbuq zeP^h5duCPK)&0G!;l8CsrOLC!Q8M{&gdNtzN~EKEJjsnI~6g*T>H*8^P{< zIql&)W@%~L?Hixi{l68lxP804Onr6ywy&4Gize-Udi#Ek&3#VZV2#WWTX z+575hZQk*E)9bJFQNnYsYk0PX;Vbv2Z@JcT~ zL+86OVLGzQW?%RfXyr47H}t||gOG(f&Y?a#J|3LDoMWAqj*r}r4Lh$rc!2E7+v*o( z)j%T!Jl2J6TC!r?>t@Y+EuQKudS&x!xqA+V&R6&8@&*5Rblxn*|If>}&!n2$k<(h9 zyi2&V_#qZPsoASJWncMSwgwG93-DN<`f&F$(Km23Q$Sek$wMn zZ{(SzgQ3y8vYtC&DcHKNimL3Lb-}T;WbZP@Z)_L8G9KJeeEjbA6n?w^f9E{O%DP`S zmG8A#$ifq^mS0c*|L;SeX4CJz@wvGt^0`n8;>9nHO%>D7Piy9Pw_cgPf{Ux%{_j#q zi*SWeh@xBY_j&csaRsJ6j)%pAnvi|9Zu5(*uq~|IVkT{xFWR;g2unRZn)BgdhWb;U z?fWd}3CCPLb+`C~|JkMcu1Ky@FcEn;ZR+|bEaATAEB)@iW<{=PV$&~*N-bO9#l1Q0 zY*uHk&R$5uyy7lwD{}ntx{8PFwc4^_DA|K= zQT5xc*VQal7HWYiOt;by$AuB{;fKY4O-$Uqkn8t+9sST{4>`K0<(}qTckEGK-CKd@ z3z-q?rWr4m3f!$I>Il)+E@1*i$%8z|bkLwtLL{B`5Q zn-+3e=kK+&uFQFLq1oT>pY87Auj}SKSk=3Je$Ki=u5-vsxfw5(irkf$Dm>db-Osz~ zFpH2TIG=8D5B?^WZ*}=cOz-91k0&;~3uvu@YpN%URSUG{2DRzO;PJ&bM#U=CqfcxX=!& ztiatFYle%x7c1^vNf8GH$&6E=^6<)$Ue)hQx65Ve&FxY>Y~?JfvPtMxM1kraq5FOX za<>$n<-toGCxE7BUsJ!lT4ur<@3B>h8Dzjm%~bQbvi~;x4%u01 zyv91j(R}*Rrq$nOFTa;@rzeaREhyhEesS#bKQUGdRZvmT6Kp=$cH`USh`!lk<5HtF z#vzVR4~D4eh{a45Ty1?=5GM=%YCAoB6bRR~$-eE$*^9o6lW)qoUHU zX#UN@=$F3fZ&yr8yt|O={M#SvHMbU;D!Q!bjb=x4PpMd};3}OX=5vo;nl06_j(_>) zge!Yru93Y9cEw@AY3pvy6pk_VeS2FQElzH;Uz9Zyh?VyXKX}16edB{y%QoAx|GyFw z)$b>HO>&h%<%7eTTMPGZ{L*^q){fE#HlQW`OF*kw6=9PCk4i*(p2tg{_I&q#{{A(W zeAC}*oCYN$ck{owUb%Oo!}eX;W|k*w!4F#Hzw)_9TiJ;V>@M5+7e&wEiqO59$jx-| z>%$Tup1URAz%AVJtJku%@0VQbOKK}^)AC5E-&0fVS72&Zes});%yo&N;uNvC)uCjr ztGP#>jh(p<`x%M5_Z}X;*caaB-v8DdWP$}KL6yC+G+Ju5Zk0~RN~{6r`8G0ru4!r! z$11%W4-Q}Kb8q|2mp{Mg^389yt7mz?)i|vDUT&3X$isW3yNjDvFMGb~+Of6F7;%{w z6Q|?0sQR4cbAv>e{tpi`j?a47x3~W}%k`qmk=Av^pC{#f-u_QjedpUYtrgq1MM#J5 z-Bp_X_*&!V3w)^F2^Xo(KYXFpK(`=EVV7Ocs)P)WrtiM#sWNkJOt7uK_h-(NZPKe2 z6mCCyYlqbQ+upf%tT59z+sfX27wzni68B!RUHqCT!n*jbZ+g`|?$FR1wq@mh1-E%a zFYu-xy|rW2h6JpkayOCX;@8A0EY7yuUhV8vEkC@veCM4Ku^&%fOw0i-b5aR+7C+Ud zwSp~gquDySXe=H9ZNumc6yoXKt9!3xnc=Q^GIu`~fhJ*#CjFi-UT<6de(uVr2d*SX zujySqFSZ{uaox4+$cvP{d$e2JeEKr>MXwL9;Bfo6%$Rfici;5ia#^PT$NOhx^N68kjJM4%^kQ~Qu-bL*!^4c_(QQ(b7A)l4_@wH7$ujBFCC!mK zJI>d?{M>slRxo|VE6&udh5T~17bEOsF@rS7eY-u#jNrCe7k$iU9-Xv$_63`KEA81& zeI6J4%h=qAc^uN#&L^K4EnU9rA(niS6v4Xq%`)Su!k_oN^jUiNO0l%}JJ3x1iX8%* zpKQCIa(}0C{o$EarKX=FK@~G{$+vFv3$8Z3ABWo)&)vjh`YlY@{>Y@)wU;9!UmEzn zk9OHwXkUHz)|@9vOIlOR_etiyyR*~tTo@!CwcpREYe(hSY^}pH8ra0J63J@@M771)9W7S#m(EvFZhHz*6|-! zl8~D8;Y{D{qDiY4a=Z7x&0m-Ky!m>$ucqv`Q@-XtGoP+mWA;vUe~sd7n-7V_y<4!A zA4w7{k590(|Ejw@)2ltR!bxYRmTcG(kUsnB9fihGtI*?OGN{A&v`uTu)+x*W_Bxi- zdi-7V<;rI5&A+eJ8>{b}XFm7ku}zyUCBDzCUb*v_rq$JBuAt#0q;9qQ#krQn&!%K? zA7)te+WD0D(!1F;GKx9+@hZue~SP%YW8rDl4dsRg7) zid9#hPfTfIP0gO4YxpO{_)l~zsrC4{XbC8F{(IQky3TyA@4f9h>N})n@BV+Xrqbx^ zwN=;6#A3d_%6hh02{bT))X%@Iev!9DYFFIeJ#W>PY-o7#Yr-X0&&PS!|2(}m=gGb8 z->Pr2%kRA&bGU7ldWfd%vcm0B_0rz!jy=zslP8I8RQko8!Zqqz+!S$$k*B*9%>z}^8_pjpixJYls!+xNCD(XIr^c61V`yAKA zn=ke(c|7l-!qw80sCvbBcXw~UTJq-OW0Bjpwq|d>ySse4p7&DS*j*;KcNV9=+~l}r zdi?wob-Ujkn)Af&|8(}~)4G!XzOKD{)7$*+1mQc+_Z8HAyEWPW$+hTzS5Dt-nr}0C z@*Qw@xvJPdZ*N)dF5@@XEchWiI4mcDHtYF&-z{u&D`5|f)8UEbcz?P3h1kalf9eij zpF213=Z5NQu@{75{?>y=J*LlFbe$(uX-)J?1K-EhkDK+MvxGZ~Ui!oW*w z()QQ?+cf7%UhU>v7pCS(%0+D7W@w{0ErvIqBg4Y!rpdm?uhnN~-vEzFBNZc{HPMUi zi!W_{zki}`wOPD!`SX3IXP4g1oDLsES}lFy-rYSbce-8Mv2jg~)P33NK9lPoAj>3C z_eXTr#Z5VNs{O6?p1nJ_NTq`2W45hccfvDoW62D!mZ`pvS5Hd12VR7T*fI&;F>rBx zchJ>OiFY;9DoT!@mi2DWTyf}9sC9AE>RUVd^L|AqSK5PTq0t>KFTOPCr@`HpYwKzv z%pdW5Ul%h`_)gNx4JVo9)9&x94fZSg8gc%+FnIVJZ7-&czSrI<{IA=L{=E3`@Xgdi zA^W8D_b7y0`9N9=;kLW~N~{WC0gsfU`jT@!IHq~m-@CfCCc^yM`XA2Qp5DHH#xpO* z%13E6WLh=vZf1=xHm^P`4BRw@|MiiBoxC@Hu|9vMQvPXQ>e;2smwgNc&5nkqE}V`- zXX0LssZ;r@YybaxcInERG|bFrAMpvlw6Xz1dBLUiQS z9EWbP z%Ra@{U*+GFEI;Sg{p4VRud9_o=9%m(SeEjhE_2$#>?#zL%00_dG=2nR>t((HH zE!{8e|8%ub;`7+~?~m^NcyKyQDZJ*^`y*S6_-MQ%eJfEl0 zVbbA~9c?}?-#&Z2A2+rnvkP3{grpvfvRiYvB4*($&ZD<>-2c_z%e)>(&a{f1@+;78 zO^(gG>f3QySN89P^lks%zG2)u`(h9qmekc*22Rtwu^XoFYp?CEeyQ5~YsapQZ*FhT zKgGGOs`TpaZs;x=*am0lK9W8Mi%vxU7nCSm zY67*i<-Z--HhcV)11TDS2W&ni$` zv*zh?SaAs3um@cp7dz!&S;F-xpAv6>pR=mz0{7cvkLBuKuV0#a*y?NnY&Q^^Ki)~~ z*8F|fI6j+m-PW3j-5+=8@B6b|Gxcx@bJTW89KLBd)qeNym+4WHR)0zaw_3K?r9Lh! z*N>m~_t{GGB2bqnBxsE^EMK5>{!ACe-B#Ff^%g>+P;)OLOOGNQa5SHb}uc z@1UYe>cWrP5k=v9J~Y31?B8cL{o3NY6?g2{-MS=EFYWzKS35p)D(`A8*nTK@R}r-9 zx;o|c?8lZT|6W~{pZ0!V`GTEWsPQFrxUf6z)S3Q&U$1X@O)8-ncVlC-TLSD{hsVzG-asJ>U~>nIwE?SL)$aP1q9ST|SSZ$C|a)XUnd=T@)hI|NG7V^18C8qUFz3 zwol)b7z!)w5d%?5yE^iuOHalZC<3hV;WAK zob31OmDP8KF8iXp8x;J1?_L*scI9$bV);z=*5vY`5y^_89-tCB@)jlty_keWl zeL35F3(NJ@xZ+rP;m7S8pJdgnJN7b6ai50h@yB+16CAe{JX{|=>2)Nou*+TK#pPIV zH6(N4swZ5rQ|?tipR4(}Q2yVa@S?|sdvUt%e|Od+G1c&`#jg#%U7o$EH-5i?`p)F| zpK;kXdHYXc_tU&@i*~>KT3tM8_099Iyw)9iYzOYhAA4N*8;A40ZxOo-TEI2=j%oeQ zt%g_gL;wA|Ki@~P|M!*3`F+P_U2bkzg{9!QdpW2?bu*~T9)2mh-M>%nevr`%JoB^NVW^lU7TgO*(nCH>32%_ZO??JXqEGamVyO zpIRHIW6S5|9IoG=oSb}fPvz%L)$jLi|ERwxns+K!^u~{$<~&)ImltX>_5Hc%{BxY^ zjym}s0hQo6f+D(wFc)%#t>-PG_s}tIbq7Q-gnAiXN zGwp-YuDh01IrYh(+7))p5=r{r*n4*AUCV#p_T{`@I{k@C`5$lb^!T_h$J?IR?RuHd z(Z&qgKmB&g=>Yiz28O#Uiq|@naQmPh#(}Z>+e%tqUO)EQ+PmK$%)NQnzxL{99JTl4EbzPCIW`}=0`X8E{pr``159o=7{ zcl+0;<-fN;cbMtbJVmq~-$#GS&L=@{>z^mzAITJI!ZeZ*QRr^uHZ+|U0H`u278ap_5* zev1E0h9+poWXs_TtPC3ho<}vlC~Gj>_4cA~nq$FL6T5Kp#i0+OBM7^>^>TfmTJNjd zv@=Z(T5ei*=rKe%l+1M~Iel~E_m6X)teSJ<_DOLkcoaRwcQU7dzri&oFtvGK{eTUEJ>%ks=krshY!-0(iG(BHXef9>yOIrn$Z z-43n4GuSV(GH`d~CG~W@$l|&mQDk~_{jy(g-6CIZ5RKolBGhV4j!m8|fY+)v(mW$v`qVEvGu>zJJU>|mD^i|T?`3~7kzsYMPqyS zUX{2LQDo}2U3%83^56U8CQrVzqvVG9w0#qNK~q;{r5Kt-VnYK1Oj{$0LUR{Nr!GDG z?*DV|sHKO+bZ>`cBKzlVmWaBc)}>s_AD8>j{ItBr4zf`=_Uhr#*gFwLq2}AAf6uSk zllyYRuE&L^q5F-uG{2Z;Y-Tp??0ozEKRGVSvK&3LyYa;`#znIw4y){%byH_r%-Nc4 z&mYX6l|6OwrNrHNzgX=UH~Vd*E2oF6?4%*ydXDegE$&$fo<% z|FdV^_nTu;VE`GO$&0aCbN%CvRA@nX%dzA$%f-&+bFaI&KKxnzZs&4S+rQ9VVo&0( z=k1KxQIMF&_4^TM`2uLaIF#V2cJy3&8@_q^?IKivAUS4~YV>i5!@ zKh9gdUi0SJ?EJjF@9*ucUT(9z_GPJj_xj+~MwU~a?r1$?WfOmAy5H?LEphu34mRZ+ zZsT2hUAz77@9*3DHl91bY{y!w-$oh&caKQ4CD;GC`CDr5_Ki59*F33t+* z$=S60`15(ydC8GC%J$gby%&9(?{)aiGWCmBvmZa!sP;SZUUN%-qV?Txfg;B5-KIX~ zJFfZY@5GpSmCt7G{3{;%ZL{#huSe(m>TLK}S90t4rrL|2lHOhBT{K%_rTlN$g0N{% zvR)jznEZRT_vxF){~Y`ZuDdQheC1utUTx4&&+P2q4UiT_hPvv-xqK&ieGl(S>bfMl zL|M$qv#ZKXRq^h$a_6F(ehH_Rthrr%eABJW+jrwuf1h|otbg^7Ip03^7@s>Kn?1Ky zUw>-@a6N}_w=XUURCVJ%jIHj7-Uy=H${xa++$sy zTekb|ONsaQy!v;o^>?TJ-n%ik#bGH(v-6#^aQ$4R1k>QGk(>MRPwJ1k)KKE~ddy74`*NPn7v+H8+ zY}d{!h0{3e_srG$eY11Z_18OAb>;2pPmG>>Zl#DriLmn4>!-J+%st?Rd{%I;#0+u8Cw+41GJm=d8_!Tsqv z=1M1)PFs0&{c5H3(Cuq$<8~IMzTO(@8s74kSqi?x2^0syf3Em>C~Gy z)92q6I`d&ql)y!;+izTMzP9dMm$AJ+F}jeoevi#=_)t&tL`>fP=?h@2h`&Xbd zjj#IIo|=~%-v7r_(Bd~xovUwL=ar>TuS$90*ZW2bzJxEaI* z?jD)3F8TTr{#D)5EaArUSLWqOz7GGRc>7E5`ll-8Kd0}%swKPZ+Y8X3{X98X8idst zFD=S^@2}3RNj`bEyrQ}|a~|{SYiX~==ggjbXMTK5u6Kc{T>0^N%4fGO=Z9q^NNp>e zeBt=Na}&dhC$0YfdZ*H?r)w-i65l?zo31Tu7K>8;Gc~Zfm>aTHzInAZ^nI?*&R2JJ zKi%6leG{bZ=)V8U)v%WaTVKkc1TCa)UXgtL2dFP8HGlQ&i%BMoN1oK4E^1o+P5yqw z6)o9iW_Ph!RQSgFo83InV6?W^yN}zyr$+1VRZ-vJU;nWCX8HOZ%T;$s_3!?}bfJSV7t}=mk zrnWS{fP~GjLP(;Wce60suLv|kf9x~k-GZ<=$I6eg{-3n_A-D|D-&-khu%H2SFdC@w z;wI~2Zn&pE7Sc|*x>@=HZ#ty_`VRM*_9 z%JnPSzVGMDIalQO?8`pIn|?I2##uj)2gzp;Tl)Tj`sR5i-Kyc{i&#qzg9dNQV@g8v zq~fn05w*h3aD+>5XgSfm*@4y?6l3l&0{VJftm)t4;(wTiTU+4znu?r{&0oBsR0UOt7z zu`j;fP~9SQTVbbA+}C%n+Mt~p`?`nPJEhO9ld0aAjU`N~EX#O(J5KP^x0eY|M(rqAnZ_bBcF zZP&QH@wvz6tN#=rC->ZPEP4GARCt{gkDs#p8{eXrwx*j?ds=+s_aA>d|KH2`QcNQD z+bcdkI`%77Ns>GMPlVD|XdCe69Lr*<+cPbT)4;1`cO?}S8O^q>E_+?uR5I71P|5oB zn$4SL=kMG3vF1+ot#_u2a{eEBtWfr_bnmwlUO|V=t{lpKbbb5nE^dU-j@&}RBEtj)hZvJaMel+!N-0e<3+g*3R1-{t1 z>V4?6coQz`i1WorKnGPJ)@$2%RHCuH;f~fRPY@A%iX{Ao@Dx*iPiTX?Ym;R&B|H) zl*?wF+htRpMtu9%_~`e`ja7l&k=O3#`P|IzKE5QkROo7j{p)$!ml+pjyQq7-IMx4O z>DAPNH?#JIU;e>%@bB5l^Uuru{-=2o+-sg+^Ljb++oQdR5}i-|uFLxPMbg|kA2;j2 zdz-7i>usEJ*Kct-_M6@D%ddIoFMFZ3YwoweDXF*nw483e{rl)Kzq9Hx=aSoR)29}v z?s6>QeZgY$d`@wZdd|luE4uq??>~HNc~Z9e&F7nsr=QLDcz1aIf3MeVoAv+YIDfm- z_djc2Q2`?E+9xmi6FSk5{~~WutjNM0?d#`0DXHpxp|a~}+*PHxMYnV1T_d->eWA7_ z^q#)JbE}MtZ@0|9dv20P zzn33RjJLY`BeW)0eT)3tO)t0gFVeYPRw8^!>%Dl#_N%$O=W0~v#$N3GZk)6J?#@X} z9qW{jPF~0bUp(ztaJBCHhv%D@@Bj5*D)sQb>Z7*22V)EV&U({kEq`ur+6BfZQC zn^IS9QT3jda(bGscI-4KWzb^iYUyV@{^=J*C-weV{Uk!?%FCcXyR1*dIP^chex$#7 z&yn)4;`X;6ec%7-l=kM$=k0cH+%xw@SJ`^b)1Vk@|I4{PRQp}^pId%YI1~T;^82lE zYE!PesN&l%by4Oz!P?I*X6}BgQJrgkQS|M%&8K!6-Gz_Gupi>zeY-Q>#a0w_uEo}f zqU$w3|EKNW{ov9y-gJAJ+jHLDFr67)Z*>dnOtOd+=tRI%M&vx10USWzQ0Z#UajAKsB*&fhgRfS@1mmyU3n5lg6Tr$=WCcQf1Gz$;)|W)uCJgO zW%~sElcJMui&PgTBegRPUMcKi>ta6YyxK}*Urz|8WTzp|@xFu`iq6(ZtpLzk&BKsW zWT6dC&`~n*f&kL+bpS7T22E}v)Up#7_#|&RmMm|2!6&}AEAPcgUWHwI8(*lgfmTvN&hwRZDc5o=dB5}d zyqt4$EPK~)Pd_(jX70^ROLw~cTb;i(>3zcXr<0a$@VQ<0uvNV1^=i;z${W|Q=uF%6 zDA~}`a^~52w!6RHQg}J{+u~D&Ww-PG9Cy&^SNEH9;v9|<8Sa^-O6THqJ+gInr5?Zg z`uEFHZZVw`W;r)5Sh2}I{`dEH^zGHNc5B>@`}nn)<)SZgO400mx9{-n&7eI}W#4D% z^K5x(8-IWQzMbOpPlf96Pg=Za#?if1rPI8V&e?oEGwsaLjJ-+uiRKrlhImypmAw48 z^ZC5lwI2?$PyJ?WcP`@Ft=#bU(s8#vOTOOv5Vz=!rQrVM7wp>JmfUs^oEB+r_4UOu z&^Qcd|LZ;R7HvuIHXbjL z?L<2x9;wK`>YWlXr8Dd9(^qS|x67~44qTM8XXT4+@~Ouqxf6E3?aEt!^|wb>x1`wo z%Xae$k4fq#3YUHh{4y(Y4{xBz3kSpFyWWSF@f^)9IkoG@zq{woJ2s<4+}tTH<-g~J zE)}xs0Bub@yEj8$$nnytB`S5k+n*|(SXcG+(fa*+m-v?4?%(vdXZzK>-HUE#zwXHU zBU+oWaQmj)zdPzW)@_q~??30Ug3I>Hzo+M}*SYP8nmFc8aWNOY)#)-Zy6!w~x=rd#_w|cFo)uV*R^!^#2vRoA>_y+dncU7%5T6 zWswD&QAc2eT({TUr-rVXn$iADV)o%RGAx^3N!)!@w{7x?vfI7im2;wvmd(5QD7)Jz ztVZvR^PSbQmviM^cjs6K-f7EF-&JK-7xVq^sXoWs9rb!2tIMkYZ`fP*{P_aM5@D1w z$y&6tu;}b&9wG4+{XIl8HqlXA%8@J1LW6*3h6QY;l z*7+!)H)*ximW6!Hb^e(0;La^%CkU*B29JTG;lDCn12*rx(wYx)^dh{Hf;8@b;XFRPJ)lHgU48S- z&FSgJX=gIFeEj(F*`#~OwGX8FdcnNNI%QGyqa&T50`BIcqutW|>S}76W*VobfsPc$ zb^frk+Ag8n@I#92DnF%|<=?XjUayB#c0xTOF96zjw{hdfoOgG2-u(FZxO9K>i#EA? zS~~Ij_7q-Nc(>%T@6O{UkQ4B?{M#O#w{vM``d{bw##G&bs(^srz=&3VLy0kSNdF4gKKK{nvzFc1pWg{jMAC z`eOt-krXr)R{P*NXfxJ^{xX5PObcH*3EW=vJ`em)J)l5-a`QK>bLU7S# z2b-o2I$!wm($cg4!KY{mUcb0?nwYQ^XOO$?^_81*Zf;sq|L`QJHbGkM8YA2uP{Ig0 z{eD%$3nm5zh65f=RHBQby5HTnfpp~b0zO6it+3e8Yt+iF+4l8y#hM|h zNl8l66Eo&a{@PJI2il)|CiKE(`*}gT^j()VmF?13UDR{7OZN-DXj}Nm#Vq~YoSch` zTziEZxC3gZJOtGZvi5a*K2|(D)anuCdx$jywB(bg;^5B+e$PCe?NvP?CChF<=a_GN zcjY9-`Nnf!PE(w3yu7kM&w_CW!_ABFlZ&69%Z;6|z&fVk*Wo8mQp%1=re9EwY<~29 zy`9Z@tJgY4YaAf12{u)>OTTp)#o|q50;nb`bmDY4DMa^{N~Famd!7U7>gJ^H#aZ2`Ea$byoLDc<`=gdFTDvl z`=R8|UAK~*3_B0(@UTwFV7C0+Jp1CR<`=hC&Jw(S;@gD8YLE+U7sgLpU;8%utoxs( zce(BVJmfDHm#!`K$xnG-!1qhwU&4c35xg$f{dV{Kn6uIkD$k_7QOLGQ2Sa|9;QHBM29eNBs zGTh>NIUV;N9qpcdeoj(Ojt(dVex9hmvSZznLijMq6V0@S7fc5q1Al}qaU z{O|8?>GhW%!-}gtW;yTYRlmzzE3Yq}D67kTn_H>w(UHzKYc`)dmACKbGtJXUR=Li{ z#pi`<3*V~PS+!^P+w$FiUu}_xjcsIC&MDsG^L}aW@kP?|?uO0`4OR-f7z~O$N>mGl zRvDkOSgh(*>KcCf^bx&1Znw49$#>73cARO&U#+h4ioGpCiy}pi&G;a9H228*ms{P< z@4c_Ezx_6Rd$J{H{NcBNKKFa^wy7;|+n@fLIk)oJ%v@szh84_F5lIAlj+YP4KtQD8@K5(TwL-cOa z`n1C8w^bUeJz{UT?cx7e-J!KeT3_G#T;8JFZzH-Et)6rE@rJ!0ZY8}lO#RKq5Wwyt z&fwO!=tY-8$VL^mmt65{*T?MKq#Bbt>8fu@=+cLc>~a~kZ{<$UzN_<`kdH+!OX*Hg37t zb6$o3aTjq=HgXf9GCis7#lPSC|5rb@ht_B&w_a?K?*I4y_S9->rPNbKTZ?taq=* z#(teWYgP8Vx>t3}czJnMx8JX`?sZ#yF~dZv*UaPX`O9DA85kHC8XTSax?eCcFfbfA z&KP`jh+r-rg681t~qTbdv3^$B!TTx|-`mZ_A1FmITE_!%E-# zo$K06&&;*n{xln;j^USA$?;>yCT%`%XKh*aCFA&_-8DZy-STTMdp@^(nOykBmEV7f z@BiWY&M0c5TiZ7rS|O0Jb7}m`s?*N?2BGAHzl_7yq&O6mgO*?x7{$EQ3ndI+O^;x?JbP^gxH+qSq`?cOiTn>mq)!lnLstQD++sbZCd3`*4>( z-hbNq*a>_7Je&MAeHAA(&iJ8eX~|7RXY0_jA8s6fufo56w?$>a+3>8~-_gonRj)Eb zBNCRtF1?wsaIRKbMb)2_Pq)SQ-=1*iLYZttV7=>BXxf8$cY=v^O2+aoNxR*j`#2{_ z-`aOr`t|x%e9**Yr?3m0dM2f{^SlkwJuI7VKU3)T)YtMJ^X}|X_T{U6)!aTR` z`=7sDlg{3I2#5Na3-dI(tJAl?i4KeZWO>$Tioo$rs}1)~R^5%w13qDG^XJ*kv)T4? zRojYnH|mPlt=M+TAi68(@4MZvy{ovfI%(puw2zaIQaM-d`rUO59@F-92#+ z^Uw>E%(4>~&ZVTxm>PUH;aZQ96*NVCth42@a z-Fgt`C)$HnK!MEk(>J!MdsOi1*=E!F$e{R3a~4T@m4((SPK!OeNO|PB*}1K(YU;wO{N{$NGUKoB z#1`hXO*RZQz!n&LPZ&hAtvNHH$n)}m z6;l7K_|T>cZf|^?aV4nQ80@uHQbNv(~q){B(vh?vi!#v7RR{E}pA47l@UIB(>R+ ztHe#;MTWk;vnj%8O3Lv~t7l)`rHGUcM1}7j`0=A+-HWt|KjxKQi#)k4_x7vvQ`q4S zo4PR1q&{LMnn^mP4vleg7>c68-lT@|`@RH zMqTZ9HQHis^d&R2T*hU4>ydNw?d@mUR+lC3$qW1<{^(#cyR`3bIs0`<(YwoXH`V|D zcPBM8(|fv}@3%KMC+}Xv)ywlXS1awut9=(!GTvU7cKUhew0CiQ_?F7F9aX=dtDd`Z z?Y+#Y+Sr?6UmpjoeRW((@|LvZuPv*r`4-)FDY2e<$7a>G_mw_R*Z1sIaJCNCJ?!r< z?|*f}Rf*Ww6%p@wHV3o5x7@O9-;&}Vr*1&1`Bm>MI`Z5~4u6iDqz#LyQZjxP*UpvDx78ec}y=y z>g-%q^uTqy(U&<|5|9#ngYBw+i|h|w{o>n~mdoRN-(%(7-(Q~g{tw@%`}^|uSJjto z1!7&Uvx4*0#SIgvM?h0HJzc1nSHKpx-pwyz+BGXD$cPEkGT8wOyX*czLkA?6u#favFNJG{-}GpB@=tT zd+)V#1xMaoKVzOCiTiVwCdUiid~3Kas$L+>v-n5N)(x>)b9?5#7lAhOKoLE6ljN(W z&o`E>mJ-*BU9iWZ^ohr&pOdE>y6yW-FHJbL<55RB_or10eI28&a<9p)tUDY$)m~i*shXa0 z(!lzrle&4Hsq-hT&R*5{ZyVsu|v0X29f8AUCs9Edf&vhcV zM^cTh@Txvsw7jrrS4Xu$-6I#@xaJdC9fv;NU4Ly_E6?V~W$}AW>vt9FKazfX#bnJw z$&Ph%SEXq*2KTS{_v2!3i1zmTL6i6Xcy_cd-*L*;e#eC7hU$9>TE8C7>3}qI!O1n) znq>*Z6m7pNw4@GCH}u+{!~Y~j&*aj zQdZ=ve-W+z(PjVd=J)9C{2Q0#S4;~3D#hOT_UVl4GwLi*!c633fbI4|{;-LKt6e8N zS(R=!?fUm?YuD=AI-4DC_P3o_Rcn5_Pjs%9#Ko<~l9I1>g!%8?*Y*C|jH@3?>vyZ| zT$ zUV#j(3jhwA%X)_+xUA+XnfI?*BPVfEziHm|g;h=Ee_t#+R(-fP`>s-SrjzB7WN|?r z>wA)3ZeK(99*dg(axL%P)sr5*>75p=dpPd`|8o}?NY4bE>cS*nHOYHB+`Im5uEDn# z1zts8cSxQ!y26`NIF;@1|1Tf;Uwi)z)L+M9o}yUEb*Vq$#>SKt^P95{teRImvy=PL zy<1A=VSGhxdl#Vv!C9k_{xBU@Dft(TRe8;GR;^6mF1pqz#B)>JG}&x3P$j?Ir>!tQ z_s->et6FDoK4fv-DAxhu8-JAAq_0QS2uk7-YRm~!6Ki=xAJG;T_%VA4F zNH+^yZ>!H$~TEsY|`so95_tH6(YT$NTJwJk^n9J|6RuR#rXWzwc-F zqgEzU1>99g%;j>oTs$1D>eJ8cKMZKon_ve)`WbGm~vy)_HX9rYqY1PuU@98 zyoz`AytwzAyitK)@29(5M``9;INzNj%-wqAtXEk^qe{5>B+;clZPn9s@@o9b9(Cpk zoEH%{|LUf7I?gM?dR39mD}F}V@Lim1Jo?whEw=gEel%tJ*HypX8!vk8EGpw}e(lwr zmCJ5D{N?=b?Cr{rpVte2U3#~maKXkIsk7VaJUaGjg_WF2k?R(!>#U#nYwP5z z(a|ld{@W?W=1xRi)CPPAQ~eHf3R2aM$~|=@TNV&utPFG>b1P%shGb zT*jzQhaz;?38|Yi`lfZ zerhEXp45HZ#(VPr?$p?|vEL7Tdvf9T(PR5piJMQ$`TD7B^P7ko87=FtZeAIWSts+L~@)_&_*%}jVdUiua4>NbDnw}01kx4y0T zx6%D|^mU{vZs-5<`QLYVo>_Bgsod4K*JG|)B))bE2rK$7_p92g$a(h*@!6Ba%l@zZ zx9Zx9CV}{tU6EyLt`@FPqr8txIky9)9$q z|D(bNN0sIS>-PM}o~*RN_59>JJ=d6FmA7)Vn)jt7$4RS&D=k*OP73_iwqfIUyLFto z&Uy9w@|wQf+n$xba3RmlNoi}-5;g~XvEWHsF?;o+JD+D|9ZRdW)vb{2PHk6N@vcA+ zl#42!?PKs<{KT+hNnX&@m}8H>@15O#K#cX`TgHxc{Gc-Hjm$|e9ko}!TNW&2T-7T* z|4->6{lgOKGfK{W63%5NvD7GXTQJZ-Kfj8 z=r+u&5ptd0-&Q|UkNeg5^4Vsale*edCS8k2Q{DOMF0bS~3)6XbHr0fCm3^JHHh;_h zmEPhjH^jb5z2G-8;?Y z6@PH&(kPpA_jhj)bt_x`TF;_70a zJH=6GwcIU*4N|K%{5~+#i}NPWe3@Ex)!kYAw)b~wId4DRS1I5Aux*>t-W5_#3+`3+ zvgWz&PTm;pEY#KXLTvHXvw^Gie!6`+)ypllDB|e0kF)aC7mL?wmhStI^UL=7^Nbx% z0uj9PgW_E4pRWcrf%&1W(8NL&wVPTDueNSZThX0Uv+!c;QXZizklIz>`R*Dq{=$H0 zgD+mE7xDS9wbJ(H7TA5`hHb~rjnWbvB(EZ$`g?eTmSv|!U8hI3LJ}h|c@6WrxE*YDg z4%mBuyS;qbz10hOHman*47<58POe>Q-GW4J*VEHqrt3R~_Pv*_-u>KJsLN=@qS@u; z`|JD#ZW!NR(;}nt^_1Hr`A?Jn__HCJCWo)BxbAmI$h)VC^|9n~tFs?2oDyBxJ#C+s z&gr-R|7LlG%&+~q{;TTz%X)_=_9m|^U7;JjegD1^rC(CU)4Kh(S)WdkZGYHi#;baQ z&r|dEE>o3-8h_Pxy=5``clSe%*4^b_vwdoQ#N1u5^linHbbH@_QS#|gSGRrK=swxM z&-8!t7A~ZrliA8M&(>YleR=j;##@!cIXCrUOHP05(Tm-vuDV?4noU>AijR9Loh9x5 zsMOCcdF;LC?<7|4P)okUzA3JG8NsiQPH0&a^07SfK0}L9=vHZ|^*_sA?f96pmgU>x z+UnQQS7xo6&+lp<6OPn}{<$Jt&-z_ZeB$#!Gv}`z$HX&Y=bZLhmnYY~bW13I&P5*8 zt*LEBZ{({Cw#rZH@>}_+P~zBwV^2@3C3`ppsu??fJ@;zIw3t=x_cz~L)qX&1>8t0? zjW2FFmQ*t=dd&=~1=KHP+2qNk8l9A3p2fuF~A#`p^P-=;?>0>#@hr0!^Gn-TU*Oyjqggc6xwn`}zrA9?Rjr^foI!)j7L1YKG{&c(P1i_HkAI zgd4s4WoH^mmUY+dSceq$=L1S?^5k5N&V*@CV3;E66dK%dH2A7mm(SzdnQfDzdN23d z2S2WGuD#db4tBA+dh;cbWgX zD5AaeaHi9L*W-_$UDR{t%++ZU>fAJ0cSU>X*TUw4Re5=WCq=LEnEo@L7Q6LLKwD1! z)+Ni&>_}!g?6;dob^fFa2F25!nEct(W%l*etG$zwPHwyw`K*6d-T4h%Uk;bD!doW6 zw$67G<+<18nQ2AKtqN_M)Uf8sw7K_VZC6B|4CTGEs3K)WX47V8$t#Ox^4-6w?KU!P z6y&j1bneX$4dv$*pD^Xv@%Wj`zQ0PBc(tkOO3K^mJga@+DJE%^_bm2cQ|GJxS9`)X zK3T<=rJlNIR;tFy-s9_6nfn+;v~B!u=g0Z%sds#qWp$)noaC$G&uvB-)3!QnGuxoD zl7qu7{e{Hk8{xf+YxgxPvS+QDAh4|Z>c^WWC0;KI`8y|K>*IiWk5~M$ywR7oYy5ir z>DAKLljZ+i*WQ{RWiogBH2JFie+5>@H9;Cn9qZ<E*uT}K1^*S5(|(S5j7i+$B3 z#f71{w>RC_sR;O5{3u1H{ny+{YxZsUoo_mG@9UM<)u#N^zV#)y|K94aYwmoq-}=>T zb?aSUXa>5t)%dE>8R`DrsrNVix^(VDn#Re{lS?9uzJwM@s%&|+msi|?bA460^+M_6 ziMto_eEhdseRu3m4VUefuM)4`i1N&uD1Fo<#PKkn)6(XXT34sKUp?P>*M{%w+4j18 zm4#ZI8j5HC@s)0Vd0XOd->c78opm_%j9FIkK+CQdw`O`tKI=VxwS^&UljN)H2N5EcGR{v5{rIYp7OI@C)Kwg?#7RZ zQ`uF$RTtay_qOu%M)W)XHPmXH;`3@Y|H+)PV+W0VmDZGc^lbIJJLT66c{7#uZ)(?n z)9q^0(4Ua0Q-I>3LEowM!fTkVU?cRy=HM9YMQ&nxcOJmM++?5CsnDpb2!#eAB( zXWr(SC%Z14eH=Ao^fqce$mhN*Klb* z=OgJO%c|PsL{0|OKk#HpzS#5MK=<~S-;>m5oz0krgr1J7!7 zu2jgpn|8tYi(LO~_Y(m@(%hX@H%}>KUU603cj3jMq^07=7wVn6xjxZ!=Fy9L^Ovbf zU6|0eXH#;p??Zx>@Jw&nKAk36w$j27cNY>wI%c9BG>L$=WEX0xDjDg z{OruFqOB7rPW(0L9H^QvdCQa4R?W9|W$!`Wrq`x&=KW8(FMs~K`uZlFSBahaB88&N z+IwyIN-v&y*S&96Z1MR`A#OJ}B{yqizN!t~x%EwNXQl7nUAu1jJl<)o``+}btND~i zL6T=)1B3tZuqdBJXTCHrmq`CJ`E#J&zPtR^rW!oEdB2<^>F^BG- zn5#B<3uYHBs(y84Wy-ZRktYwea!@_VL5?MSIp9wm!AZpvFn}%aZnGiBb=DocyO~lv-VK{P^)=*YG6h0IzZK z(?hRy_U`If$KSDTuH&f>moFcmEP6ZN&vTvJ>z{cO=E^vRvYqm2*D7)9^S|?o_iNgw zKIcxyZ)$Vz*J;}}x=q+qH`RpKJ;3+&jzcb+mq~v%k#RJN`DT&Xs`uzs?4M_E>wWSh ztb*N5*B^~n+@5uF{q)&e{j?g7Ncy!MSk*f3a751`-h^_U+6;yN5id;b_r0CoJ3m)K zTqf$r&$7?ycUHaR|FF6)a^0O|^)FLjWO*EZb93|L$?E6-m|lNVnhH$}UTu|+3$Jco zEq5vXi8|7+F0c$e?Z zfAg>YJ8;pU`G?x>J@T$lylaswgJ9m`M-X|7oSpV-&(4yUGXJ@7S{PeVU zS#7_3_{OzSTUTAnp1XaG*4uN|?|s_Z+l!}%E}XbUXu@SD8hAyYYDE`mNivWZJji z`~CX~_x5#b-@d*s{?9J*>(TStylZ|wJ9=_b{XF67Qkj3>%=dfF`*T%4`0Y#UJpZtJ zUk-;`etdbK6=Z_SEp{MV<(S)Ow~tlaz9VZXGM znbT!UQyFuswoS)Fx4+E3_;!hVc)g;WO~rVWJHJ9F@^|ZJY0G~f zH|3k1Ex5mHtMIM(HaVZNyY=y__SSsvpS1S={P|gn#p_MKe*Tmf^}lca{b_st{a!w4 z`*Hf@*YY>+Y^(i!UH@60_r-5d=jUgvc|G0#-HO_8pPt9(=H#2#tIyiHYCiwf zUk`T$r5?Yzw_K<4&&%hp%C6b|@+y&B933mMD`9!Oafs}lxW40O{puEoy?Qe3WWZ$m zBj%YepYitZ?uZUt`Z(bCDn+SV-+z9tpM0wL_4fa^Uw5qXHJ8busWIoJ*!9!_;J4~gYSnk{@M4r7D>C9 zT{*$4D|T;3-@2_^W_(w9tO% zy9!HW#NsQnmGAHQcIo>*b4Q=6v*(EhhZ=Vr4Ox46)|Sc=?c)Kaj*&-#q|LuJtu}Jd z26fsY(;Rbo1Y+d{Vy*v9S(s+pU0oQO>UdRbm+Q8g8C9j9v*v9(9iSxa-P5vtx~b*r z8ktwxRleP;q_3>|cd)?g-6r4inAdzcwXAlM70+&*ywFkx^$TQVvE=O+=QjD;PmZ6? zXxsaGzPlWq?_>9G%jbvAZv*-hb#_Y4zrCh>LulKiDQljz{d`dEYA@5iD*o4Q-u!Dz zwrBiQ=v-gr^tE*VsTrlx{W7}F!nYbhQxmX>!;4#kzw*q#{~^T4=-1pSPuAV9Ex&&3 zaaNdd@AV$l?RQ-S&KiZRkF^P&@WgG=dTpZ zacJf)dZn&vv}NJP9sFy5MtK#L%g4mVzv@p3yS(YxB<+{E=1cSA+Fm7|snfNUoTSb6 z&|-?ttJQU1+qMZE+wroW=j;~Mk6(AZT<_C&S7O!$KJZ8oboEB4z2?Nk=Wnd{7^?0# zEUo8#@9&4Nzk=#Noodhgwpl5~c*>Kks*4d>Dz=l4D#*1@a{k)zD*N9Xlf!3c#Vx=7 zx_0h~)QK{?Eln26dd!lAuU%MG7o>Ff+>MXlCTpIoE&ANFuJ3rL?Uk^nhi^?hYjCBL z*EjgDpIY!kizz!+JrA?p@v&fHn6K^ONkM-FgO)J4ZI-Dv#b)3qpA~C1eEW6lY{p%s z^))k2g-lgE7w|}gyKfbXk6G|Xiz^oxJJ+*px%#1Thi8cD5-Gn4*B?tgw~)jZlsd+ajPXdRPGdwSdCjEKfMw^i2! z{?D0Z%4GM^XkrSw(81-ZzgNnQ zd6yrnt6w>BwPYXTd!A6o#PgGmYMu=Jx#9A0>-jAS4<{WDy0lpOVpZVvtr{}zM=$u~ z`uFV%{{2%_@O~V>Zw5Q4dt3`$jpK6tTuPe8$*V{BH$HjgwngGR&*xL^d#heA+V=Et zl;XwCr^~Y@XGZtw5#uS=f>>c?$snep!1`(rMwVHSL~E2{cHONWqMlTz{K zae2_B-ye2$XO|X#{&2!zea(%7%jd1o*X@6|jo-FrVeRvKx>Kjb94vg{e6?7`**XQj zK5183)!cY5oRULU=B)?-&==VL6(r(TA( zvG?tXdA%1jk+u8n3zNee-P?mFt^SlKbtpjWZpoDE-K#q}4*90bv}3M>^3ylE^5eF` zjx4G9(IsB@H%V4a;}2fBGH2$>3*FL(3$ZNADzQEs%O18wCH(1;F0XYOlNX+9fBWmA zFDUvAY~$I!K6>W+R_gnsV+Ar;R%zWmQ}8=lSy&}}Yw_s-lf!9G51+br;_U0AQ@?Cm zHTx7+>$IJHA4+?#TlPa&d*>pLHmT~}KPPnuPpVFM^6}TDq$gh=c~z+Hd;7p*3;OB~ ziLyP$f9{|EH*w9+)03t=d9`ug`<61vsww=hmx!CUVGGHRX98@uz1rEU8g9E=EGkuF zVQ+O|sGZNFXc2GyJf7o7i{T(kF%E8cJawT;cydAV$*U5v+GmZ@&I!zIo$ z^K>?U!4V2_lB>E73C~{d@40T3ZpDPN0p`akp8t*dC4brqwI9ni-%wSnH3e!kImM(e#SN>IoDZEUM;?-aX2~u z&CCEhAJCN28u9SpUv)n`cmCS77Hce6o&0cM;`Hn!X_fFptMA>-+fevpV#=3A`b$;5 ze<)u4+I#lpw{dqQ`kav#6oO__Seg=-=d?-HOS6RgJ}%y^y7T6z#8ne-ud9hzk0W5_ z8cD92AxmTy^|Xre{)mKjn3f5CohRs2Q{s}b{3ZdJ=$j7)tPoj^mhI_UxACfe0E=5yo$`@ z>LNd#+?f1zsqo3yN3T8x&qj2;!Wwwx;phF?ux9nRwn;I) z2hV!tZO>5KsdR3G*WMk5SQVN%hXz(8eg3L>^6tlPlfN$gm!ew!ocF%J?@MW;JHL)y z^!kb;VHbi^=2V_;)$;ELzfCqSIW_zH{n?Z5gy+YaBzoNst-m$n&Ym21uY#*;yKy8@ zPRXoG?G;|}roMa^bwR`b+a|3(#VLMon%JFHxboAfg7*if2TfZ2vX)==c4&R8y{pRi zo74Z#5{fZBTM;%DN8~-f=bpE=Gj~D^=lk9A`{LqX`3s&r>-X^Y+y8gwys-_rJKq>v zm@iz&=~&SGB#Xm&`w6di^HjUlJ@U4#I<$3v+Pxcs>NYTujE-tS`l_fA_Ad%iHE^?oXJd6azJd_ib2^MYq8cem>I z{qcX|)~<;#pYSBB!b$t&)g8BA%iWka=}!B+`pw&V9$(;-{1xz3<|K%QHlaA&DmP8~ zJv)8h)II+XR)1Z(Ph{d9<>UUU;rBPIs_yjftFxc8gt| zUcv@i4IJ19TGHaVF3o=T_kX*0Pf8R#DO>&i@f8(;_&vGKo8Fy&e{K4_Ms%}gf)`F( zKA%%ODfjlat8)Fly{hSROT!*RhAQ<*ktYcTX7j7k%fqjfk&myUT9p zqxb9RvMEoVT|NHP0(tGS!md~W@TktaJ3GI+r_Zpj-?#I5s7KCuyWek~ytrtbEV22~ zZO)E${2Xr8|Gsaof8w=np7=e1Sq4l(DO^m}#8O=Pa(p)O1nmYlrB}^UhOL+uiB-IL^~ar?o72_R{pYQ*kM6-- zU3T)s36FQ1R&B9TB<__Zg@I?fK69U4>m`#*R`unf7mPZFXy4Sx#EGcj7|-)w>>ld3vGs!ec3Z z{TZg&*IFL(%oVt{l-WCD#*r^CFMIRGUWo5t=ae}5NG4_&GoyUXO} z=5+seo3yr^JoVu%^Y2Obd+$w{bm!{r=}-1uQ$EaG{atPL`h8leJ9od^um5${ZTWgj z)t&Y-w`RSmeeLkdw6gc{g?lG1EDr@OH;1f#o3n4Pr0$m;ZPz@{XUhrQu84j7`0=xc z-1mOp{RGFRG87u zgVem-t+4A=;nXWx5eJt}kBh4I`)~h#&*zw9FRPkg{Ax&0Ik~a_{>z9W*P9^y7qRG{ z>lz*^c+U3wom&#OB1#@VU(L4Y_2Ce49?y3l+D=O~fm*ARosXAYoi4W6r!Dei?ZZRd zlR&G#^mWYzT>>rm-cCSjXy*01g#Z7(X7OuIkRf6`{3k_Sjn7QJ!(aAph5p~?or|Bi z2)I5(%XjA%X`7TdsvanNe=k-z_Po`uy9^h50}SV|OL_dliKmho>Ih^8n2;u|jw2m_XKZ9{cy*Y`Mz!jlSVCRl>KN+A#G= z6t-w@dXe?QX~qG^&Nnlws=;0t;t37nXj;9@i(5-OZE0j%(Lrum|6OM$S|)1x^Bg@PiLp_qlQc2R@`BxoEY9&$*-T1ko^~8p$Ps)<9C-MD_FMb)UXxjMl>+j%6zu%m*o&;J){&nd-jl=&XYZX;@&XRBv zcIn9TWeF5Pi_zB#yUg#`6sxwYh_GJ#nkc}!c={ymw*0+IS3W5##&?OPF^F=AN5l$@?-ia$_AREe5bG zp82G#`~1H`tDE*;5}XT?Prkj1B_;PQl3rYtv^ni;mPh^!hecf=tD6#Dux(C{`bWtmu;%;flh3^M{5;>uum2JD>EQ17-)b-e;I3RpUeB@5VZLR8vGTtn4XSi^wui0r z;9PgBapLi8$$h`B?Y5nd#cJ+Ft265l2eA5z7#2S}lk)4!%dMU&t2jWe;A&g;{;J8o zjiBl-547NLQ~JqAQy#2xTUS+n^nBgd_>!{V!= zCNf_9x=Q%N9l7v27K5z1*Ln4v zoo&9}%tvwFH}h%tRyDr(b-}+$@ucj%f=I6-PneVoe8EP#Sf9|}xW%TvYs^@0Y z?!3RVd+BrR26BXoJ(_(k>E&9fj&=Nhl_d5~ikZrH`|IM_L6cVh+5sxMZg;OPyZh+A zs(XKJM7;A?jG{;2ZXGiyTv#5zE#H3qh?&Q_mheZf&$|Eq=BFY1?bX`uEc3-rzdl+u z<;FeddiP5*3g~?s&>+fcbDLYjC$DZ`;q~uO4PSbgZ*RTPcJcC?yZ-jhJU0Qf=LE4O zYCHF$*>kxf)~-4%#y;pSeNHyUH7Pqu0os_w`t#AeF6CEw;0pPOl5$r37_Q&9M(2i!P(_dagcd%c~nHlCfv zcf0!g`bo>p_L(je55Kfpk>@OU913Y~RrK>_mka0q!;e}GM*oklV@;Zwj+HxQi6g67v zOD{iGpS*ngy&044eBJ%t&nzeA`LuLZOb09{)cmO z1Y+e^6$D-4O}nXCyZudcSoE{03%%M`%H`?oyq&j0F|y)+x8_0JTiw#lpix$&-s`zV zxBC@#+4+3)e0i5Y-rtvH;kM@&X8j79&-2-QGWUW6k7Cpgj(IcbrvgdSP-n*uBq3L-yP9)9ZcocE0SK@6* zUp6*-A-Z*uA+qw|t38`O$B)vqi;c1)3esdAWi2^S%jxp1j%}w0Fm>OWzOs`(3}E z$POA_L>rpwjGH^f&bHQOd(Dg|+eKZCLtbvUURM|GRW$$irSM7G`4wxl_wFzRjqDUoGs_ zZd>s3`(*C?xY)^emTG@fQTOY;9)DLFtth!3ShC!IzTLuGPWBBiem$rRdbOk`!uk;p z|Js;|LNQwdtbF$Fu)b#%Rvo+E5wy}7Da)!~6xC8RD}Ht+>V59^e7o-I32U2rU;KJd z8uV&QO@#H7hw8`sd^KdZz3PtlTleqa%#}6ji!sAM*412X-7W(oqoDe0?4IkEdnoU+ ztC%yzJ1To&B_D=KY7g!l&A0t+uV+cg3yD=G`s*Iq}-E;2{LGg#NbaMPGlvdhxR}nH?`c ziwnHp^z`*DJNATaqMor$(%D(2CqF$s4cpzbH7hh%{`0iZrFScz&pr9!L&3XEVQ(ki zzdz~K(b^Y3y(hW<|Gn?d^=Y}Q*Zq(4Dsq0gLArjgQRS11ub$q_Y}=dFTqE;s6{u4O z8m!Jwd2xBwJBwP?T_1Uq?(UNGT_pXThkd1<^5tLLA0HfShRqVk?l!x*zrG&Sw_NHy zU9I}to0GBE)~vm^PepZSfBvt$P38aB-MKz3^ZlN^_LV=M$uCt2e_XhJXSVOAms4wZ zzF+C|TIF!uHb+~&>YM0&Il;SeQ^mQR#Sc9%QQ8%0qq6@t&#miowS=eX%6~hfZErf2 zpSx$i#l$_3>g3ms|8>{rPf9*+zjFVoSs%9UmNnXPIQ$rRC-1L-(&I0+FY@l)d3M%u ziMwlVq_rEBfZ9?QCkJgm)!sR|-gXM8J2vIXt7V%(!yPvJ#SU(00+qP(;FP@_JWis1 z@vZ7d-n=X+`D9Rw<%Fl^`kEWxw%43GU%Ms$eap;qb${l@t8Lf0KXs1Wf1ZN{4dC&1 z__%wXeAhbjj&=NR{k-n#$$vZ4DsI9OK2`MD`TxGoMevP}aOL2Q%NJiQxpMkb;`w+3 z{h!x*mAC%d;fPb|nqG93gOF1Gu3rObKJk2mtmCqoLuP`fvWW{0bPUwbRMK2!>}BM&|R z0Ukb5veWc+`)T0&+5swFS36lFV+C*m9h9#v`T!t_|wBGJ3)n1eW)RJ>l>*Omd_JnV7u*{QRbXjFMiqk zu_yheP}1CaQ)YqO9S2&552{+@Gnui5Blm)r)tfK>dRL!cT>Dbe%2Lx*TV71%`;nC& ze`&wP8c%uBDm%}=+oPN`fouL{*<+2k?X0uo_S>uOynSD4rT$EhiC~Mew*UXWI(8xs|NBn30-k7g z-3?Lf9sXwJ{!P}{y}>6nPr&=#ue;^fr<*Ll>ZuuRm-Cl3K4S|1>((b+SYzVs0_n3! zC$IKq>{47Sd)CkE=BLE${XdSn{WRD*u{Zf^c$qj>ZM`SF-gUh-7oQX}u{`M1H2&&m z+0#uSI@F0bu2ofzI4y0#Qpo9oB~-iHTvK3Z@1#G$Nztq z%Ri1yTAf*U+G8CDSX+NwW%A;h8MnY0H2gibB)NRg3%TDv=B88}PSrg5_s8YMUzZ-e z==m}qJgxKQ-}^ha1n%hH+m;xlf;Ha;N0q2<+4$tuf6zpfzV7kE+wV{7iTkp2%9DGQ zhwGQAlpl{i{%ZX@TP(Tl-4xMS&vjM#$tOeK=c%u#xzWb`%#5Y{vberS-mZrWPhrnO z%a^>!`tugNkA2;-=daSX_??)>U;R)!-fLg+XJOuxdtZppp0x41-Mal%pRoFO&I_-i z(7=PvMVp@UclTcNcQ`m#KWW{+{RQH%Dsy{>4$)T+FP$3p(YQT3P>k z+LKmp{S1zEr`rEMy5=4{srsj^_N2wf?bhsHT!mG)?ylYZ>-;?5&DyWCQeX2NXscOO zVW{03cKe_2r##V%-YyTVZIK$1F69ZX;a5e#*=p6UfVRKR{L56zADi!=sU^RxQz7og z-|e@upE~gIROes%~lTb+4XHn;0{d|MupTLa(CvyFbl(a%gtAxytw1-folk zw%gfrDoTJ`7f3xM*AjNW64jlvSd^y4_zDG1+4CvUKfc(x=%D|+e2;gf{5k%h5z4=^ z>CohZ)UD`#!8Zk-U3gFaecOHC*Y4k!jM~iV5WAe;rr}Ff%3ti7Z^{z>cHQ#0-^(rP z!|IPehlUoa6WmJr=Ue1Vy5oQB-n;<2H{V|WQ&!!nEPedd`gt7CUC=1uq~H?nqz#*{ z2(-JgQ#sTw=kXqW74z8oc;|x0v(L?fmI^2{^>3>${{8ahU$4)D^??u;-nA3E`}6a2b@*`r%Zz_TmXuDOnP=9m8g9N=H1*BJ zhf|)|RbRY5**mXlk>XD7wz!!Emm`g#g*j4WeOn2hNBaLs|No>vxA*_OJw-HjDtGk8 zzt7s9tdh0(R8*Up{pnQu--oa7drjrfF2iofLRcf)bDC~+*za#qf zTJR}xcU?)aSCRbAlDbtjHzqGW+HS`JbBf970P!Wq#eUBNBE z;9z}fj<&#Ep5)}@lQ+}n&y}&Q+LC-Rc5|BVrkbCh+%E4s-oN@r*xIP6Jv}{F{{4@y zkC`y(j{Cn28HZkfzV-gy&+{pN!}WYT-}$fGw_0(h_}zcYpUi9jZ~c9*>#e!v_h$0& z^FP;H?bL0&CQI45TG_cedz#`RZU)y2JC&;cWn2@u+o7;+m&cL*(_QPTwD{y~Ze-m0 z{-aRz_N}eilmGtyuKv25Z>h@noztg3+h=$CUd6kcPg*^SuE!VDZF;b)T6$9T+r9d# z)%UF?{Jys+_vNLfC+FE#7flac*vbRyl}rRp8SOPaEpU;wLFq-zMB93k?2f!6&{G|* zYU!rjhAfor_Ai#c%>R4cdh7VQ&nKrm0UZ|d>(aG7+w*c)iGTP1{7g0cf5HyMo!;SZ zG)pf>8pF;Nu;z(nVBk(YC)$z6z3h_2UC{X`p6l{lF7sx-h0J&V|FLxavQzDA?|icV z8V<`eTbf@mHL$u|?_8<0Yt^!Vu%b}AocDM4dwISSj^C-X^Tl!P?566A`evmy$jN6H zLjvQ)wGyHCzOQ+bRm9;dbdhT+fA!aEu9_!npFGNzdU{wd{#Q`Zi@)dED!0HIx_pbc z8N>wcb_HIoJ@55y@@(-fQ}2{LJuJre-UnQG-h$Qh)*X5b5e_BVZld39wRfJr7B{)( z%SraHOMikk#BciAbvkI$@6Gx5FHX4gJM3-g&X;Q>Vd+R9mSID~i?E(mZ>#SWw0@QT zf92DoAD~kOy5s*AITu{7efZ}|`~APA&PBHQR@rQ@^1wx$K}TVi=wh$m`(7XD;+(Yl zA=g^HJ}r50H$~<9Pi{MX`DL@umC66u@bA>SCdhdt8SEEX8Mr(0j;=Vh?|p@Jth)KW zk85ro{rDi*--IPR_i1aq@4nK<9==8E_oaJ>=OJ6SUE-!^&!#QWeE&+6i|hKHI)eQA z%~pHo`8}U5U+GP+6h9vJ^zg1qlpz1f!^^8${QTTnd#93WN3+jm3cIc{TwE(LdGGtk zlV?{?mX4R1J(Yj=hi6w!PhRy+pUepNow>Zr_U{V2{&FsgHdHIV-|exEL;U#V-L1{~|F{0@y9Zt1lfiz`SJ*{;URj*5XzO$Nx*rdZefe^ExxfFm+}qQt z;x|7{`uFGOO3gQ$`$1=H%`{FovooKz_D$K@8HUQ1RbS4yIJnyFzPamg_2ctWva-23 z)0aKAe?F&p($Q{lbGz>k4mJzVyS%MrSMSa5D@A9@oZ58XDM@zT`8@B{rQ4j|&o|GH z+mv~E*|FbK4?jHIF8w>=S^DJI@R_+UMLY72&RA1>|I4qxtt#O!9{o7@Z~Lri{I8$- zrk{n?DZ#22LHl!WshB_P{~2dm`wY}XyWeGO^7>-0tMmGkUU8eBE}HtnOGDMz@xzxN ze?FgIugm?qZ~OAQ>DhN5es#Te*Q?~~y$btJ^MpU9bLA#S`0vgYxq0SxV9Nf(bzK$J zD$~>F*Oq1dcAfY($F-vVTe>iJ&K}vd+YIxgx7f`7oznawtmov7??#if&FsEq7*$=I zF4eT#Obe8J`ef`w>z_V>)h5fm7FkP9lD;LLe>>oQmvPEcZ4Y;ymPsOCbG)2x>E^dT z*);RO6VJ%q=lfE(Ufp$jZ}@!Wm1p0Df3wVe9GO0M>aFWWw>37++`jmPRq`VlXZ@@{ zae-xXBR}1ne|LSDeSd}Y=WC(QX1oH0*^96H(pAIzqe0!_+&SfS&PAbiZ*IM}kFgfK zzo{ntJW^JetJV2#^5XYC0vF=8zWK}N>T1przx(Oi4AtPuB$4{Kd+EntXK#I1_Wtbc z_C3#=EZWxJcjn$Z@vWVEiF3);a~0K(WFF`8?Y--I?Pk}W@7sm%rrn;geVx(qkoBPK ztpDBCzi9Tt+2_9LMBWjI*}9|VhV%LTk@cyjNU^t8$klba++E`*xye4e&kNpLH2d!R zwA&Kz{XqeK?{{=(-s;>l(cv?bSA~PJtoZMBa<`2p{l2(w@5yi3>%2gI1b6x4m-{|L zN*B#9o+(>)K2`H@FWZw+;k)~(Q?l)BTfR-9X+3%0HqSm8xw^Ewf+y?6(=+?bwWDu7 zKV;T&{=}s`X;-uLWm}@t!<4qpuRJ8be));AZA$O|-dewCk#v4?MEL6DRa_uv-2Sb1 z@~Xt0MUz&0Dm0K*@w;Ez74$}%ck8umUc)cIA!iB zi=5Lk*g0O{lYG|DLD5Q-7afNc=3frH40t zEi{!YKUn(xTANnQ_kC-p?m#Iz8?q*;pABWXxK`rst?xD`L+{?PvAeNT`DV?@qnamW zf4&mkx{zzq9eMj5c0Yd~SAVsBof@LpZOCeRQO36Dc5AJ^^3LRbzU3_8xgV!)zs3S; zFlLvkAOe74?nK#D6LVeL%qlLw&UGrWX1-W@?>lHJ0W>ohebN?mHp%C<_sdzzuZr)V zqb2+8q5AUd^{xic)9PL@9azx(B8=yJ?er&hd%qsrHmN$LBp7r|%A84e-1YXUf$FT{ z)mrezVF_cx1;-NYw(I+zd%kW?tkK{3 zvhx;C`s;sx^8QYJ3q5)aRK0`4tH%#izkp6`vn+V>^w%X7OVAp)f~)WL)gF=+qw{6+`aE&Os`vXhv_t*aM10A-;IcfE+9lzVMGJHRf;p`Mulo-nQJ@G3!|P*24VAeXGClrmw|N90rPS z_r5(7-$ZV*P~EYr_xI%0?^oR|`1a;>{o4Jv`O#gIvF-i)x*xw-PTKCMPKLHkzD(ag zvF7!$iBUHHmy6=H+i=%zmoF1 zw%&7Oy|`9lXcGqNJ{O2}UL+CXROm?i%;>^{E-Bo$=_C zvyG}>cIWr!O}T7~qIm`Gb}62${jyQtRHgid^mWznKS{ZpKxr=A#}4lAy9Ze==JvUm ze|f?taQ7hF#kCTz^{kY4%sTe{`2C-$MW;W-PDb_8(;uL<>zf0=EqZBuAGCg5G=0K` zm#@F?_)S*QC82Ch#oErkSwSN%HLFtB0Mr)lcRsVN30yzlJRxY_yCO6{K9UcW`N-_ATU z>2CX*Z>`6x?t&UszwW%*Hs$7aBWLkXF2?$|)1Ew?@%?Axo98b#Rt0X3oEDq!bFX^y z?E<$wjwwth;5jC-W}Kc3^){ z`F!qltAx$$?vbD!Q9RHiSa%7ZJQXh}`B~tu!u3eOuIeANJpERG?+9Bo`)%miN2GD9EO>2An7wJiB~{qXod7`>+wS9GNw|)J= zCf1WXpU;bqzUNcoy7_xc>dm*@{fED`d|#Ei-L@`Y`%{ZSUH$4@k;qu!zLlUZ?-w0{Mb9G=i=F?mPjGvOIM=X2cGZ|A(I>mT2l z_k*$8a~)_W9->VI>cTseEvU$FWk3475wzU#2IS0NXde|r_4Xw2vP$R}UJ2s?rbW?) zD2WWx#RskOg`7fM2JKw)xLjw4Cv!+g9lU-SmYg7cY0%l0&@ot0YXa2oAJ)zimXw$G ze|Bc(WYE~u{67aY{l7mcpykxz4uJx9yHJ8+?unGHcNZ=Q zYzm#^XCy5v>zj6F#zfHA*FT@2h?1X@OC3wJQ9`&}sUxrc;>s6o^X@P#x-Hp}cN8Uh z{Zu;M9p<;6b7q$5>Th4aeR+9#GH7Xd{KS8+qIGXu-^kmUqN#IvSA5mWrB_~G1szIS zy_Q91+NL+jhGu5d&djmg{PCW`%eC7UpQzgwo&V>!gHHc)zqwxLET7Lw`S|E)?|Ivb z4-Zf-wb`2wt)ZlTFQ~Pt`B9Ku>sl;c`TF#} z{nHh8i6SKozo{>%VMyY5ZOGUc~*b^G~&=v7ZbKJM$=RuhD5$ zLr|aF`B~DJT{-!Q))%LScvUo&y!`m{`TY4cUoN`0ZZp1jKH}S~wc+!n@7)IV-#r)%PFwCB9rc;MLli|;;vHoBO-Y3DaC z7nB^aa?%UGwkK`hKJ4m#9sO~q#}Og*p8@$xjwtzuoYo7^kKOanA+l4b^0BY$?zbmy zpO0Xey*7MH^?Ez^pxI{)e$UzVan0s)Ssb%9Huo;NeCAPU#~KIrO_tOC?%7j&V%_4< zYd6p5m)Crg!m#t5)dr<-r|OGwHOVXXiJo;;@`#!@<;eTY?J+DjL)XXs`MQ1jmd>)< z6SvR*xN8>3#(c?(z55sO>wXE__FLv6@5QTDkKc9{ziEB3?bn|@U!SJl)<#Wj%e@vw zi_YqFnJITW>~!*`rb{cgq_&1_x!qSLbTen~qPx|bf2)3)cl!3R8D;a8tslteXy%JoKYtY7Jb(H&` H zJCDr2`R?=KU$bQA?*%oXkPD%^ogOMB;k%7E;#VJEq}a5jb^Wx>lP+fMbWyWeFUIXJ z;ASiTL|=Q`&EraS@j4D`w=M2)F^gW`aeZygysB3#k5zwOV)ouraGP>Y@}9Y=+c(ZM zNI$vGndKtyfBxH>5)L*!xl?>T_s^fxb#eFXwg|;ueD(S8uSZ@YHy>|COT2uGxG%&% z>HM71^dbzkNOY)ge6fw~qH9SkqIYpf0n+PjH=DBX`ut7|dC(XvN&<8ooJDfxw1<=Z z?IL$KT+A?uTD`2;HBjKLTmz!MS^i403%c0@v~wMDraE%X2XdA{uMI_kBMczb*8> zo6#00qk`4Kx2@J$O1 zN(#F`Hz^oQpcd_uiFySD&lJTc%grFCRxkdl8o+Y)qiy=RIc{xPpbnmX>mtx9(0?=Q*Fii?*e&LX5Jntx0)!sC6rIgK$9Q zl!u_|VUA7Zrf(Jh{`~Za@;tj}d&Hc{f;d)n$)cSl&KlxgvY)*s)2Q&slZLofJx`uP^;*^L$S6 zi46>I*D1%O-wHNSUT1tZbDH9HNtfxu6PO?Bdll)pTl_n8-Loe44f9^RrzgKXXuF^J zk0aZE_iK*pGrx(P?0D^yJuCG@Dci-TEGY_dm)j$51m?5Gi{Ey*VC7z~a76rY{om{L zs>hkO?CvX=o9Vy0XU92_#V-oYMAsX4=&37+PcX6em?gXK)|bZQjTRm2WL3`kUF2E* z=)KvZ)0`(*kGbAbbe29bQ~pj*n2gJHzl%KpWyD6sX<Sb+D%J_=XoSi^R}cu08A zl4d`4{5a@P9~EU~<*(^#?Hgo4Q>|&|=gkErg?%dXwQ|0{c#*L%mz6<-dl5GS^YK4F zKW8u6o^f%}FXMCxDJidK=jKM&Kk*KA3BP0kOPuMKQj8r+7!xKqmM|tfnDFA?{QsZp z)i~~l2;L35)bWDpKnT-C)`mvsMY{_g9-4LFXpp#f+`I=z4=sKA`Po@-v)o%#K#j3$ z&N_v5H9rd8)>hfT`t9G_zS{ozaJcvS@zbsqz0+F1vI^Rlz6vQiXZc*ETU>wM2AySB zt+v!ZtlVm|uy^|E#s$6H+$GQ|_76uJwkhj`h z!iCfqTD@MgS!z*8Sjko!|2K!Kx9ofKI@$2@rwE3vv5_D7-cJwkGTD4cLfB4uci#`& zx!+zKyDgiazWHs|cKzq`>+6!GAEiG0d}za@4gWU8*l*r^-fnj$8v{csC?p`z6|F??rddA84c}dR zQYgRtp52l8%b&h_n>&@6!68r}mO;WI=_2o#X8&pA`4{rJpu z^L+1jMSlH@xm)#}<_qg{-=4dj|7LFAb@{qJ28Ou}FPIJ-5bR#ZQM9x;cD<|lyNkJB zr`8BwJR9x)M^Z_pIFKX?&8F%*0uUTIM#3t_A&{rY-=)8a{$Y1CLDPMN!8UNuE5HME6D2zgoFm>d;j6$hR)xp;B`7e?A_&{%+%&&JBLL z+-s)>JQK_>{`1el`XaC8i@m2b^6hVTuFKe}?_}Tn;SX#`f1>Li-cuQ`udO{<`Fw78 z^?Ez&J^s4qq!||Ibm)P~V;&W1)20{y_W%FUf2$sJ9T4bNpe+Y8x6A+k&p2&v|Ep(z R4m|@|>*?y}vd$@?2>^O3m(>6O literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/clang-x86/Parallel workload.xlsx.500k, 0.99.png b/doc/diagrams/benchmarks-concurrent_map/clang-x86/Parallel workload.xlsx.500k, 0.99.png new file mode 100644 index 0000000000000000000000000000000000000000..5cb16d08cba16c29cfa88e664d2a43f4bb7cb5dd GIT binary patch literal 27129 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 MPgg&ebxsLQ03dgP= zMa^RhpZDIWeEj)I+nuwadV6H*?B~q*GwI|J&!m#1NA~PzW;D+xF~h4*fCK-h_6ZgiDJ+BU(auUyR~jr?A_SA?{Du7H=lq0^BVSD zt9R{swR`nyyS=~GhHgG~?AW2J;qktUFJ_oX^|~$Y4XVzY_K%r?fq}um>qMboEQlpx zDFC58sYP!Rxw~=WMuY5YYc^DTeDuuta^#kbz~hx5%N~4D)xKz3_{e33d4AlEKc7w? zZerz*@_KXRh|7;7>UM&5Usw7o?gFU{@SJA5Yq{UtEX`eox882Qe=n?!k%58X!I!ib zF5#)X+~RsU*8Cua2Ld|ZJ$aI1@%fDL;f2oaRbqEVwZlxzthT9oPZN=Qe&b5@-_7&? zt|^jQ8>j4C4My|b`o6iffzY>h`#%da)BifZHx^kWJzrX0Uf*Whw-?X0y$xLxzu&Ir z!$J15?v+K4d(GRn->=*KQ^Wqm1wP4Jiq7&y=O^;Kov@I9r}a7Iew#~w;{QuXUF;QK zmARy~XtsTQokiWBisEzc97<{(4BkvwC~LH(IpKz)vvtY~=0(;ni=x})?(V^qnLYEx zwpUx%PKtCf*B9WiPRVHJd8;5%2D0>5!;3VvUB--yUNbi(wu9`JWjPFSi$JW4_{CDD zgBzUNKuV#~{2h9Wx?fl|LafbThf1@%h+lLqnTu(6_Y0+6eo)J9IYJ!$h4G?miD0Zc zrdyPDb*vNSXp=SC;@nmSm9BLt5sY=Y&IxswHB{P8VONLVqSq`?ca1t5lm&bB2ZPwvQh_KAUn6Yr!&m=8Jh2g-T~;o9oBa z{d}5uu!;4uOLA4Q~7j@jFjXpX%PCgPY`0K9Hi{?l6R)6<1&8_}raeQ0a*;yN^zrQ<`xpPbE>1nNVt;@swPA{Ce za4#77pSE>3+G1|>C3B}W-=f=2CDl{!XcgW5ruAdzWpJ5ko$|u@o#mv3vPNGN%a1#j zcrTKk|9w(S^ih>e2M4nOUIP$LBq(%XN0lqHbpAJD&z@ z#tg<{x9Hr5vy1iHtgpvNU9+uEe*AxD6U6&3m={4xsLZkx7yQ;q%GLayT5$W;=fk?I z?d`AD{eNP9cv|@J!yCVUH-CPL85#=m(2B@&vvXTn(U}i-`T1I{&0}IF+$pL~INg8v zTb%&hmFO{De72Wd$gc(8KeIry53DG-^k%}sxiimwII(-ZcvfEM z;?+@iJ=ROUdSv36tF5IOH|pVau>$LC7ca7ec2%8d;YIF z$CAs-zTe&Vyz#@qlgVepOWCnG(s=5^s@&k8Av+zfwkFS$eB~y*>!Hi;s<^KjtLK@& z4Sffz!(nX;u+NOAK73^#Z?8ICD>lPaC1StP72Q_zJr%{btKxj`m#0*J7G3LT@aD<1 zg>{@4>=xFY$6!{>Zb zR_x#V#JRryWNX$gRiV(|4xyLr7H^v2$dee}9(b4k>rQR%}U4o`HbjJb0|d*{3MUrGz+6{fC_`^diik4ko@ zOxv}5fmoO8tl;z`nbM}k?BSpH%JKCPpVSp`DqkOM+x>Mz)t4*XC69ggpQy4IS**89 z$mi=T&ot9nZ7!?s-d`6ynIl_rlCf^^?{~`m_eIV2mAK#5cgm`Y-pyqax9vs!r8)fnUam{K^q2XfYe}pFIC(jyu9zHu zt9w~8vGVc4eJL5gr!0PY z+I97^{e^e(Z<{`ixwkxeYl*!6_NDuOJiPM1c+JsgNn02}r6o8aHJ=FBG=;14U;KBy zRLj&AljqentyMN(eCNvI$Z0&QEsp;UxNJJ#%VP_V?(Vx63QA{H-Co4<^8NCs-;1X2 z3H!b18+UH@ihHnRTe1>z*}31?=e^%D z;RP&9q}vn^&(7Paw`W(rck=C`+)V#jvr+CyYtY-JviL{o(XW8G*6&Z=)KrC zXL+0Ao~@fQKMSS%Ua#3D5ODg!rr#fp>-8gy-;{Sc>;GPO&C~S7si>XdYrS`7-u=4$ z>is>p_n$SWnI{P8MSu(MUsD(Qy?A*tQ2lLOg8di8=Q672%zI;tZ>7G7eH4&x9}>If zNZ9+?(?qsruUdKiw|CBNP3u)j=&m{uu(#l)|GZz3o5OGL*PgIFe%aQ!U#NW*kLos| zl&=%6?_J^lNO$Aa*6Mpp6%T)?bc1vrz!~J@xd8QJU%YbW#^{RQ+93aHLUmut55v9F zEHn=9m%DenVy)Z37gvI1W@eskh84vy&UEDz09(exbOp?cwJhq?+XM)P+?GFLg(T?0QfrDENCJ|IX(gZC_t3 zJECZI^^w<<(An0Pldtm~el7{=5rNC2efCxj-&#Kp>hX+r9$3!` z95@?OR&=LaN<0!`{^I0AU;p;6)9cj#{9GR1Qp;~AaIdm5yx1!aTU~iMyKR?-+FI@_ zQ}4_Z-z5{VTWwAJj)bV{N1NyKosZvW@g}x1-|SI0wg!N{q*un}gU?m>%ws;iebSw< z+~56m65o36i}t;(*tfIgAl6oczT`4z>6ImpsyuNX5!_1E4_On7p4L8W&0fbPy}zce zFsfVa*Tl@-~yH$ObQe{PQ6&U&ow&TZRuL&s~~EIqf+i*$BI@Bh4Z!@rB}+>&zj z$?-S-FK<75?)CI*V)t|J|ITx+3H@CC>hJg2-k+GURjGMKS2iTBe3BJms2{iU=dz|b ztN6nAZk%gmQ}`ia@s-5uy!QOF^>!`T&=cm%w72@rw6-7fY|6ImTyhUv<6(JTn^cY6 z)EM5N_pK_Xq8BxGF1pR5x{qDm%;ZSrX;pFYX}4Ep+MQj0{j1VGw7&49w054iS55|O z{}lW02W#-8)z}nI7?a;k=~IVpvBU zB|W^H@{nD^7s6!D*IM( zhUIR^YB`Mv=rpZe$;ruy*VaTPetdNFY`R@qQIXM^xz^haV{>b2_E;`87l@SyC->#C zZLhZ0CY1y{XPiZGRJHc2|&{QST7>TQ@9n94 z`02DhKew3947FW_RW)@t)rptnBmF<~e`=rS`@>s%TpM>gQa~qWj_#O{`NSq~dM9 z{=B^2EYRZX(vbO2cg3d^=SJ1%9^bXTe(lb!f0eZ__Rg^?T@_TKx~=%x8P0aT+AG|` z&o_U0rL@aV0o*8F&9i-vm7VI&EKu{*@8#S))$k`17W&QEB|N)oVQKodlE2obu`iZQ zo?O*)m3d9-)s?@$s(r6DoPNG&_q=^yr`Oo#{C@ad`hWQ4&xP}Dgz(G7%g;W3LatOI z^6n~FhH&zQF>+kbyebMFGEmo?!YjyZ~KJBnIGqP@d`%>7K zdv#T4Z`|uunLD?rdQWS4dU|>|q<_Yn+*W?4+Ih#*jru;G@ABn$Z850VfBSv^Pv3}W zp+o<7zFI!@^R(LrDL+mhwdX63uaB+x&~DGMTJNu4#g{|ehjyK>-?m}zT{GRs{`Yt6 z*m&mm`n^_nzJLDDSAMs!`swfMnLD4~c%2@(bA_gI>e6p7j*6RpivGK_%3MUUb=$K) z`YrbVUW*@|zwg)7W80qJx)%2*Kk;t*zm-MOHn)<+YgT@=>#zAdY31LC`}WtHZSB8P z`+2?A_k~5LuNJ=B`Eh#h+lnvetY?*7vu}$kseXC!Dseqh-sm>#_`6bdyT6?do>YDQ z$oE6@uE)>*HY@MeyYy`>>ia*f4_05qTW^2z?VrcZQ$L4oO}x(^Uzy}w@zTG)Rgx37o!lUbbI%HZ^|ElkP(eEsmN$&f1>)nS>w@iDtJzi%meS7EM zWAP^2qIK`i-*fxe{`#F4s%<_$maqM??`+Rjz3l1#_4fRDF@M>$w?FsW_kBLH?E2BK zJ!e0CUsk*4{OY5B<8=SY##O)DnfZJ2(x>k%mcL!Wy_RK~)ZLng>E)pAQrZf|!>0~+ zWM&w0|JrHsH#7e0TB(N%q}?j*ubiL%aru`Y##P%*4`g1>5QuX7!x?k>`hABllNNiK zT$-O1z17P8;i{vvPOaMZ>hympwSPbVhvrRp?-u{0@qCBfE%B}Y3>W)VyzAaLMJadH zL%+{Km;GNqU8D2V$kH^|{m!x-=d7GADHT?heldGfsj{h~drnjoia_Za=pl0r&X?6QN zzqUwYrI^vFL=n~Z6ZMPkiGQv9eb;|IPrk`(pW@ut@AoM0DSzToy!JE8#j`#oi%%Rg zEK2rnHJ5e2Znb5GLrE>TB`ur!?zpod&-`>3bK{G1IblWH^JgFBMO4C-^&%!b zS!UZNc2e|B``3&9dxZ}E5Z>W((dbLipPl=IFHiln(R^!c_fn=%-@Oytzwf(uXOhjy z+IW`|l$t?FU zbv+aFUtHTK9k*wv!p{Dj-P{{Qg2|^KbLo{R%t3+kJh{`}6J8 zO;27uFIum?>?Ww;=MEYqhK&M)YQEjh+dp3I;9B|Q-!uN{Er;`EGtOPKUA0%od)@Q9 zd%fD8+jMqT-SNlngXN#!v9}*$j;@QD zaL3v_#wK)Cz_PryNikE^d4g~S?8H?;tiAoSr)f=DIXQ9alUGh#91qmNN3NiQ^Psx< zhhcP_tUkxOTi+iHV*3FvtdfF-;)t$5Tb|rWf%>VIbIlJ0Ub^lt4X|@|S2NkWpwY`4tQpKa^_f;*~ z*>VsumjWK8nsaW_v!gF(Ec6nUyH%CySP;7E!?TOxvx`gIEe}7=e=}QcXMS$=Hp{L1 zSG0dq*WM=X@-6%SUcH|6)up#SubuU*`oqipEhm}Zn@zt}k2>7bc{%XX9?QE%Q_gs0 z1)rSy#jCDb_3VeeC$Arx9RK4^kH@-K`B$xHd%Z5zj_1g}CTbjVer;93>Ca(X^<(WX zMjX5K{N9`FTD0My!d;$G_7-!EzG#JN2pj8oulu+DIa^~@d}#G-uUBjP>q-n`ck^6j zUK162_06>S_^bO0A7AdRES)$z9ct2(Sr0FM_cOCwuI2gb*rw8l?ECLZ>Ba9`yXoa; z%YAj5k}n@!Y5h&+?TxaxukH9Dvpp7bRo2GO;gfuI$S5{p8rQm|Aq#tq{+vD{Y^=8P z)xy8e=jUp0tn^Eom}hjS{Kxyqzkj!e-~an(OKRBG)5o@%`OVrphbMS~iS^FZ75Yv> zs(+;x)Vin|PBQ6bFnV!m#;zw*63zGTd2*@5WFj@`-Ye9qv2)e=(7EpvCb-l*TXlT7pUJQN zRnfsZl3oe#XIJbkyyWGd5$gM1*KyjBl4>L8lDB6<+9rvAHS;DEUwtS(oWD>_zgC*}{uZG-TJwHnI@voO#Wjof*txH>> zZ}aPR_WaBDLyCfyAK!o5?D8Y)6Mq9t-~amRGAUf$`|F!mo4@b=EzciaE}L;8AvCxw zQ0n&9ALVbZ6uk)iCYIz<&Y-Z%PGMJ>o1ybmtu_BXe`P&*HDaff@%71flehbDuDdnO zTTgDrlWFbzlPk9EidYl>d*PE%q1B}muPl2VeDzUhl)8pzG1sE(s>{&yr5J(ri`*PdUowk-3^f}`8#|6b^2`6atME8}%dV92*qetVz3 z6hAuW`(cgS_y51tm->Hfljv5BbEVt%UJncJ5Clh4!;4#zTX{lPefX`uKXO9M`ykf4 zZ*zY4*F{XYbN2q8B_C_{uavU$VgFVp-o~3{f2GsKNy*Ujv}Tm)+U<3#Lh}_DWiR(@ z`(?Cba^0J+p9T6ubW~EK>V9_nESaCp%ap>&A9s3ye&s_gZ1-w$DK;F zSbwEdruN&^K%Lsa%OB4s@6Wlew`<@3SXR9)EPGFPGJ>(p~$)95kuPv?x1W#3(0p)rRK}jL%xu z1lZE;Rc^f{-bnW!-H%{J7v&i`P_S@z=xj*}i z!Xi8DxaaO$uq{dAoVM{(i;aKW=D+?iGcdj3+v`u$_trJPP}-%Zu&b=i_OSB%eQ}Yi zKD=Y!XS=5Q*n2IGb#46CdtSagwxuq81#|gNmGyC3cRkr5@3laM!;15&V9U%rp(!PQ zE?Mm@wKPjz`*nJbC)1Yv$}cU~j_%U`7F}aqJ56=@q>!iK&$*+`#Z#`ON}SW)Y0YQ4 zi<#l#SH_E5jn7EFyR*G2YUQEJ`uFAjeLuY4^!bLxyKalG+qp_%=dKqzvUY3cORg%@ zovQUWey`|V1D@Xxx1P@9ogH`Mx`4iEXU&_5oxX<@JiVj$SGTus^n5qPSLgqP8Ks{u zSJ;2NC%*US_E($Nyt(o?ZuuYQ`R8~1DJ$J3h8V@^ST{HEOu$mE*JXDC1n$&L(eg-B z-MaFLSJi3pxnH%N=f+%~yrQbT>Se9<-EW5@7SG!DW%-#cQ;$ZT-Ml@lDCDc$(`7}C ztG;S)Ulr7Sqwd$|`)$tdeP+QYCfB|14*PhuZb$K_^}YI0rPCMg-}tYt`Iiy6?FO0D zDS68i-KG_~qx|tw){}4DS2mpzx@0|f<;t3cU3DodqON^OSvBu;+-udF{BJ#)OiaRL z_HViV>r~yO>b9f{zmv8?^}5F@5Knj zOx1d0w_&5@bpFt7<|NP5fb_44JcEl&$`q zN51+G|HFC9#Y9!U|MR`ifBfG4`*RMrud0fZx%HX>KH8_mv`){ zMq3uj-Hs0b@9kJLzb5m{-jIc>Y8t-ffe*12 z*=tNpe81wGnJ6eWJ8Jpu0t9023OJd!yHjimj)4C6Ss@JU`I!}P6D4pA&?P7>V_fXq+v)$u!d_JGq zuu87{|9rVTzqfODNqVK7-Y$4GH|lroXU1>N@Wm&PLEwqM9Xok$2Z#2rUU>M@?m3ky z5uyFNKc0TxULJSv>YT%Fb9q9iU04qZW7wo8wCM;A;_Owg3a`K2k$CGu;x(StuL`%< zzTf@u$;QQJE#H)!+K~0b;BIPW0LEBk=N*2plnCF|Rd=*>B*U&g-dFV5w^uIC|5oaX z-w&4`ZuhL%+s^-*1!Iu&#NP!jZI?5{`=edgORoB6CK|K8>W)@as>KSNE_`_W%F5$w z7v1K$8ofI1c1V%!>%!G5PJOuM9Upel45NS!shut|~xN z@P)@DgZrx)!Dd~0IWKYh*Bzl(P6lkB^&P<;t$tU}3#k38Oj_`!vjxS5jZn$1` zN9$Hr&KrxeBhZJ^}-CX#D<1Ttv(JVZ!6L>KtUhcW*pid?dJ5`KYYzhp*u4r?A8d^OshpCVP}n9PMcQ$ zy7Tk%^Y+Kb`_1RCJ^t$I>cd^4+RJ8bv#p(z9KAIwR6<^U{T!5u|64DoJ^b{0T3fW- zZzsp1>Hp9BYd>FmdzI0i@8aeyrSt#iI2OtOe5U$vopql6nd|4vUVr-dVe4x-%`cDY z-mSX&@YwBl>*my}NzYH6KdrhiB_luO#pR;%ISX}SJ8aiwRvz3_`FYt?<1bDz)5>%f z8g<^>oL(NfYe9ZT_4jvkp~F;XXPG8`e|LB4<)X~f({#D5%ipb;GtJq0u2D_S>t5D_ zf8XBHZ-2X6ZvC9?v%}7PXsv$7CvErHpY`P5&o^{iuYcKncXOKik2|LF_gS3YW}lwN zaenWQsHcC7@BYyFjl7tF=k3vqjmJ&5#Z|?vo^WH?X{*v#Q+C!ItiJu5(Z2karL57H zHOJQ~oh|r&ztv1M=IUd)dtXYDZz)*(x_9!!WYOE)O{>4XUHZMn+5JzR<{Mt5=-X+1 z?m}1I{tFk9GV?cjDLp$k*ZS(=+uR-N_&e6gdbi#Cx+7HM@b^9$&fE204mUl?+xg^C z!JppRvpawFXBCFl&*oa^7I|aq``mBN?1-*UyHAPh&h0b0FMeV;xZ!ek+qI8{uQ&a> zSAFQRc->Nko%%7wp%!2N{XT2?>-4_E$Cvl^u3onA@S`6)-n1hv1^cbAYvUXTA*t6z zSA|~(F22n#5GyahW4$-!g`r)ZET3Nv$LsUf+ZA?JAA6tW_tyTq?;bh3;?Rv^=HDJI z+aQm$0_|<{i&e)%gH~^9T3_>2yZ8B*|HbDlk1zGRE)8CQ;b$)SZMU5HrL3Aej#nRt zeSPFraXIde$Tw%CrA6l!tqv4CXZQP!8*i@Gu9u&8LKb?goc&PYRiW$EA3J96|G2dg zqPt3U|Jnq5!H=}o@0(*u>#^Ow?rtYSi&()+ZBpxZ1mu&6^NaxcSPD^g;}_H$%}7hdz+zNKL>I+`?YnP{x#n&v!1*<_q@p?r>^q0 zxV<|xQ6hh?>vo%6O-sVfODcvxkRl<{`1^*qsEAHABk zZ`aoKtSIqSD{{AF32U49=7T3Mp4*U8@nbDB^UCA4h)VeK+8SGXvMc)EzncKNS6@^h=pZe2*U zULSw!W7xiFJil+E1tKU0Pl=dZIyA?!c-axNsrT-HBQR%$-1lk6_XSM2Q+MTY7_aQM z_XpW$1>e4$-FALol}gRaU!6@4s<5PDKIx8i{2XrYb1F;sDD3#V;&^EO+i&uJHz@1? z&4*Pye;Rw%(&}<<+xz(zqM%_9g&kS<&;wT1<@<5_e}&UjIZGW&Y8?!AxvO%n{$Eq; zSoFU3)&0A#3spDIZR1<*Y%a;SN!+~k?*+tytaZ&VmYKJoC{1ZC11F5TiZidera=ec zw)e^9oqKrrpS^92Wl4nom6IDn_1DdN9j>3p^?M=Gl1=uDy)T@$uyTv}c&={~m1YBF z>YiURpyff&+q$_Yt^UM%p2xK0#yaV`IDwehhO&ywm(N=6S{~dMx_3v_n&<4SD8{*8 z^u1V+^!eG@*^};7Fn7v;{q*YNr+;5tXFI(3ze~WkB6Wr0pT$TGpE7R@Sii2STv<+X)$wgvFE)INJy*3*%lYaZP;=vs z<9D?3MYiF^vK8~p&2PGb0yL`l)0K~z;&VgqvK*TA@QLhc?xxkZVy?cZnwYktD!O;x z1g>?re7>L=@wWNJt8M!?hB%sc&9E-dn_*jB_E=5GvI(>fEF`$};>;!6U#SV~O)34c^kqh5au~=S1XlA-}``@xlv%!h@ z*zu6qlCmdvug?yS4mNj=jabHgCg|W+KG|zrU$2B+eROYMS-C~!tHaM?ejY$Bmaa#Z zxXPp|?tP&H%S?8=Uc8x@dtFQ={OeBnIX2hkEVeB*x+0J$rKK}9hF7)h(Sw7}Wa>hC zyMKR;&pWdY!-p>AHylc8E$*^9i_g8g`{SjiC+lt(IzM$y2X*HUcJf|s;{%QQwDEZd z`mcSJ7kn`E_xX*b58sym&91k#y!+jmA5rgtRxfv6jx^Y{_r)5~w?-UPPeE;rWxA~fD`dW?jik#!0FSqek|LZ#X&beLI zrc5cuY};0a9a8Gy%DPK1P4vn-Z}oajrg?k96;|mvGH)x=R&<{OkCzt7-}v#^l1ETSG zZjtt>z1ugIDBcsZ08KXgu^+rqp7MK3_qlEFD;HQ^J-60-dW&!T|LsjrR@q&zez9Ry z;^9|!{xtptjcJ3HhJcK7D4FYO?v-1m7!~TBU6C&3a=krJya%*Qk7M1_MVqxuN^U%! z9~Uvdv3czrjmE;p601-R}&VUi>mx)wFOD%lqzg zK`NUfF*>fd`)baCbiDXTaA;4wyOdvC!Pa{66Z;l8RtaA-rxuCP(}v?bX5@F&-Fp}6(k=Smj0&%Lg%%dxJFFZlhs4<`bia$H|qvru8jTwIUw!8N2yQT-LuF9IN(Re$hYw<~5Z{EivPvFaCPrKPo}XoYN@llOZzC@twUEu7R+YMrOy zvCc~zJUzLbmsfV#r!Vl)w8;MArZ8?rs>J6LaUMp*{IYV$>%TbiqD%byyms~fr3yQ(zpu^n+gez0`SMm!d&KgvBQF+n-cEku z_DEt^+}=HF^E?tko#X4xL09LVyJzz&sd!cIN$Y9iEa6*s{{DaE_X^(3w%>g6@;Bff z1W#LnM{Vxfb>~H$dr)XNYg+j+$@CWgx=)jXZI4}(4pi7>_oN~q2$WpE+iX4YbdhoI zybEtHB$jqa@>RjJ6=(`ABONsOmM8D>{mRN<(9-!}rCp%?OCr|7!osby?r2=PJH>rX z++M3S5gU_MW!}Dg+;Us!>M+x78?&PHa!*cDP5k-k>D0??_ulPN{!@0foNLnVKO6Jg zKJI?MV&4AlviJIV9NFGWx%%U30ww<6eXsTW{j$Gb(*Miq{kVCU^`PzhQ&%6ZioY9g z`G0}*Z_qM!0Tdt&R)3p?}d9OE-Zfvnp%~X{I%tdY^v-x@*H4o17a*_g7>+INWv>)U=fZFARj1{x0PcmWc0Ixzg?!)1udhedMN? z)ZEC;K0jY5=5IZ?Hnv|ho#$$Dw49ZX?e4uiveSY}KeKPm*WaryV^y@pGQ1A0gncK_ zwa&a_9lvpDfEK@e+4HvU!xPJw|9^8ow{2H$zSfRw3lA%W?%g4-_j`r7IC#}0T0>%{ zg~rr&{@vw|mp;79zv^`ItF#ph56|15ROnb_T5`i$|9#fEi_&ZJ{wcPw%I0`syKHYIMb7$D~^!MW%7M7^SC#z}>I?au{BLZGUgf=zHRv55K$}Z>h z>aTikIa3$v=~O>`Hk+$yHTMO+{Hk4+?)g4)73ER-@9h8QI9DVckDp!a!;Z}Xb@EfJ zc;&BkuHrFuxv}ki?RJHoa?b6ccMpA6*WT8?`2AM-wOAcqCpTqP8~^R{lRpQ@2J-u!9?a?xF47ba{`8^^wM)x*PP zdn$^LKRkS^upr4fqW<1a){~$Gk;PfxSBQs;PhZ+}J6k`G3!8R1cGc4#64&0aE%SCP zSnJ##`t?zkgdwkb8Y`A~;C$`4GV8~N>EGAq`@NN$sm^!W?E zyAK}jnyL?K3z@fE=vA}<4{9TI`as(Y4;6v3f!f2jWj{7dSrz?r-T!<0+rQs`nfte7 zb==)e=AtoQAHBM^ZyweFe(iWN`^SckSB0myf4ckq0kduDiny4&>&!)Cwnod>9$hPL zj?LYjaeQ9$4!3uLW*Q`%N@gd9>aUY~{j;tSyy_WsMWu^5yXyOghgX5ESUY=RRaR*E zY_4^)s>-e&zU>b#-`P{)^>xG& z)r-!7!x6MI#BytO)YZpf7xfN@?cE`zz5V{)9h%_fWYu~r}u)I>W`oDNny)^w`8Z-wDYf)k^era+q$nmdg{V=Da)*9EuFMFF6Qpf z95rlFwCJkiN}HM)UgA}Il>nm3MQZl&?}>49~bhud@S2ix8WSJ(Nv zLy(uZ?CvRXuBO$?79J^L!IIoNXCGU~xj*{*RLz51dF0d99v9v9^Kk3wd0HIruGjC} z__1akHvcbpcsQ@}4R`RQ)ejGYTOV2F6;~dwdS@mYv-SUu)YyvOcYp0YXv~NuFa*Ee zayuCMy7Y%Ze^pFxef7^zxvU4To|QM-Tf%X$paHa37cr0znla>k@on$QC-;6|+279Z zCztKFw%^_M-RAXjnI5@XJ9jhk{OCmDp*1?@RYOey?j)F}(cW z-o}d`Klb<1SK+DRa}=RFjS*cc!C05=+^h0f6km$om6>Ams?gLd?mLigO~RW7!4H-%+eSStX$Vr#AA z)w}0P6r;T6Hc#3rw>>K-W~$zmorfi1dz3(H@xgre<2e z%Pu>fjOvdzHCw0WboppTDRgr)c-=p!j|ko|mM0Bra~F8uFDtjsE)Cdp?eR7#>F~py zrLg_fsBsj%V7A>0zVww(-dS(jYguX)3iHXNdlqJS=CGO?HLRHK?lYfy$7=f*UHK_G zUw5oMb}aO#(|YV`TdH!eR?qgzn;ZRW8M~@CIFxoVtj)@~lZV~?O|SA7KUlTWE?j)^ zRY&%v+wav}4gq_*O(@$Gr;|?h?d2C&2|wIp zWA7d^pBi)b@0xW_>V9mWysYZZ$FGxF4~Bv|c(;-tmtlr@d50^gJBwI_UiG*{^zQ$^ zAMz9L_DOS1+WqVNb@O@a51&kSKMZL|%-;7ef9tD4Q=1u8xxoiR*XM1=Zm@@Ic~X*6 z%_HIY9P@u&SuSu_Zo(N*UpwaOn`=*R^gTa(On-j-ggf?eU#=cInlbmrmrDyXlOO*} zIr?qhv14!9V3j>=zkkO&fv$HegO?v_V&y&rI+x&w_{Fz;Q&nv?JUz~O^6sZs6~$TU z-)w(4I2K$D>A(GQ!IGku__en}1!8(PBuGkcmj>sV^cluiIA5;Y{ch34j4j*NN>{p; zXiG>-b90O9%@K-o*=`3K3EOc2)KTsTWAB}};q$MhtOrAF*Hx9fS|7a7ntFf7$Cph{ zUTrJ|r4~?CH8=5GfVu%ggI`JU8ix{ZA3wh~d2Sy);ZE*#UC#CU!(!&ALiYOl+1~s5YTgI={kLNT?wpVP zT(RT-M{%z8(jlAEEIPu@8EkiJ`(}1VQfU`M0K2UAspI`&ycc~fpyy5`!;fg#v%N69 zJ@K}#eK}}<>#^LL#ZO*++qdg%zU(RIcH56P{o9$dubV6v^Q?cDV`}#9V6wW@<#eXH zvhB`oWg4*z4S~yNC$lcP&6ZVSt-NsR4Y!k1Kgd;X|9muF%4$uG_I+RRSb=-9Yt~vG zFL`uw6MSMnd&sEodv|q}9^SFW3a%UeK-H zUiUnj_2kvZ!tDHJ&sVsKPn(+mxca01Jg&*?_j1oo6o<5rUN9Y4(flIq*ouyIc@JlM zt;_qkI{NRn#~+q0mj+cD`@qWx?v>g6el;<3`^UXM(!aMmyMJ17d@U2Cs)yNMc5y0r zQD*G+$8CL+tSgUyYI?BB46mtseqp)kH;h9CNH?_2H zi;J8P^YzU(=f&zJ)+IAQ6Rnv`xpwvD>x4o}-!08At}U(+UTidXS+o7$6La<_e+Ow_ zvq<~o)z-9?pvAal87~YxbG3F(y65NI9y(PEw4~tB3NC1&SSD~c3v>j_p;qqM{Q`F# z1J3Oc?#Pn^8Pc<8^}<8%?p9pkVGQ;%O?_TJRc&~R(qF4_N)>2pP=>BU-o z-3OXT|2F5^((9!<`~B9QJG)Tlwyw&xk3T={+7g{+{pHu^bJqGfH#Q```g$ty|G&S% zw{M)iziiK%ylbfepkVM>wAy<1OS64}1*Ok7+p^EkJZCD)D|qeng~VO6XTHmt=JoDs zx&8fbYyz+lEfR{I>id$rtV&19<#P2arQ1`&YsCg1Q`jl`uCr= z{eI`zmK|?OYOVHepCc02Iv9FP0K8M2Rytky2pNWa%t}}~T>hG|y)X3jqL)waq)WM&8@IWZ#8(%4I~INYU+ccj z{P~Q&yl(;E(S*5eeA4mukrofG?`4E-k)AuprCd$L+)Va&(5XwJOO-{PJiDsQR2Ang zD|afo>6Nf*$(qa6%QxN1yE!**_4kQarfpySXU?^cJ;vuWaoA@&+UH~cHK9v$`3vn`aVxPq-q^Z+o(Z(=aqFaK31~5Gfsa(m@2jg~&u;zEv1^fZ>-x)I{I*P9sb%POfBmby zm!EEpE?a&*EZ6yJaiGlO^Y1qvy?4Uzz49-`T~TSf>@xdaS{=Xl&gyiI`_G@R6%YA2@(Yec+y8_mxuRb2QZP&_+y8FwuEwi`h zOuV+>Slqc!*{3&!*6FQtuIV?Ldo6Iv=T|PvQ>Up0-HufK^RfsO`K#jAzg?xh>!sDP z)UCId7cpEs+Y_@rHu=%AN!9m{ot8>|9#)v}c2=fy`^$6bZv*n*`iJG{bF8Z>eU0S7 z<`-eYt@{7g=)Sxl`r(wS-Fz;u7eQR4Rm!314S{O6^wBBx=Ikn$z1~*C05ARlrN}u_5MOCf!MbWj_ z4E3~S85iyGf6B0EwxnfVb!1U~e6eT6^S#?=1>3ucPupAc*_ZX;TH}(4(3MR~w$Bb~ zHG*X#f!GF{m6M*yIF=YIZ_lm&v~*+B6T3}sZ+-ajYwKmdJ}IS~+u!#^Pl(}_Ed#9- zv0Me(Na4X9hA2@ODw|$>V_P));?nQ>47!yr_PA{0h7=X5FF35aCc_oV;v%tPyxEmg-(jE6aW5 zPdq9f&RWei3AFmHT*k`By!yg{=PRF7O+lo+<`+y2tS;upX+^hJtAv~GO1*Z!+W*F% z-u0p?+b>^83|-3g|IcRLyro>JNZw6gym+=}=B+Yb(X#)`-ixb*FV>v`T1LLc{{5Y% zC-3BTFSXn{YgJI|>0{e=p(F#ST}iiJd*rQmZnyn(M*6asXkPs8-IiPHVOi6$gz*5= zqS=zo_sZ_xT%)sd7jsls8^`5<#FbB0l`On_AyF$7GKLzu@(`$#c&eiShYlvI&no^RkBAXS zUI+a%V{5X;ty|?`rYcKI^S4y;4?tiJI*-G5)^C4RWs37Faa= z+WU5{O1Sv+(7R7{WUbcJ6x}KR@uO!tW_CL+R zYV5g7lONxDes50Lst>C}ceBN<3P9AJ+zes@ch9`IzHRmOu)Uz=f92sTLK4Br`;M=8 ztU>6i05|pH+wa%f>r^MCgEx*G>DzDid{!iS2-oHOp4WU{L?v8&q3g-DTA>%#*51DR z^v%Ea>+S3R7bQCu?f?5)w^yhw2GM$eME;)bvC5h*2YV;278G|0U3q99yL^UEc579R zeeF}w(1-I%CrPxZI9z-?+VkDXWOr?EQP6(w&r>f?JmHlQd@wY3T~x_5QUqW|vO`u65JZ_$uR3of;U6ou}Z5RAowZ9g z&-S0IvO-oZ=spv4^6$a**G=`@PGdN0*IUpgBae5^(d&8M%igqK8TQpm8TJHT1kIu55tWCMS_2GwYx|h8~K?C2ngIMprSQxWvfwYs4T*Y=nS$H#t zn*pqEZ(jA(C;Ptrw&n&+Lht!2AG7_9?sTme*Y<(;5HTP803J;NwdLGoUCfPl-Temc zBYvH~Uf<*0;5w^ zZhzjj?XxTW=UR!1=|l*mot-86?EHNBK6(3l+jAaO_xAUj&;PhE{?!kycePWt^}ZM;5csy#b=mBK17Q8`d^eI@9~709u3FZ$Fv^0HNS zZB#dJdeLXrk(X9->zmfe+Jcw+MO4C_JGW|wf?B_)f?8vLznVKikOz9~+^)L|rQf~K zoAmNynSk&6%7nMGo<-HPwD{WZU;bA9|3`UgrYUyYD?UCt_9s+nCRhBg2&K|{mCsvw zrOjF@KR+{_W?cR4&4$X)&yMX$f@~-+e^tAvWUfV_(wx$3k%=!aE$w}N`QGKQ^@5?V z#pT^McCUZE)je#I)bxd7@ss!MyJ|J| zsu@H9~K>{jmXy!)$f{}5C8{`f6wPtzq^ucs=F`jvWiWrWbSvV zz^!#33oE0RU*EPkwm)xqsYunjch#X!XROO!{(W-Hyvk=YTV-6AKi#Ih&+K3AqsJj} zr(a*pv$cM!ZQ1Z5&24q=dzGD9p#k~M)}XRqVMo??-#z!{L?O!Hyz&=*lUCO7Z35NX zXSdGKb4aaRJ!xZJioq%GTe~%GdOo#Mjg>#Eeo^Q>>aCAPcohNm&PT<<8G zaAWHHI^|bWSLND&diR?3$TwbXa`r~BR+t{hd)d8P+>d)26$#RRNpYGCe%H)sNKAHJl>Y`uC>AkB` z_qc4oY}K_YC;FnN$Ll@uDrZYCEV>Pg2dhQ)bwAao<#}%3tFtqizgB0bR;b0EmoJ~q zx!SXSz7A*);^rpE=@($1s>r=f@qTp4>zPv3oO6@y`aT7%+nuADQI%U06JB!p`jO*L zzg~X4L+`ELAFch{WTX1SZk9y32Tsk^w&Kj3`@MU~=~r9sl!ffw7Jbq8y0PB+tNSJ~ zcdS!>I(H!-Vyk@F-|F9c|I30#o>@=UepxN=2_JRQ+@qJfT~D4rKK7G`V}X77)mI-r zN55Y?XaDVOSyxvbYhQvg?Oc?Rl98X1k$-s3!k_`D%6Cp!-2NXH>-=lhMpfRue0|$uFOJLCw?~70 z^NJ&L_xH%7u$Q)jxt#&~WE{J$}`?2pl_l{X-h()PPQ zZ#3BUtb6LD-EViPKKy#UOgwJ#wym?vTlMuK<0stluHS3;=7wpcWxakbWDWMU*Atwh z;d9r}GvRIw^epwx|kjGx~Ht77#jP^^d4_%(m-on1_ zdf&7#gzGvi z`$xL;q}3aRZ}+ZRpm@k6BtmPal$Xz^+lW&>A!9;pr&=FvysF!^-f2?x@wxx?*ZhA} zc{%TAgqwM_2Wz;mdFu;vL^B>T5)<+J{D1YFolhQZTgoNH9G?7eS<~tu*4~-c9OyOO z#xLssZ~lolUF#*vCue52R!W~D{rrZJ0(2v`$xBPa0r+=8aT29~N z-TJV%QWdC86tG1Zx1*{xX+_V=CHXVX8!rb^%~mq~Z>@BAnUL#Z|LG&Qy}{y!Jh%Jcr3S{ZycxA8?98+c(l&FSa)W}D~doeTW) zGJ5SD>l>N7QZ)T8Z<=5AYUQCnS)kQ#XS5l;m#+B}0UE3Zk35-qRqg(|V)NX&(`MI6 zdOZDjq*M6en$739yr=8Ut-8Or`nzAX-|I`St)|5<7c8sku*|$5_#Yx}y4zX3g^j#$C_>MV#uQgT~y>{6x zceljab(h`8Li6|H+ZWEM*4ps0x}@s=DjySn%j=G)fe0Nl+IIWey!$cT{jN$=El##q zDXWTwF1q!$xQdEuFJ2#s#V$gl;`yMOxg1mk9#x| zh3~Fe{K9l|iR2=k3lod_?(U1fYBpv1MZMRbewW{}XXjlsTN1Ro7&NX{t$D}Y-)tA{;zT$ypS65H}_vfu@?xM?8A-Un+j&)9kpWdx zw{5!6<@frcr&6?Tcu7@mzxwYN1IfW zU(M9rzfC9k>GMZcOZMK;B%c*U$Pg6U>7Oj4M`8YUyG9+K#SFJto_g;Sb^{oFg zCaCUrELpBR@j~d^Qwsmq%Bcnq?k}Y) z8Ma3ETM6CF+Nl(~?Nj8=qNi7~S1;N2%2M#U^19?*^X{%o)=tz5j%({!B>i9h`qNFP z^|;gL*M7TlfB)_8t8cZHEz-WVw>~D$O31bHzl;jHOBma_wF>8XqUUBN9`TO6YVa9@ zh|;o0=xI=I(dy`17kHcN{9U%&g7+*VmDivVaipxlevy@d8^c2I=mb)V5r}2j(14cv zA%p9*F=}~e+xNTW@sC;D7GKPGJF8E$)Dg0?AL^00cZ@}#GsNxi!~NjZB&0lror?ks z8>qCM!Y*fxU6UtIPJDM~CusJu*UZlD-keL|MefiH0sWybAyiet9rM1HRSo4mp$e97Y%QSD;Fw>XR3ZLja#T=VnO8v74u zI@tRZztb*6wk4(F1xX(vbdOQ%i_zGEuVeT zZ>1ioTqd{H=!s47tgd&R!s>_iRDM46bb9=~8;lkVrTs=W-|rNIs$J=RaownvC8eG= z4B!C(M*SP|yPYS^KDAa(S@iA861j&qmuFf@7R}4_U(REBe$F%7U4`rq7*gEtt35wI z-@eqL>0ANBEB?uoCqL{lKDWSq6Zfh4@pEm?TfNpXTI0C?^CJ^9;eUJPn-+4uy9_c|7sq~^9s9U8hDnN z)^@bYehvFjypfk>ZTS~gMO(?bPm|}X9G5Dw>Mxl0(r0&%#rZ3X-_?~_%}W=E<#W(e z$Sj*+V*O{!OXlB6wF0r$9<%1|lAPB!Ki6eC^91fIuD2AOrBBS1kLd}Mce(C&x5vV5 zTN_Vw^NS*eSDdvG`#w!wZzBKEcei-mhh}*ZKC@phXWTn+FH!!BVqM~c9TEI4*Zo$5 ztXv-2HYeRL_+l^nr-<*u((H?V9pMi>)5pDXm-<@UVCSN0cFWC*3ujoDzq_)}qYvgi zKgq&*FMVL)2})1~yA;3)%U}{MX}&pDrCK@>8x(p#V?8q^A60?U+nM?H_A>T$dn}jE zlDSr$l(gvDE+&Q&?nT@T%EuSEc1tY+jh#F*mNztm?&^Kwt+z-!a}H$QE#UJ^rKdas zu?!NaAi`2**Z&Xv{|$V9_=#W4RqyT4W0>Qou#3T<$fHDm|DR872bKm+_llePVCkWy zFQ3mXkGruU(HV5d!s7ObM|9{Z$*gb z@Aeh7F)-Y1c)@hwgyO}iM?6=Y12sVcUxk&J?)7@}D0|DEPk&a<$gI+8h+4m?;`nuM z4b@qxi9Vh01LNF&&5Elox%}1d`sS-urMIRPpSLZ~lD(Aq@o!S3XH4Ckb^CA5OrLkt zh?$`w&!L1d;R(3DRMncgM+Wj6sYcJ=nIOJV&JGcA7?d1_ym`s4rQhy(s2o zgn7nA-|CXve^uh$OIA;txUXHqJ6TfyV(xa`Og*K!`(JHcdu^wm8Uw>yu=6##^QKsS z6<9WD`OinmoHMEf*Di{FcU4D!eJFpb?8@%b9@iCX`2*ccF24-DebckVI@de0WNLAZ z%yjYi=ySG9v+go5>|(sg+Awjk&MqacP_53uU61@3V zubZ}0Z3U;V%-pzD_>|VAQonflmnypq+4qJ?hUTWLUle`2&GkjT!2JDhHl03nGkw18 zRM%a6r!p=t^L;p{_*~|mu=ld@%cni#Ww@Z*p$E!FJu1|uc`yE5|NqzeWA%`WNiJq= fxtJL)|NB2<_ii7KiF?u`K-PM?`njxgN@xNAH8`i8 literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/clang-x86/Parallel workload.xlsx.5M, 0.5.png b/doc/diagrams/benchmarks-concurrent_map/clang-x86/Parallel workload.xlsx.5M, 0.5.png new file mode 100644 index 0000000000000000000000000000000000000000..18f7762f4bdc95c838abcecd694415c353c10d14 GIT binary patch literal 26872 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 hsdo&v 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μ{G^px*BrMAstT#ze(~GQt8SY8`|J%_-E|>M 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 IbxsLQ016lW82|tP literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/gcc-x64/Parallel workload.xlsx.500k, 0.5.png b/doc/diagrams/benchmarks-concurrent_map/gcc-x64/Parallel workload.xlsx.500k, 0.5.png new file mode 100644 index 0000000000000000000000000000000000000000..057cd82944852b6be19c864ae30367a89a49fa7e GIT binary patch literal 26057 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 R!3e4aJYD@<);T3K0RWP~q5S{= literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/gcc-x64/Parallel workload.xlsx.5M, 0.01.png b/doc/diagrams/benchmarks-concurrent_map/gcc-x64/Parallel workload.xlsx.5M, 0.01.png new file mode 100644 index 0000000000000000000000000000000000000000..6517f4e62e6abe362aab2f5e7bc73fcb78f7c257 GIT binary patch literal 24966 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 literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/gcc-x64/Parallel workload.xlsx.5M, 0.5.png b/doc/diagrams/benchmarks-concurrent_map/gcc-x64/Parallel workload.xlsx.5M, 0.5.png new file mode 100644 index 0000000000000000000000000000000000000000..85e9b144ed2c31a87e5dd822f00a794b3715df17 GIT binary patch literal 25724 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 literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/gcc-x64/Parallel workload.xlsx.5M, 0.99.png b/doc/diagrams/benchmarks-concurrent_map/gcc-x64/Parallel workload.xlsx.5M, 0.99.png new file mode 100644 index 0000000000000000000000000000000000000000..5be0fc3ad1e814901b6fa8486d5aadcfc3324cea GIT binary patch literal 25655 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 BNrC_X literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.500k, 0.01.png b/doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.500k, 0.01.png new file mode 100644 index 0000000000000000000000000000000000000000..3f96e2a1e9a6a5a027d2a23135c2025a9ece3428 GIT binary patch literal 27332 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 Rwm5-o^K|udS?83{1OR>FU8n#6 literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.500k, 0.5.png b/doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.500k, 0.5.png new file mode 100644 index 0000000000000000000000000000000000000000..58afa8dcbb303d9e0b14c9d0ad0ca22dfc64559c GIT binary patch literal 25948 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
c@53V-`#PW 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 FO#qubDO3Oe literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.500k, 0.99.png b/doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.500k, 0.99.png new file mode 100644 index 0000000000000000000000000000000000000000..94886b6c242dd9804b2fa5d2f715468768ccd2ce GIT binary patch literal 26374 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 literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/vs-x64/Parallel workload.xlsx.5M, 0.5.png b/doc/diagrams/benchmarks-concurrent_map/vs-x64/Parallel workload.xlsx.5M, 0.5.png new file mode 100644 index 0000000000000000000000000000000000000000..8235baf402c04668917f2e85b9a5147c4ebef475 GIT binary patch literal 25065 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

Y zcE4ZTHU7Hvqocu_9X!EVN54ug;R$X<9xZhfiM>86I^2%xlQ3HwZ~?RWFfUcHbcm9$3M>aT&U z{Qm8VJ6CDx*>O7>@iHSfvL(%ZAN zPu0yR`8aczro~CyFMqYQcK^^}Irw4@xbghy*-5p7dw;0=t6uGTZHzwND0uf&SySh8 zb90sO|Jwte9bVFX{#$HGqI1#L|62MWubf&Oc1QR2@>wk5U*G!vpF6j1d3pV2KS_*n z!8}!K#~ZU&-xfPnGb3(C)e^;>dv}0bq2V5<{qFj!M1p?s^Sc@Q<>E$^4h!f{UVa zo@DXdl@O_&VK<|0Z9X^#xHT_#VNw1H(pbMM@uU{iSifBRm+4?e+OM_W#kV}l+O}Dp z@BhMo9j&10Bcx?p;um{&3Cql@)Tqi{cjC9f%CFZv!OpquFVM3&L(TYX#xw7Cq0#)Q z>2?`+Z~38zkNGXX57+zuPrAGL`J=WMesgE6(BCLrH{%NbbN71CgB&0Hm`~* zZng7~-}BioNPK>)j@!Y2=`5wbA5_CHt=8kYi#}{1cvr)^QO~A$(dvgslQvghKlkQ= z=>2SuJT8!4wSxiL>-6+Kvu?i~79)?==k9(ns~pr(cJ9vX*m&d3zE^L)?9=iUnkd!n zYVawl(6nbpoABl)w4p1(*w20+J{)|loqP6b$-aG0vUc4Ukt{oSfrTH`B~sa;<;$_K zQ!xp{m9dxoKAii#-drVIbhl~ww_agi@%f;oB(k?=e9gTss?c<6z7J?X z0cm}v-lFVVoNDv#-^|?*7HDv#YxT>j+Jm8Y|Gm(@ z7*$w{VZiOLpoI6T%%`va{_P1dS6_&+X@=_k#aUybI;$kQE}%kf!udjH2wMn zGo~g6YOgzXYV9u3>zE7tWZ$#MpFMB3^;wq8-G~q_em@bnyFcO1%Coayd0SW>Q_y!lo3c#uY|Y6~?R9B(uh}sY z>e~$UAFq$spL}<-xX`&|xu4IK={GFD{MDA4wEMgEb8}wRV<#{6&3_2VQ|HY%*R^by zU%SH&n>in%i&ggs-Iq9%vwOYbPOaWu4-TKa*mv4sy=eF)?VoSWvFX{7|9&a&#i?J8 zJ=ih9Lob=$3fzsqHx#RBRt`?8|N&FmE~`uh6Pyuth8W-Z?{7rF~K z`dJoeqG;*ez_Qz=7bD+Xi#c0Y_8?l^T;==C_c1}c^;;$t$aGwbfK=$-H#tX{<1AQmA4y~KR=w(1=SI2cWus}vqC?! zZF2JSjED^%?_S<2C{lAUG*e@v2>SSBXXm=ZoVi+yUT=Svm0{>Bk@)m&t-rZSc3cdjH4zy<0sMgV0x8@~wEWD~}ttjwwE>4|&nd=U3-s?`L+r zn|pixiCQ0ZQ@g6_Z#V4g)+z4nkE;oM_c-S5jg5=dzE_?OTG=aYo|c@Pyz_Kbp#A@& z`kTG${v>?+9{xT?VMqL~e;3~zPwt9QtIogr3!-%$;U0Gr)TUZ}8QdcPtxsut;y3SqYtF9|5APh`d245JdfA&B z8}Fp9yfRHUdeh34EAOO+Ub}aH|D@RJ_r}~$!b(q`^-tYj`{?Mc$KX}b`bIs$pe6gD zz06n775<9t$dkTUT3hwCcUAPQ+}}q&&z7FF`hRpFsO!GUUan@{z9kZM?_xT3Da8C< zEndX>{nWk9_)NK zJ3VfV{!2bzmWL|gYj=TG^2J}8ceJMH%9cIxw?AEB+Ie*amsIS-E4z!ox^H7U2;OIn zv~`Lbq{&tNAFyR;6G8( z#ia*lPL7G~SjK%R$$&$C+3bI>pS?M_cm38mN7r2TluK%Y48fzgdMzuo8FAG&dtDn} z@ubz4E+lRA_i}?4=TB#$W_2!Vge;U92=eA0*&03II8W(YfHdz;Q z!@OP3<9*idRF}JPZ$iw~-R`h%jt0}I%*FFA5T76cV(CmHHYxb$=Y0j1DpZ6npZFZCX&R6@Oaht(@k(GhFBQMF~ zCym-4Qs7T1VyS7WjuTM(uO#ac()$5|lWjSDHCd4vqXn3L4rt7pk4DK{p*Ubj+z zWnbM~na_GwlfOLcU4`z4l3j0wqSbc_#q}5C`lUe8JQD+;Z*`4Lm=x%JDhCo47ihRq ziqG}k|Fy0*(-6&jZZmVQZ%CA~JL9>oO>}jZ#zvc}!lEm#>EXGyS5$VsnqU2_x#)H$ zBzb1AU(`JgT|kjjQ?sYe-}vn`=bBX~=Il>?uCS{!Fzt3fxYfD!Rh_}#s9Mkb6~CUW z=svga=jG+IRX`cdYSt-nNFjTHZ_(`YFJ5G9OFKL3m#)ip@s^p}nJ@Y>yO^LeRJbV!rBnoV($t=`k(x@|GR!ox)*fy zWT5!mpG~W~&wbllvTNTKV`#Q~tFY^=#l=X_$`CgvOU3y;g*JT+B2`@4SZE|c5) z>+3In?wZyOI!|DA*xFNR>FLG!{{H^yr>E=Z?^&-O{jf?mdYg`Q?XSs?%V&PqtIGWz z^M9|Jy1MjhQ*-m_bF1I&yt&MGw$yuTW8=*a?ACg3e=T9>zx2%Q#G84F8_T}8zrOU! zG$uwgh^yU~se}V{!2E+pXExuT9%|{oD2BcdK7-z5Dp|o-*x=SBw9?J+a&W$9v5!eeX(dtzFDv zobNvKZP{(jKVK)t%qu=;>HAv$)S7hdJD(5l@73S%uWC!w@jay%KN){YH$t$X;1bLnZ% zcKwz~B3pC3oNnpnw?EnBk??8Bn!DA%H{E)*^|o#Jd*zk9+qL)jzuu8_v};-J+Z0*X z8I{(j_AKk4zc}0V>wImi+Ore(Ja4~!_0{`_>yz(gmu~X^%H?8i_-R|L?sosWho5;* zhMvx|?<_5{`S@+k&qbR-%f!I#kR>X+zWeXHSF-0->dSEb`I{EKZdrf#Q`weDD{Txv z-PeD=_wV)WueaXLo4uX4^1EeWiT(cFMQbO%z2_dHxU0&yzV?sI-_o+RcT?jwi&mb` zSKsCOddBytxo?8^GF|lT*^?U{AEUl=ubh`ozl6H`UlxhDl4X-UbYLlOiOMd`z_60c zi+@}8lzz$lZl|>EMceJzEq(vKO^G}o_QZDo)EC>Lj_NmfLol-|hpK zXuZ2GYMvn9r`!vo#ESQ++V-y?0TEBda~WFht=XIBUfJ+ ztCJ78eoE5Le9GDncH8;h**byZ`g4~1?(00NLAPhB|M~YC;(++upOWB*V?y|L96!g>E>hsioeb1b!wYh6QZ_B+cbzWC{*P*rT(eCxO*0INx62EDFo^|TR z+S6^k(wjhQf)r=GtD7o((P;h6u07AU3&jR*>3g^JkD$V#S}j(AyFwZ27vv4{kAEw# z3H@>KmB{wD(0cf{>68e$yK`i^^{g)ch1L9^+QDP8O{Slq%XZdx(GRDF$3^~5Zd>$v z+V0<9bv6E|-O|jv-QRof+JuzC}xyRk?9asRs9|BnwohmAd5GI{aj$%=j3AG)T$S{!YPC|*4T_glNhO?`Pw zV0PSk;YGJ)nN@csE`Pn~c)dh?#V`?d9a`s4jSTrYnT$vWOAyEVU{ z@^nb~-QxVRll}5J)9z%~PyhR+FaD|BzAmKwm08mhxj@GrIaiCOcD!J6kStll(J$}X z`XcPYqSe#5&YpgI3beZ})_V4|r9sx^@6PneT2I^k=EKF8Zzj#2{rhoT)$4n2UaUS| zEFT|=vLMS~D&!EQE+dx`#sxf9VJ*k|7de$MH?CdOzi#rKkT#*yi(foHx2OA@$()F( z;&Vk0`X2-BK9jt)@HG=d#ET~pjW5_1N%vh9@5(p#DC&BnaM zoqKm66)KSWFgl=wb+P`QwQM`zwOvuM@Bi>b;@tmr-RDe>=G#M8&uwXb!PLO&Vs6Oh zS#nkNVOy^IES2zBalbux>&m9B!cgQ|a=+&H-dktV_C3jZws}>W)oT&7DmYnS+uwcc z^5^%J92WMSbbCEYrhr%#bpM+=-+#s5Z}`u1t$Xz_{oV?FUv@NOl%M9FpZ)U9hmXcz zmuA9Bby%}z$*#ArWMS9?fSTkNq5%&|FQAS+;aOB_Da66 z;znT?LjvPP-<~PkVwE*&eqUquowWMlkutlsgb37zSJ2IG!SB!M&N%n|n|u69U1!J| z6;PNR*Z?Z$xo+2eIWj*Bw5|Jf{~Dy0OG6_2Mc-!}M_lY68UUWO~|Vla54u&eXp>D=jOys~OfPW|Fl7mXBp5NE~ZRaf(!n>SBQeS9w)+^msVRdqL! z^`dW2Tu$}94~e@3*6P6X_1%XlB_$<|I}~@#H?Xvv32hX-$#9x$W3PW(N#L%DLy7R> zSGoGeH4&ht>?s+bU9P(hb1sVRak;+riIu?Jhin&pd$hL4PPY58c8BZX_N~nD;JGac zYTIplzleMD-R!XMTfWT8e|sx3zT1DohL_LF_y2qM{@*$^hkv!o%F0`hKkre($*F^*eWB%HHjKo;KSo zch%kZ+vkbdx<6SvfA`xX=WN&C7K`*;Rn0rKy6^v=MWsB~9iMK}J!Jd5&-&d93-|W@ z=icp1joaR_XaCwyHkF@Jl7D}STOF$R_S;l&YasOYJoB0xo!igXEq)8_13O!HyOs>Sk2^!mOQ=X#j7&yi&MY+IJD?`Nz=;j@w>}%AMcEMHg9#R(7XV* zXo!-Wg~EzSL2H|ccoIm_jm?ib&cMn9bQ`k&pqZTjmU zR&9woU-sLJOJP^1VD)X&rbE29`%FCFZO^y8Q1oL%8MHTf%dx~dIqi1sPN7@fRZpkQ zex1EBpuwr~^yH0xDHl$5-`}Ni^W)Q7$K&N~{T4}Y^J#0jEwFC>?zP9)Mr>U4vi5N4 z%aq$zFLzB)pH+ENY~u9fd%vv@eZQQ$bx~Vn*{@x8mFIc4d#=Cg74rA%o09mK>>YVY zNA7&fYv<2qhi8d$gG}|R(9U)G8>;xf8*e+6|5jJ%?xN=%1%L14s#pK}9CCL{T36Yk z+gY-%aob#{2wp0C|2%*HYpsj?7j>h~8~yg$?^q(Nd~I9yj}4%8Lux1g-t1fab*UL5 z*Ef4=)TgHCw7sy}$>(|^|DpDzIbr7o*SeVJgz|Q(?s|JI@QBD=qxa806u;g2VjCjV zl5a1+Uv=LECAl_t9$7p!$1|oY<=n#C|C0aQ_KQ1IH{G>q+4EhuQ(dpu_wANlH$QK8 zX=dre@E>==GHkmxWp3|N(!Z^Ai!*cP_u?Pxuf8jdp7{NapX>E_|L;|Pp!J&%X+i6O^tu0^vY?&kfD-Q8#E zxh@q|vG9J@Ad8z0B@3@B?3&yBqHM=o*Td~|e?D0JOZgG>WV{Uai?Tj*Vs^}*6uMC* zoM+LCU3$WGYIR4?9KYEAJ;#0hbJ6DAOXdqSXn`}=G3eVL)Be_h~<6=z7e@e;d|#~iW9^Q=Qdk0g^5n_B$uFW*!!1r;{g60|QP&lbVo)PzjER+L)_eu zE%7#dIVZICYeYH^6588`93Tcdivra61rLpY&R;@Q=inv~Ly2KF0k>ZbBzV}(bI}x@n zCh~V>HS`R=*Zmp)UPg;<Ua?b48r_b4bzms!$nQ!m+xie=*7R&v-hgs?+tqWGJm;#wd?xT-%HQus_v>eEx^~!5Ol5UvG(a zpEy0zI%?~SU!Z<^;dbjv`|Z4m@Ae(|_5R|!zn_gRCLfBaW`~lowxe?#4Bx* zZQnCx!`DPy6fnDZ(yA-Z_;b8-3)5SU;x}LZe%hv3(;F7q{{DX6|2L)`c}YmgZStfS za#s6n!=0)F{r8zf#djx7ESNM+UG-n_EnRWHva73}|NkTFeamI{*Aut>{%`Gy3*S9d`oHJ;>C@Z76?SzZC44`xMYFkFl`eXA z=dONgw{Ss;cJ9IpU1hH?`YCmT&D#;H^nM?tuf6E@nJyReJMRi)SBu>(!{*}V7kWoJ zU2d>n>YNz;uG>rfbgfh7$?7>QXZGvwdCRgXNB(YsZMVv%7o~r{7OdBm3_hLn<4(Y_ zL%;7>{aAa;?(MZek<{B=A52{)zIavlzij#Y>Dzg$c>lkDv_F0K_wx1OTk5|_xtOD+ zY^8}8Lf@V`@ORC-?zVzU&+4N1R2N?@>g?Ga|CGC5*zNB7C;B3{KWzIj|2cQS>aQBg z6J6J9|G6G!cY9lIwvD-e*5`M^dA4_~e!af=(n^f8*yD%Pq0V)`_eW3Xl{QOxKDRvX z@%#HPe?QH2*1jmZ_TK%y^X!FOchr9qLU#xwTenT&J9X43Y<|JSBi50Z4L&*y-tdi; zP&>3vDsk%DTNgO@*7>_^haCBZRCR%dSD8Ua(m>C(brEOKL5(;*8kbm*AzL=^p_-sG zb>Ty&NY0wOi0A$O|Nm}r_AJs^75XwFPz-(`7kD|wqzl%kItUhpk=;RhKtJU>k*GH5kuVsz3wrxCx8!{fK0W` zU3<4|(d(#+(;G@(U%TbDd)K>NuV>{2i?{}gxCUOm&TD=rVEg7*7k=}sdC~Rt_4RU< zU5d>c-ZpNti=kVk zYGnoQHXOa>KAnFuSOj#S*q^y?CoGh`c9Cz9^aHopf+Xp#d~=>d+g7XoKFn{w=DyeD zEA~F%&Jxm`VbJ-))6;an{trFw(7Eoz@rzsebk(*9FX@X~7o47xqw|vA27I(p3S#1< zuajA07x-N0REHAKY10WBj#Q!}uYf~SH*%ATw4~(9`^-TKh#$3SgN+xwj+1pz=Wo6sSs{j7_>JinxgZaXb9~FP)I#_=vr+XCD?!212 zWRdCHbINnlZw0G4pEF*YIUQu!y+yCf82B8@zT_5NT@~8PEVQMaIl$D%-#=YDY|R4K zP0WYRulJkxd|tJl%p zTDaiq^5R2cG2drC^Ny&7I^M@^PI^@E6vgL~F4LJOFz)x4PfB!OEBYrUhuQJ9T&?2= z|F-(i=Jn2fsu|^S4{a{b3zICIr|Ew4Z;s)-bb(kthd_r*Zxk}ieq9*~OnY^4wKRrDy{aeX$+j2;F+)6!Cxy<&h(G#1^C!!&# zr-E9k2%kAh2GHYzrRC+<-@hz>r%ESslS=f~tWYy+>*)AN{#>tZEiEInVi_5%STC|R zEKL6U>#ONSaBBR_2|XJ4)%Hh|x?XrmO^2^QFivFa&|}~+1`)DCclZCjy+2l=rrWh7 z+EaNEH$yikXuO86^BriYRM;Z^Rn5uM!Zz_cw$$9+ReE!tZS|(9+TqutI#kc8`_Efr z>%V=vUhJ&5_xJDr7W2KT{7#|!d!1i)&hoiP8Uw=xPH-6bmE8S5 z*X8G==Pj$}zW8<8+4IU{XTfjs@1vYmCoC-CuHPwa_y4D2rO=E0Z7;$~x{FRkuP?hT z^k{wgJ+mM0mp{#Vn>&@6!68y0mO;X*=3?ohWw-wd#Hx4tJ-Fll>7oDUN+-!j`{ygy z#MeuI>QLRacAM*q+CQmzdp~bej7k6h_3nK^h6U14&7ywmO4hvI`r?=FuC;ZO^p$s2 zu79|{onxt?%Cr~TZi(58KeV@dzti<~jG8b*Ll`)8S{A)d@zZVD_vB^ayKO8!YdgX& z@l!a zc{})29Ie54-`~KhkYnE;3%~Xm=1XTl`u6{1- HoD!MeVN literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/vs-x64/Parallel workload.xlsx.5M, 0.99.png b/doc/diagrams/benchmarks-concurrent_map/vs-x64/Parallel workload.xlsx.5M, 0.99.png new file mode 100644 index 0000000000000000000000000000000000000000..deb1f083ee03709a26ed8114c61a54c31b127e66 GIT binary patch literal 23242 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

_$E1y}*Pily{HpZMtFKorGfqrC=lS@zb?4E- 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 BH5&i` literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.5M, 0.01.png b/doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.5M, 0.01.png new file mode 100644 index 0000000000000000000000000000000000000000..9f01016f2395884071963930534b9db4dc271e85 GIT binary patch literal 27247 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 V6!e=plNVG4c)I$ztaD0e0sx&^#sUBU literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.5M, 0.5.png b/doc/diagrams/benchmarks-concurrent_map/gcc-x86/Parallel workload.xlsx.5M, 0.5.png new file mode 100644 index 0000000000000000000000000000000000000000..f956b2c7c6e7f29aef046638b2dcf6f20a793fa3 GIT binary patch literal 25134 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 KelF{r5}E*gDkA;> literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/vs-x64/Parallel workload.xlsx.500k, 0.5.png b/doc/diagrams/benchmarks-concurrent_map/vs-x64/Parallel workload.xlsx.500k, 0.5.png new file mode 100644 index 0000000000000000000000000000000000000000..88e1c64274a011359279c57f26b631b1245a4f88 GIT binary patch literal 27193 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 oM`726e1W^ac&5JC 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 Ar~m)} literal 0 HcmV?d00001 diff --git a/doc/diagrams/benchmarks-concurrent_map/vs-x64/Parallel workload.xlsx.500k, 0.99.png b/doc/diagrams/benchmarks-concurrent_map/vs-x64/Parallel workload.xlsx.500k, 0.99.png new file mode 100644 index 0000000000000000000000000000000000000000..0695aca2e7581227bcb0ec779b91717d7851f452 GIT binary patch literal 25995 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