gnss-sim/3rdparty/boost/unordered/detail/foa/concurrent_table.hpp

1793 lines
54 KiB
C++

/* Fast open-addressing concurrent hash table.
*
* Copyright 2023-2024 Joaquin M Lopez Munoz.
* Copyright 2024 Braden Ganetsky.
* 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 <atomic>
#include <boost/assert.hpp>
#include <boost/config.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/core/no_exceptions_support.hpp>
#include <boost/core/serialization.hpp>
#include <boost/cstdint.hpp>
#include <boost/mp11/tuple.hpp>
#include <boost/throw_exception.hpp>
#include <boost/unordered/detail/archive_constructed.hpp>
#include <boost/unordered/detail/bad_archive_exception.hpp>
#include <boost/unordered/detail/foa/core.hpp>
#include <boost/unordered/detail/foa/reentrancy_check.hpp>
#include <boost/unordered/detail/foa/rw_spinlock.hpp>
#include <boost/unordered/detail/foa/tuple_rotate_right.hpp>
#include <boost/unordered/detail/serialization_version.hpp>
#include <boost/unordered/detail/static_assert.hpp>
#include <boost/unordered/detail/type_traits.hpp>
#include <cstddef>
#include <functional>
#include <initializer_list>
#include <iterator>
#include <memory>
#include <new>
#include <type_traits>
#include <tuple>
#include <utility>
#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 <algorithm>
#include <execution>
#endif
namespace boost{
namespace unordered{
namespace detail{
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
template<typename ExecutionPolicy>
using is_execution_policy=std::is_execution_policy<
typename std::remove_cv<
typename std::remove_reference<ExecutionPolicy>::type
>::type
>;
#else
template<typename ExecutionPolicy>
using is_execution_policy=std::false_type;
#endif
namespace foa{
static constexpr std::size_t cacheline_size=64;
template<typename T,std::size_t N>
class cache_aligned_array
{
public:
cache_aligned_array(){for(std::size_t n=0;n<N;)::new (data(n++)) T();}
~cache_aligned_array(){for(auto n=N;n>0;)data(n--)->~T();}
cache_aligned_array(const cache_aligned_array&)=delete;
cache_aligned_array& operator=(const cache_aligned_array&)=delete;
T& operator[](std::size_t pos)noexcept{return *data(pos);}
private:
static constexpr std::size_t element_offset=
(sizeof(T)+cacheline_size-1)/cacheline_size*cacheline_size;
BOOST_UNORDERED_STATIC_ASSERT(alignof(T)<=cacheline_size);
T* data(std::size_t pos)noexcept
{
return reinterpret_cast<T*>(
(reinterpret_cast<uintptr_t>(&buf)+cacheline_size-1)/
cacheline_size*cacheline_size
+pos*element_offset);
}
unsigned char buf[element_offset*N+cacheline_size-1];
};
template<typename Mutex,std::size_t N>
class multimutex
{
public:
constexpr std::size_t size()const noexcept{return N;}
Mutex& operator[](std::size_t pos)noexcept
{
BOOST_ASSERT(pos<N);
return mutexes[pos];
}
void lock()noexcept{for(std::size_t n=0;n<N;)mutexes[n++].lock();}
void unlock()noexcept{for(auto n=N;n>0;)mutexes[--n].unlock();}
private:
cache_aligned_array<Mutex,N> mutexes;
};
/* std::shared_lock is C++14 */
template<typename Mutex>
class shared_lock
{
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;}
private:
Mutex &m;
bool owns=true;
};
/* VS in pre-C++17 mode can't implement RVO for std::lock_guard due to
* its copy constructor being deleted.
*/
template<typename Mutex>
class lock_guard
{
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;
};
/* inspired by boost/multi_index/detail/scoped_bilock.hpp */
template<typename Mutex>
class scoped_bilock
{
public:
scoped_bilock(Mutex& m1,Mutex& m2)noexcept
{
bool mutex_lt=std::less<Mutex*>{}(&m1,&m2);
pm1=mutex_lt?&m1:&m2;
pm1->lock();
if(&m1==&m2){
pm2=nullptr;
}
else{
pm2=mutex_lt?&m2:&m1;
pm2->lock();
}
}
/* 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();
pm1->unlock();
}
private:
Mutex *pm1,*pm2;
};
/* use atomics for group metadata storage */
template<typename Integral>
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);}
atomic_integral& operator=(atomic_integral const& rhs) {
n.store(rhs.n.load(std::memory_order_relaxed),std::memory_order_relaxed);
return *this;
}
std::atomic<Integral> 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
{
using mutex_type=rw_spinlock;
using shared_lock_guard=shared_lock<mutex_type>;
using exclusive_lock_guard=lock_guard<mutex_type>;
using insert_counter_type=std::atomic<boost::uint32_t>;
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;
insert_counter_type cnt{0};
};
template<std::size_t Size>
group_access* dummy_group_accesses()
{
/* 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 accesses[Size];
return accesses;
}
/* subclasses table_arrays to add an additional group_access array */
template<typename Value,typename Group,typename SizePolicy,typename Allocator>
struct concurrent_table_arrays:table_arrays<Value,Group,SizePolicy,Allocator>
{
using group_access_allocator_type=
typename boost::allocator_rebind<Allocator,group_access>::type;
using group_access_pointer=
typename boost::allocator_pointer<group_access_allocator_type>::type;
using super=table_arrays<Value,Group,SizePolicy,Allocator>;
using allocator_type=typename super::allocator_type;
concurrent_table_arrays(const super& arrays,group_access_pointer pga):
super{arrays},group_accesses_{pga}{}
group_access* group_accesses()const noexcept{
return boost::to_address(group_accesses_);
}
static concurrent_table_arrays new_(allocator_type al,std::size_t n)
{
super x{super::new_(al,n)};
BOOST_TRY{
return new_group_access(group_access_allocator_type(al),x);
}
BOOST_CATCH(...){
super::delete_(al,x);
BOOST_RETHROW
}
BOOST_CATCH_END
}
static void set_group_access(
group_access_allocator_type al,concurrent_table_arrays& arrays)
{
set_group_access(
al,arrays,std::is_same<group_access*,group_access_pointer>{});
}
static void set_group_access(
group_access_allocator_type al,
concurrent_table_arrays& arrays,
std::false_type /* fancy pointers */)
{
arrays.group_accesses_=
boost::allocator_allocate(al,arrays.groups_size_mask+1);
for(std::size_t i=0;i<arrays.groups_size_mask+1;++i){
::new (arrays.group_accesses()+i) group_access();
}
}
static void set_group_access(
group_access_allocator_type al,
concurrent_table_arrays& arrays,
std::true_type /* optimize when elements() is null */)
{
if(!arrays.elements()){
arrays.group_accesses_=
dummy_group_accesses<SizePolicy::min_size()>();
} else {
set_group_access(al,arrays,std::false_type{});
}
}
static concurrent_table_arrays new_group_access(
group_access_allocator_type al,const super& x)
{
concurrent_table_arrays arrays{x,nullptr};
set_group_access(al,arrays);
return arrays;
}
static void delete_(allocator_type al,concurrent_table_arrays& arrays)noexcept
{
delete_group_access(group_access_allocator_type(al),arrays);
super::delete_(al,arrays);
}
static void delete_group_access(
group_access_allocator_type al,concurrent_table_arrays& arrays)noexcept
{
if(arrays.elements()){
boost::allocator_deallocate(
al,arrays.group_accesses_,arrays.groups_size_mask+1);
}
}
group_access_pointer group_accesses_;
};
struct atomic_size_control
{
static constexpr auto atomic_size_t_size=sizeof(std::atomic<std::size_t>);
BOOST_UNORDERED_STATIC_ASSERT(atomic_size_t_size<cacheline_size);
atomic_size_control(std::size_t ml_,std::size_t size_):
pad0_{},ml{ml_},pad1_{},size{size_}{}
atomic_size_control(const atomic_size_control& x):
pad0_{},ml{x.ml.load()},pad1_{},size{x.size.load()}{}
/* padding to avoid false sharing internally and with sorrounding data */
unsigned char pad0_[cacheline_size-atomic_size_t_size];
std::atomic<std::size_t> ml;
unsigned char pad1_[cacheline_size-atomic_size_t_size];
std::atomic<std::size_t> size;
};
/* std::swap can't be used on non-assignable atomics */
inline void
swap_atomic_size_t(std::atomic<std::size_t>& x,std::atomic<std::size_t>& y)
{
std::size_t tmp=x;
x=static_cast<std::size_t>(y);
y=tmp;
}
inline void swap(atomic_size_control& x,atomic_size_control& y)
{
swap_atomic_size_t(x.ml,y.ml);
swap_atomic_size_t(x.size,y.size);
}
/* foa::concurrent_table serves as the foundation for end-user concurrent
* hash containers.
*
* 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 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
* 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.
* - Parallel versions of [c]visit_all(f) and erase_if(f) are provided based
* on C++17 stdlib parallel algorithms.
*
* Consult boost::concurrent_flat_(map|set) 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:
*
* - 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 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 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.
*/
template<typename,typename,typename,typename>
class table; /* concurrent/non-concurrent interop */
template <typename TypePolicy,typename Hash,typename Pred,typename Allocator>
using concurrent_table_core_impl=table_core<
TypePolicy,group15<atomic_integral>,concurrent_table_arrays,
atomic_size_control,Hash,Pred,Allocator>;
#include <boost/unordered/detail/foa/ignore_wshadow.hpp>
#if defined(BOOST_MSVC)
#pragma warning(push)
#pragma warning(disable:4714) /* marked as __forceinline not inlined */
#endif
template<typename TypePolicy,typename Hash,typename Pred,typename Allocator>
class concurrent_table:
concurrent_table_core_impl<TypePolicy,Hash,Pred,Allocator>
{
using super=concurrent_table_core_impl<TypePolicy,Hash,Pred,Allocator>;
using type_policy=typename super::type_policy;
using group_type=typename super::group_type;
using super::N;
using prober=typename super::prober;
using arrays_type=typename super::arrays_type;
using size_ctrl_type=typename super::size_ctrl_type;
using compatible_nonconcurrent_table=table<TypePolicy,Hash,Pred,Allocator>;
friend compatible_nonconcurrent_table;
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;
static constexpr std::size_t bulk_visit_size=16;
#if defined(BOOST_UNORDERED_ENABLE_STATS)
using stats=typename super::stats;
#endif
private:
template<typename Value,typename T>
using enable_if_is_value_type=typename std::enable_if<
!std::is_same<init_type,value_type>::value&&
std::is_same<Value,value_type>::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()):
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()){}
template<typename ArraysType>
concurrent_table(
compatible_nonconcurrent_table&& x,
arrays_holder<ArraysType,Allocator>&& ah):
super{
std::move(x.h()),std::move(x.pred()),std::move(x.al()),
[&x]{return arrays_type::new_group_access(
x.al(),typename arrays_type::super{
x.arrays.groups_size_index,x.arrays.groups_size_mask,
to_pointer<typename arrays_type::group_type_pointer>(
reinterpret_cast<group_type*>(x.arrays.groups())),
x.arrays.elements_});},
size_ctrl_type{x.size_ctrl.ml,x.size_ctrl.size}}
{
x.arrays=ah.release();
x.size_ctrl.ml=x.initial_max_load();
x.size_ctrl.size=0;
BOOST_UNORDERED_SWAP_STATS(this->cstats,x.cstats);
}
concurrent_table(compatible_nonconcurrent_table&& x):
concurrent_table(std::move(x),x.make_empty_arrays())
{}
~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)noexcept(
noexcept(std::declval<super&>() = std::declval<super&&>()))
{
auto lck=exclusive_access(*this,x);
super::operator=(std::move(x));
return *this;
}
concurrent_table& operator=(std::initializer_list<value_type> il) {
auto lck=exclusive_access();
super::clear();
super::noshrink_reserve(il.size());
for (auto const& v : il) {
this->unprotected_emplace(v);
}
return *this;
}
allocator_type get_allocator()const noexcept
{
auto lck=shared_access();
return super::get_allocator();
}
template<typename Key,typename F>
BOOST_FORCEINLINE std::size_t visit(const Key& x,F&& f)
{
return visit_impl(group_exclusive{},x,std::forward<F>(f));
}
template<typename Key,typename F>
BOOST_FORCEINLINE std::size_t visit(const Key& x,F&& f)const
{
return visit_impl(group_shared{},x,std::forward<F>(f));
}
template<typename Key,typename F>
BOOST_FORCEINLINE std::size_t cvisit(const Key& x,F&& f)const
{
return visit(x,std::forward<F>(f));
}
template<typename FwdIterator,typename F>
BOOST_FORCEINLINE
std::size_t visit(FwdIterator first,FwdIterator last,F&& f)
{
return bulk_visit_impl(group_exclusive{},first,last,std::forward<F>(f));
}
template<typename FwdIterator,typename F>
BOOST_FORCEINLINE
std::size_t visit(FwdIterator first,FwdIterator last,F&& f)const
{
return bulk_visit_impl(group_shared{},first,last,std::forward<F>(f));
}
template<typename FwdIterator,typename F>
BOOST_FORCEINLINE
std::size_t cvisit(FwdIterator first,FwdIterator last,F&& f)const
{
return visit(first,last,std::forward<F>(f));
}
template<typename F> std::size_t visit_all(F&& f)
{
return visit_all_impl(group_exclusive{},std::forward<F>(f));
}
template<typename F> std::size_t visit_all(F&& f)const
{
return visit_all_impl(group_shared{},std::forward<F>(f));
}
template<typename F> std::size_t cvisit_all(F&& f)const
{
return visit_all(std::forward<F>(f));
}
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
template<typename ExecutionPolicy,typename F>
void visit_all(ExecutionPolicy&& policy,F&& f)
{
visit_all_impl(
group_exclusive{},
std::forward<ExecutionPolicy>(policy),std::forward<F>(f));
}
template<typename ExecutionPolicy,typename F>
void visit_all(ExecutionPolicy&& policy,F&& f)const
{
visit_all_impl(
group_shared{},
std::forward<ExecutionPolicy>(policy),std::forward<F>(f));
}
template<typename ExecutionPolicy,typename F>
void cvisit_all(ExecutionPolicy&& policy,F&& f)const
{
visit_all(std::forward<ExecutionPolicy>(policy),std::forward<F>(f));
}
#endif
template<typename F> bool visit_while(F&& f)
{
return visit_while_impl(group_exclusive{},std::forward<F>(f));
}
template<typename F> bool visit_while(F&& f)const
{
return visit_while_impl(group_shared{},std::forward<F>(f));
}
template<typename F> bool cvisit_while(F&& f)const
{
return visit_while(std::forward<F>(f));
}
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
template<typename ExecutionPolicy,typename F>
bool visit_while(ExecutionPolicy&& policy,F&& f)
{
return visit_while_impl(
group_exclusive{},
std::forward<ExecutionPolicy>(policy),std::forward<F>(f));
}
template<typename ExecutionPolicy,typename F>
bool visit_while(ExecutionPolicy&& policy,F&& f)const
{
return visit_while_impl(
group_shared{},
std::forward<ExecutionPolicy>(policy),std::forward<F>(f));
}
template<typename ExecutionPolicy,typename F>
bool cvisit_while(ExecutionPolicy&& policy,F&& f)const
{
return visit_while(
std::forward<ExecutionPolicy>(policy),std::forward<F>(f));
}
#endif
bool empty()const noexcept{return size()==0;}
std::size_t size()const noexcept
{
auto lck=shared_access();
return unprotected_size();
}
using super::max_size;
template<typename... Args>
BOOST_FORCEINLINE bool emplace(Args&&... args)
{
return construct_and_emplace(std::forward<Args>(args)...);
}
/* Optimization for value_type and init_type, to avoid constructing twice */
template<typename Value>
BOOST_FORCEINLINE auto emplace(Value&& x)->typename std::enable_if<
detail::is_similar_to_any<Value,value_type,init_type>::value,bool>::type
{
return emplace_impl(std::forward<Value>(x));
}
/* Optimizations for maps for (k,v) to avoid eagerly constructing value */
template <typename K, typename V>
BOOST_FORCEINLINE auto emplace(K&& k, V&& v) ->
typename std::enable_if<is_emplace_kv_able<concurrent_table, K>::value,
bool>::type
{
alloc_cted_or_fwded_key_type<type_policy, Allocator, K&&> x(
this->al(), std::forward<K>(k));
return emplace_impl(
try_emplace_args_t{}, x.move_or_fwd(), std::forward<V>(v));
}
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<typename=void> tilts call ambiguities in favor of init_type */
template<typename=void>
BOOST_FORCEINLINE bool
insert(const value_type& x){return emplace_impl(x);}
template<typename=void>
BOOST_FORCEINLINE bool
insert(value_type&& x){return emplace_impl(std::move(x));}
template<typename Key,typename... Args>
BOOST_FORCEINLINE bool try_emplace(Key&& x,Args&&... args)
{
return emplace_impl(
try_emplace_args_t{},std::forward<Key>(x),std::forward<Args>(args)...);
}
template<typename Key,typename... Args>
BOOST_FORCEINLINE bool try_emplace_or_visit(Key&& x,Args&&... args)
{
return emplace_or_visit_flast(
group_exclusive{},
try_emplace_args_t{},std::forward<Key>(x),std::forward<Args>(args)...);
}
template<typename Key,typename... Args>
BOOST_FORCEINLINE bool try_emplace_or_cvisit(Key&& x,Args&&... args)
{
return emplace_or_visit_flast(
group_shared{},
try_emplace_args_t{},std::forward<Key>(x),std::forward<Args>(args)...);
}
template<typename... Args>
BOOST_FORCEINLINE bool emplace_or_visit(Args&&... args)
{
return construct_and_emplace_or_visit_flast(
group_exclusive{},std::forward<Args>(args)...);
}
template<typename... Args>
BOOST_FORCEINLINE bool emplace_or_cvisit(Args&&... args)
{
return construct_and_emplace_or_visit_flast(
group_shared{},std::forward<Args>(args)...);
}
template<typename F>
BOOST_FORCEINLINE bool insert_or_visit(const init_type& x,F&& f)
{
return emplace_or_visit_impl(group_exclusive{},std::forward<F>(f),x);
}
template<typename F>
BOOST_FORCEINLINE bool insert_or_cvisit(const init_type& x,F&& f)
{
return emplace_or_visit_impl(group_shared{},std::forward<F>(f),x);
}
template<typename F>
BOOST_FORCEINLINE bool insert_or_visit(init_type&& x,F&& f)
{
return emplace_or_visit_impl(
group_exclusive{},std::forward<F>(f),std::move(x));
}
template<typename F>
BOOST_FORCEINLINE bool insert_or_cvisit(init_type&& x,F&& f)
{
return emplace_or_visit_impl(
group_shared{},std::forward<F>(f),std::move(x));
}
/* SFINAE tilts call ambiguities in favor of init_type */
template<typename Value,typename F>
BOOST_FORCEINLINE auto insert_or_visit(const Value& x,F&& f)
->enable_if_is_value_type<Value,bool>
{
return emplace_or_visit_impl(group_exclusive{},std::forward<F>(f),x);
}
template<typename Value,typename F>
BOOST_FORCEINLINE auto insert_or_cvisit(const Value& x,F&& f)
->enable_if_is_value_type<Value,bool>
{
return emplace_or_visit_impl(group_shared{},std::forward<F>(f),x);
}
template<typename Value,typename F>
BOOST_FORCEINLINE auto insert_or_visit(Value&& x,F&& f)
->enable_if_is_value_type<Value,bool>
{
return emplace_or_visit_impl(
group_exclusive{},std::forward<F>(f),std::move(x));
}
template<typename Value,typename F>
BOOST_FORCEINLINE auto insert_or_cvisit(Value&& x,F&& f)
->enable_if_is_value_type<Value,bool>
{
return emplace_or_visit_impl(
group_shared{},std::forward<F>(f),std::move(x));
}
template<typename Key>
BOOST_FORCEINLINE std::size_t erase(const Key& x)
{
return erase_if(x,[](const value_type&){return true;});
}
template<typename Key,typename F>
BOOST_FORCEINLINE auto erase_if(const Key& x,F&& f)->typename std::enable_if<
!is_execution_policy<Key>::value,std::size_t>::type
{
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<typename F>
std::size_t erase_if(F&& f)
{
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 res;
}
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
template<typename ExecutionPolicy,typename F>
auto erase_if(ExecutionPolicy&& policy,F&& f)->typename std::enable_if<
is_execution_policy<ExecutionPolicy>::value,void>::type
{
auto lck=shared_access();
for_all_elements(
group_exclusive{},std::forward<ExecutionPolicy>(policy),
[&,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);
}
});
}
#endif
void swap(concurrent_table& x)
noexcept(noexcept(std::declval<super&>().swap(std::declval<super&>())))
{
auto lck=exclusive_access(*this,x);
super::swap(x);
}
void clear()noexcept
{
auto lck=exclusive_access();
super::clear();
}
// TODO: should we accept different allocator too?
template<typename Hash2,typename Pred2>
size_type merge(concurrent_table<TypePolicy,Hash2,Pred2,Allocator>& x)
{
using merge_table_type=concurrent_table<TypePolicy,Hash2,Pred2,Allocator>;
using super2=typename merge_table_type::super;
// for clang
boost::ignore_unused<super2>();
auto lck=exclusive_access(*this,x);
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{super::size()-s};
}
template<typename Hash2,typename Pred2>
void merge(concurrent_table<TypePolicy,Hash2,Pred2,Allocator>&& x){merge(x);}
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<typename Key>
BOOST_FORCEINLINE std::size_t count(Key&& x)const
{
return (std::size_t)contains(std::forward<Key>(x));
}
template<typename Key>
BOOST_FORCEINLINE bool contains(Key&& x)const
{
return visit(std::forward<Key>(x),[](const value_type&){})!=0;
}
std::size_t capacity()const noexcept
{
auto lck=shared_access();
return super::capacity();
}
float load_factor()const noexcept
{
auto lck=shared_access();
if(super::capacity()==0)return 0;
else return float(unprotected_size())/
float(super::capacity());
}
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);
}
#if defined(BOOST_UNORDERED_ENABLE_STATS)
/* already thread safe */
using super::get_stats;
using super::reset_stats;
#endif
template<typename Predicate>
friend std::size_t erase_if(concurrent_table& x,Predicate&& pr)
{
return x.erase_if(std::forward<Predicate>(pr));
}
friend bool operator==(const concurrent_table& x,const concurrent_table& y)
{
auto lck=exclusive_access(x,y);
return static_cast<const super&>(x)==static_cast<const super&>(y);
}
friend bool operator!=(const concurrent_table& x,const concurrent_table& y)
{
return !(x==y);
}
private:
template<typename,typename,typename,typename> friend class concurrent_table;
using mutex_type=rw_spinlock;
using multimutex_type=multimutex<mutex_type,128>; // TODO: adapt 128 to the machine
using shared_lock_guard=reentrancy_checked<shared_lock<mutex_type>>;
using exclusive_lock_guard=reentrancy_checked<lock_guard<multimutex_type>>;
using exclusive_bilock_guard=
reentrancy_bichecked<scoped_bilock<multimutex_type>>;
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;
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_}{}
inline shared_lock_guard shared_access()const
{
thread_local auto id=(++thread_counter)%mutexes.size();
return shared_lock_guard{this,mutexes[id]};
}
inline exclusive_lock_guard exclusive_access()const
{
return exclusive_lock_guard{this,mutexes};
}
static inline exclusive_bilock_guard exclusive_access(
const concurrent_table& x,const concurrent_table& y)
{
return {&x,&y,x.mutexes,y.mutexes};
}
template<typename Hash2,typename Pred2>
static inline exclusive_bilock_guard exclusive_access(
const concurrent_table& x,
const concurrent_table<TypePolicy,Hash2,Pred2,Allocator>& y)
{
return {&x,&y,x.mutexes,y.mutexes};
}
/* Tag-dispatched shared/exclusive group access */
using group_shared=std::false_type;
using group_exclusive=std::true_type;
inline group_shared_lock_guard access(group_shared,std::size_t pos)const
{
return this->arrays.group_accesses()[pos].shared_access();
}
inline group_exclusive_lock_guard access(
group_exclusive,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();
}
/* 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 inline const value_type&
cast_for(group_shared,value_type& x){return x;}
static inline typename std::conditional<
std::is_same<key_type,value_type>::value,
const value_type&,
value_type&
>::type
cast_for(group_exclusive,value_type& x){return x;}
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<typename GroupAccessMode,typename Key,typename F>
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>(f));
}
template<typename GroupAccessMode,typename FwdIterator,typename F>
BOOST_FORCEINLINE
std::size_t bulk_visit_impl(
GroupAccessMode access_mode,FwdIterator first,FwdIterator last,F&& f)const
{
auto lck=shared_access();
std::size_t res=0;
auto n=static_cast<std::size_t>(std::distance(first,last));
while(n){
auto m=n<2*bulk_visit_size?n:bulk_visit_size;
res+=unprotected_bulk_visit(access_mode,first,m,std::forward<F>(f));
n-=m;
std::advance(
first,
static_cast<
typename std::iterator_traits<FwdIterator>::difference_type>(m));
}
return res;
}
template<typename GroupAccessMode,typename F>
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<typename GroupAccessMode,typename ExecutionPolicy,typename F>
void visit_all_impl(
GroupAccessMode access_mode,ExecutionPolicy&& policy,F&& f)const
{
auto lck=shared_access();
for_all_elements(
access_mode,std::forward<ExecutionPolicy>(policy),
[&](element_type* p){
f(cast_for(access_mode,type_policy::value_from(*p)));
});
}
#endif
template<typename GroupAccessMode,typename F>
bool visit_while_impl(GroupAccessMode access_mode,F&& f)const
{
auto lck=shared_access();
return for_all_elements_while(access_mode,[&](element_type* p){
return f(cast_for(access_mode,type_policy::value_from(*p)));
});
}
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
template<typename GroupAccessMode,typename ExecutionPolicy,typename F>
bool visit_while_impl(
GroupAccessMode access_mode,ExecutionPolicy&& policy,F&& f)const
{
auto lck=shared_access();
return for_all_elements_while(
access_mode,std::forward<ExecutionPolicy>(policy),
[&](element_type* p){
return f(cast_for(access_mode,type_policy::value_from(*p)));
});
}
#endif
template<typename GroupAccessMode,typename Key,typename F>
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(
access_mode,x,pos0,hash,
[&](group_type*,unsigned int,element_type* p)
{f(cast_for(access_mode,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<typename GroupAccessMode,typename Key,typename F>
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
{
BOOST_UNORDERED_STATS_COUNTER(num_cmps);
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;
BOOST_UNORDERED_PREFETCH_ELEMENTS(p,N);
auto lck=access(access_mode,pos);
do{
auto n=unchecked_countr_zero(mask);
if(BOOST_LIKELY(pg->is_occupied(n))){
BOOST_UNORDERED_INCREMENT_STATS_COUNTER(num_cmps);
if(BOOST_LIKELY(bool(this->pred()(x,this->key_from(p[n]))))){
f(pg,n,p+n);
BOOST_UNORDERED_ADD_STATS(
this->cstats.successful_lookup,(pb.length(),num_cmps));
return 1;
}
}
mask&=mask-1;
}while(mask);
}
if(BOOST_LIKELY(pg->is_not_overflowed(hash))){
BOOST_UNORDERED_ADD_STATS(
this->cstats.unsuccessful_lookup,(pb.length(),num_cmps));
return 0;
}
}
while(BOOST_LIKELY(pb.next(this->arrays.groups_size_mask)));
BOOST_UNORDERED_ADD_STATS(
this->cstats.unsuccessful_lookup,(pb.length(),num_cmps));
return 0;
}
template<typename GroupAccessMode,typename FwdIterator,typename F>
BOOST_FORCEINLINE std::size_t unprotected_bulk_visit(
GroupAccessMode access_mode,FwdIterator first,std::size_t m,F&& f)const
{
BOOST_ASSERT(m<2*bulk_visit_size);
std::size_t res=0,
hashes[2*bulk_visit_size-1],
positions[2*bulk_visit_size-1];
int masks[2*bulk_visit_size-1];
auto it=first;
for(auto i=m;i--;++it){
auto hash=hashes[i]=this->hash_for(*it);
auto pos=positions[i]=this->position_for(hash);
BOOST_UNORDERED_PREFETCH(this->arrays.groups()+pos);
}
for(auto i=m;i--;){
auto hash=hashes[i];
auto pos=positions[i];
auto mask=masks[i]=(this->arrays.groups()+pos)->match(hash);
if(mask){
BOOST_UNORDERED_PREFETCH(this->arrays.group_accesses()+pos);
BOOST_UNORDERED_PREFETCH(
this->arrays.elements()+pos*N+unchecked_countr_zero(mask));
}
}
it=first;
for(auto i=m;i--;++it){
BOOST_UNORDERED_STATS_COUNTER(num_cmps);
auto pos=positions[i];
prober pb(pos);
auto pg=this->arrays.groups()+pos;
auto mask=masks[i];
element_type *p;
if(!mask)goto post_mask;
p=this->arrays.elements()+pos*N;
for(;;){
{
auto lck=access(access_mode,pos);
do{
auto n=unchecked_countr_zero(mask);
if(BOOST_LIKELY(pg->is_occupied(n))){
BOOST_UNORDERED_INCREMENT_STATS_COUNTER(num_cmps);
if(bool(this->pred()(*it,this->key_from(p[n])))){
f(cast_for(access_mode,type_policy::value_from(p[n])));
++res;
BOOST_UNORDERED_ADD_STATS(
this->cstats.successful_lookup,(pb.length(),num_cmps));
goto next_key;
}
}
mask&=mask-1;
}while(mask);
}
post_mask:
do{
if(BOOST_LIKELY(pg->is_not_overflowed(hashes[i]))||
BOOST_UNLIKELY(!pb.next(this->arrays.groups_size_mask))){
BOOST_UNORDERED_ADD_STATS(
this->cstats.unsuccessful_lookup,(pb.length(),num_cmps));
goto next_key;
}
pos=pb.get();
pg=this->arrays.groups()+pos;
mask=pg->match(hashes[i]);
}while(!mask);
p=this->arrays.elements()+pos*N;
BOOST_UNORDERED_PREFETCH_ELEMENTS(p,N);
}
next_key:;
}
return res;
}
#if defined(BOOST_MSVC)
#pragma warning(pop) /* C4800 */
#endif
std::size_t unprotected_size()const
{
std::size_t m=this->size_ctrl.ml;
std::size_t s=this->size_ctrl.size;
return s<=m?s:m;
}
template<typename... Args>
BOOST_FORCEINLINE bool construct_and_emplace(Args&&... args)
{
return construct_and_emplace_or_visit(
group_shared{},[](const value_type&){},std::forward<Args>(args)...);
}
struct call_construct_and_emplace_or_visit
{
template<typename... Args>
BOOST_FORCEINLINE bool operator()(
concurrent_table* this_,Args&&... args)const
{
return this_->construct_and_emplace_or_visit(
std::forward<Args>(args)...);
}
};
template<typename GroupAccessMode,typename... Args>
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>(args)...))
)
);
}
template<typename GroupAccessMode,typename F,typename... Args>
BOOST_FORCEINLINE bool construct_and_emplace_or_visit(
GroupAccessMode access_mode,F&& f,Args&&... args)
{
auto lck=shared_access();
alloc_cted_insert_type<type_policy,Allocator,Args...> x(
this->al(),std::forward<Args>(args)...);
int res=unprotected_norehash_emplace_or_visit(
access_mode,std::forward<F>(f),type_policy::move(x.value()));
if(BOOST_LIKELY(res>=0))return res!=0;
lck.unlock();
rehash_if_full();
return noinline_emplace_or_visit(
access_mode,std::forward<F>(f),type_policy::move(x.value()));
}
template<typename... Args>
BOOST_FORCEINLINE bool emplace_impl(Args&&... args)
{
return emplace_or_visit_impl(
group_shared{},[](const value_type&){},std::forward<Args>(args)...);
}
template<typename GroupAccessMode,typename F,typename... Args>
BOOST_NOINLINE bool noinline_emplace_or_visit(
GroupAccessMode access_mode,F&& f,Args&&... args)
{
return emplace_or_visit_impl(
access_mode,std::forward<F>(f),std::forward<Args>(args)...);
}
struct call_emplace_or_visit_impl
{
template<typename... Args>
BOOST_FORCEINLINE bool operator()(
concurrent_table* this_,Args&&... args)const
{
return this_->emplace_or_visit_impl(std::forward<Args>(args)...);
}
};
template<typename GroupAccessMode,typename... Args>
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>(args)...))
)
);
}
template<typename GroupAccessMode,typename F,typename... Args>
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(
access_mode,std::forward<F>(f),std::forward<Args>(args)...);
if(BOOST_LIKELY(res>=0))return res!=0;
}
rehash_if_full();
}
}
template<typename... Args>
BOOST_FORCEINLINE bool unprotected_emplace(Args&&... args)
{
const auto &k=this->key_from(std::forward<Args>(args)...);
auto hash=this->hash_for(k);
auto pos0=this->position_for(hash);
if(this->find(k,pos0,hash))return false;
if(BOOST_LIKELY(this->size_ctrl.size<this->size_ctrl.ml)){
this->unchecked_emplace_at(pos0,hash,std::forward<Args>(args)...);
}
else{
this->unchecked_emplace_with_rehash(hash,std::forward<Args>(args)...);
}
return true;
}
struct reserve_size
{
reserve_size(concurrent_table& x_):x(x_)
{
size_=++x.size_ctrl.size;
}
~reserve_size()
{
if(!commit_)--x.size_ctrl.size;
}
bool succeeded()const{return size_<=x.size_ctrl.ml;}
void commit(){commit_=true;}
concurrent_table &x;
std::size_t size_;
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<typename GroupAccessMode,typename F,typename... Args>
BOOST_FORCEINLINE int
unprotected_norehash_emplace_or_visit(
GroupAccessMode access_mode,F&& f,Args&&... args)
{
const auto &k=this->key_from(std::forward<Args>(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(
access_mode,k,pos0,hash,std::forward<F>(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=access(group_exclusive{},pos);
auto mask=pg->match_available();
if(BOOST_LIKELY(mask!=0)){
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>(args)...);
rslot.commit();
rsize.commit();
BOOST_UNORDERED_ADD_STATS(this->cstats.insertion,(pb.length()));
return 1;
}
pg->mark_overflow(hash);
}
}
else return -1;
}
}
void rehash_if_full()
{
auto lck=exclusive_access();
if(this->size_ctrl.size==this->size_ctrl.ml){
this->unchecked_rehash_for_growth();
}
}
template<typename GroupAccessMode,typename F>
auto for_all_elements(GroupAccessMode access_mode,F f)const
->decltype(f(nullptr),void())
{
for_all_elements(
access_mode,[&](group_type*,unsigned int,element_type* p){f(p);});
}
template<typename GroupAccessMode,typename F>
auto for_all_elements(GroupAccessMode access_mode,F f)const
->decltype(f(nullptr,0,nullptr),void())
{
for_all_elements_while(
access_mode,[&](group_type* pg,unsigned int n,element_type* p)
{f(pg,n,p);return true;});
}
template<typename GroupAccessMode,typename F>
auto for_all_elements_while(GroupAccessMode access_mode,F f)const
->decltype(f(nullptr),bool())
{
return for_all_elements_while(
access_mode,[&](group_type*,unsigned int,element_type* p){return f(p);});
}
template<typename GroupAccessMode,typename F>
auto for_all_elements_while(GroupAccessMode access_mode,F f)const
->decltype(f(nullptr,0,nullptr),bool())
{
auto p=this->arrays.elements();
if(p){
for(auto pg=this->arrays.groups(),last=pg+this->arrays.groups_size_mask+1;
pg!=last;++pg,p+=N){
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);
if(!f(pg,n,p+n))return false;
mask&=mask-1;
}
}
}
return true;
}
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
template<typename GroupAccessMode,typename ExecutionPolicy,typename F>
auto for_all_elements(
GroupAccessMode access_mode,ExecutionPolicy&& policy,F f)const
->decltype(f(nullptr),void())
{
for_all_elements(
access_mode,std::forward<ExecutionPolicy>(policy),
[&](group_type*,unsigned int,element_type* p){f(p);});
}
template<typename GroupAccessMode,typename ExecutionPolicy,typename F>
auto for_all_elements(
GroupAccessMode access_mode,ExecutionPolicy&& policy,F f)const
->decltype(f(nullptr,0,nullptr),void())
{
if(!this->arrays.elements())return;
auto first=this->arrays.groups(),
last=first+this->arrays.groups_size_mask+1;
std::for_each(std::forward<ExecutionPolicy>(policy),first,last,
[&,this](group_type& g){
auto pos=static_cast<std::size_t>(&g-first);
auto p=this->arrays.elements()+pos*N;
auto lck=access(access_mode,pos);
auto mask=this->match_really_occupied(&g,last);
while(mask){
auto n=unchecked_countr_zero(mask);
f(&g,n,p+n);
mask&=mask-1;
}
}
);
}
template<typename GroupAccessMode,typename ExecutionPolicy,typename F>
bool for_all_elements_while(
GroupAccessMode access_mode,ExecutionPolicy&& policy,F f)const
{
if(!this->arrays.elements())return true;
auto first=this->arrays.groups(),
last=first+this->arrays.groups_size_mask+1;
return std::all_of(std::forward<ExecutionPolicy>(policy),first,last,
[&,this](group_type& g){
auto pos=static_cast<std::size_t>(&g-first);
auto p=this->arrays.elements()+pos*N;
auto lck=access(access_mode,pos);
auto mask=this->match_really_occupied(&g,last);
while(mask){
auto n=unchecked_countr_zero(mask);
if(!f(p+n))return false;
mask&=mask-1;
}
return true;
}
);
}
#endif
friend class boost::serialization::access;
template<typename Archive>
void serialize(Archive& ar,unsigned int version)
{
core::split_member(ar,*this,version);
}
template<typename Archive>
void save(Archive& ar,unsigned int version)const
{
save(
ar,version,
std::integral_constant<bool,std::is_same<key_type,value_type>::value>{});
}
template<typename Archive>
void save(Archive& ar,unsigned int,std::true_type /* set */)const
{
auto lck=exclusive_access();
const std::size_t s=super::size();
const serialization_version<value_type> value_version;
ar<<core::make_nvp("count",s);
ar<<core::make_nvp("value_version",value_version);
super::for_all_elements([&,this](element_type* p){
auto& x=type_policy::value_from(*p);
core::save_construct_data_adl(ar,std::addressof(x),value_version);
ar<<serialization::make_nvp("item",x);
});
}
template<typename Archive>
void save(Archive& ar,unsigned int,std::false_type /* map */)const
{
using raw_key_type=typename std::remove_const<key_type>::type;
using raw_mapped_type=typename std::remove_const<
typename TypePolicy::mapped_type>::type;
auto lck=exclusive_access();
const std::size_t s=super::size();
const serialization_version<raw_key_type> key_version;
const serialization_version<raw_mapped_type> mapped_version;
ar<<core::make_nvp("count",s);
ar<<core::make_nvp("key_version",key_version);
ar<<core::make_nvp("mapped_version",mapped_version);
super::for_all_elements([&,this](element_type* p){
/* To remain lib-independent from Boost.Serialization and not rely on
* the user having included the serialization code for std::pair
* (boost/serialization/utility.hpp), we serialize the key and the
* mapped value separately.
*/
auto& x=type_policy::value_from(*p);
core::save_construct_data_adl(
ar,std::addressof(x.first),key_version);
ar<<serialization::make_nvp("key",x.first);
core::save_construct_data_adl(
ar,std::addressof(x.second),mapped_version);
ar<<serialization::make_nvp("mapped",x.second);
});
}
template<typename Archive>
void load(Archive& ar,unsigned int version)
{
load(
ar,version,
std::integral_constant<bool,std::is_same<key_type,value_type>::value>{});
}
template<typename Archive>
void load(Archive& ar,unsigned int,std::true_type /* set */)
{
auto lck=exclusive_access();
std::size_t s;
serialization_version<value_type> value_version;
ar>>core::make_nvp("count",s);
ar>>core::make_nvp("value_version",value_version);
super::clear();
super::reserve(s);
for(std::size_t n=0;n<s;++n){
archive_constructed<value_type> value("item",ar,value_version);
auto& x=value.get();
auto hash=this->hash_for(x);
auto pos0=this->position_for(hash);
if(this->find(x,pos0,hash))throw_exception(bad_archive_exception());
auto loc=this->unchecked_emplace_at(pos0,hash,std::move(x));
ar.reset_object_address(std::addressof(*loc.p),std::addressof(x));
}
}
template<typename Archive>
void load(Archive& ar,unsigned int,std::false_type /* map */)
{
using raw_key_type=typename std::remove_const<key_type>::type;
using raw_mapped_type=typename std::remove_const<
typename TypePolicy::mapped_type>::type;
auto lck=exclusive_access();
std::size_t s;
serialization_version<raw_key_type> key_version;
serialization_version<raw_mapped_type> mapped_version;
ar>>core::make_nvp("count",s);
ar>>core::make_nvp("key_version",key_version);
ar>>core::make_nvp("mapped_version",mapped_version);
super::clear();
super::reserve(s);
for(std::size_t n=0;n<s;++n){
archive_constructed<raw_key_type> key("key",ar,key_version);
archive_constructed<raw_mapped_type> mapped("mapped",ar,mapped_version);
auto& k=key.get();
auto& m=mapped.get();
auto hash=this->hash_for(k);
auto pos0=this->position_for(hash);
if(this->find(k,pos0,hash))throw_exception(bad_archive_exception());
auto loc=this->unchecked_emplace_at(pos0,hash,std::move(k),std::move(m));
ar.reset_object_address(std::addressof(loc.p->first),std::addressof(k));
ar.reset_object_address(std::addressof(loc.p->second),std::addressof(m));
}
}
static std::atomic<std::size_t> thread_counter;
mutable multimutex_type mutexes;
};
template<typename T,typename H,typename P,typename A>
std::atomic<std::size_t> concurrent_table<T,H,P,A>::thread_counter={};
#if defined(BOOST_MSVC)
#pragma warning(pop) /* C4714 */
#endif
#include <boost/unordered/detail/foa/restore_wshadow.hpp>
} /* namespace foa */
} /* namespace detail */
} /* namespace unordered */
} /* namespace boost */
#endif