mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-23 04:03:04 +01:00
2803 lines
113 KiB
C++
2803 lines
113 KiB
C++
// Copyright (c) 2006-2018 Maxim Khizhinsky
|
|
//
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
#ifndef CDSLIB_INTRUSIVE_CUCKOO_SET_H
|
|
#define CDSLIB_INTRUSIVE_CUCKOO_SET_H
|
|
|
|
#include <memory>
|
|
#include <type_traits>
|
|
#include <mutex>
|
|
#include <functional> // ref
|
|
#include <cds/intrusive/details/base.h>
|
|
#include <cds/opt/compare.h>
|
|
#include <cds/opt/hash.h>
|
|
#include <cds/sync/lock_array.h>
|
|
#include <cds/os/thread.h>
|
|
#include <cds/sync/spinlock.h>
|
|
|
|
|
|
namespace cds { namespace intrusive {
|
|
|
|
/// CuckooSet-related definitions
|
|
namespace cuckoo {
|
|
/// Option to define probeset type
|
|
/**
|
|
The option specifies probeset type for the CuckooSet.
|
|
Available values:
|
|
- \p cds::intrusive::cuckoo::list - the probeset is a single-linked list.
|
|
The node contains pointer to next node in probeset.
|
|
- \p cds::intrusive::cuckoo::vector<Capacity> - the probeset is a vector
|
|
with constant-size \p Capacity where \p Capacity is an <tt>unsigned int</tt> constant.
|
|
The node does not contain any auxiliary data.
|
|
*/
|
|
template <typename Type>
|
|
struct probeset_type
|
|
{
|
|
//@cond
|
|
template <typename Base>
|
|
struct pack: public Base {
|
|
typedef Type probeset_type;
|
|
};
|
|
//@endcond
|
|
};
|
|
|
|
/// Option specifying whether to store hash values in the node
|
|
/**
|
|
This option reserves additional space in the hook to store the hash value of the object once it's introduced in the container.
|
|
When this option is used, the unordered container will store the calculated hash value in the hook and rehashing operations won't need
|
|
to recalculate the hash of the value. This option will improve the performance of unordered containers
|
|
when rehashing is frequent or hashing the value is a slow operation
|
|
|
|
The \p Count template parameter defines the size of hash array. Remember that cuckoo hashing implies at least two
|
|
hash values per item.
|
|
|
|
Possible values of \p Count:
|
|
- 0 - no hash storing in the node
|
|
- greater that 1 - store hash values.
|
|
|
|
Value 1 is deprecated.
|
|
*/
|
|
template <unsigned int Count>
|
|
struct store_hash
|
|
{
|
|
//@cond
|
|
template <typename Base>
|
|
struct pack: public Base {
|
|
static unsigned int const store_hash = Count;
|
|
};
|
|
//@endcond
|
|
};
|
|
|
|
|
|
//@cond
|
|
// Probeset type placeholders
|
|
struct list_probeset_class;
|
|
struct vector_probeset_class;
|
|
//@endcond
|
|
|
|
//@cond
|
|
/// List probeset type
|
|
struct list;
|
|
//@endcond
|
|
|
|
/// Vector probeset type
|
|
template <unsigned int Capacity>
|
|
struct vector
|
|
{
|
|
/// Vector capacity
|
|
static unsigned int const c_nCapacity = Capacity;
|
|
};
|
|
|
|
/// CuckooSet node
|
|
/**
|
|
Template arguments:
|
|
- \p ProbesetType - type of probeset. Can be \p cds::intrusive::cuckoo::list
|
|
or \p cds::intrusive::cuckoo::vector<Capacity>.
|
|
- \p StoreHashCount - constant that defines whether to store node hash values.
|
|
See cuckoo::store_hash option for explanation
|
|
- \p Tag - a \ref cds_intrusive_hook_tag "tag"
|
|
*/
|
|
template <typename ProbesetType = cuckoo::list, unsigned int StoreHashCount = 0, typename Tag = opt::none>
|
|
struct node
|
|
#ifdef CDS_DOXYGEN_INVOKED
|
|
{
|
|
typedef ProbesetType probeset_type ; ///< Probeset type
|
|
typedef Tag tag ; ///< Tag
|
|
static unsigned int const hash_array_size = StoreHashCount ; ///< The size of hash array
|
|
}
|
|
#endif
|
|
;
|
|
|
|
//@cond
|
|
template <typename Tag>
|
|
struct node< cuckoo::list, 0, Tag>
|
|
{
|
|
typedef list_probeset_class probeset_class;
|
|
typedef cuckoo::list probeset_type;
|
|
typedef Tag tag;
|
|
static unsigned int const hash_array_size = 0;
|
|
static unsigned int const probeset_size = 0;
|
|
|
|
node * m_pNext;
|
|
|
|
constexpr node() noexcept
|
|
: m_pNext( nullptr )
|
|
{}
|
|
|
|
void store_hash( size_t const* )
|
|
{}
|
|
|
|
size_t * get_hash() const
|
|
{
|
|
// This node type does not store hash values!!!
|
|
assert(false);
|
|
return nullptr;
|
|
}
|
|
|
|
void clear()
|
|
{
|
|
m_pNext = nullptr;
|
|
}
|
|
};
|
|
|
|
template <unsigned int StoreHashCount, typename Tag>
|
|
struct node< cuckoo::list, StoreHashCount, Tag>
|
|
{
|
|
typedef list_probeset_class probeset_class;
|
|
typedef cuckoo::list probeset_type;
|
|
typedef Tag tag;
|
|
static unsigned int const hash_array_size = StoreHashCount;
|
|
static unsigned int const probeset_size = 0;
|
|
|
|
node * m_pNext;
|
|
size_t m_arrHash[ hash_array_size ];
|
|
|
|
node() noexcept
|
|
: m_pNext( nullptr )
|
|
{
|
|
memset( m_arrHash, 0, sizeof(m_arrHash));
|
|
}
|
|
|
|
void store_hash( size_t const* pHashes )
|
|
{
|
|
memcpy( m_arrHash, pHashes, sizeof( m_arrHash ));
|
|
}
|
|
|
|
size_t * get_hash() const
|
|
{
|
|
return const_cast<size_t *>( m_arrHash );
|
|
}
|
|
|
|
void clear()
|
|
{
|
|
m_pNext = nullptr;
|
|
}
|
|
};
|
|
|
|
template <unsigned int VectorSize, typename Tag>
|
|
struct node< cuckoo::vector<VectorSize>, 0, Tag>
|
|
{
|
|
typedef vector_probeset_class probeset_class;
|
|
typedef cuckoo::vector<VectorSize> probeset_type;
|
|
typedef Tag tag;
|
|
static unsigned int const hash_array_size = 0;
|
|
static unsigned int const probeset_size = probeset_type::c_nCapacity;
|
|
|
|
node() noexcept
|
|
{}
|
|
|
|
void store_hash( size_t const* )
|
|
{}
|
|
|
|
size_t * get_hash() const
|
|
{
|
|
// This node type does not store hash values!!!
|
|
assert(false);
|
|
return nullptr;
|
|
}
|
|
|
|
void clear()
|
|
{}
|
|
};
|
|
|
|
template <unsigned int VectorSize, unsigned int StoreHashCount, typename Tag>
|
|
struct node< cuckoo::vector<VectorSize>, StoreHashCount, Tag>
|
|
{
|
|
typedef vector_probeset_class probeset_class;
|
|
typedef cuckoo::vector<VectorSize> probeset_type;
|
|
typedef Tag tag;
|
|
static unsigned int const hash_array_size = StoreHashCount;
|
|
static unsigned int const probeset_size = probeset_type::c_nCapacity;
|
|
|
|
size_t m_arrHash[ hash_array_size ];
|
|
|
|
node() noexcept
|
|
{
|
|
memset( m_arrHash, 0, sizeof(m_arrHash));
|
|
}
|
|
|
|
void store_hash( size_t const* pHashes )
|
|
{
|
|
memcpy( m_arrHash, pHashes, sizeof( m_arrHash ));
|
|
}
|
|
|
|
size_t * get_hash() const
|
|
{
|
|
return const_cast<size_t *>( m_arrHash );
|
|
}
|
|
|
|
void clear()
|
|
{}
|
|
};
|
|
//@endcond
|
|
|
|
|
|
//@cond
|
|
struct default_hook {
|
|
typedef cuckoo::list probeset_type;
|
|
static unsigned int const store_hash = 0;
|
|
typedef opt::none tag;
|
|
};
|
|
|
|
template < typename HookType, typename... Options>
|
|
struct hook
|
|
{
|
|
typedef typename opt::make_options< default_hook, Options...>::type traits;
|
|
|
|
typedef typename traits::probeset_type probeset_type;
|
|
typedef typename traits::tag tag;
|
|
static unsigned int const store_hash = traits::store_hash;
|
|
|
|
typedef node<probeset_type, store_hash, tag> node_type;
|
|
|
|
typedef HookType hook_type;
|
|
};
|
|
//@endcond
|
|
|
|
/// Base hook
|
|
/**
|
|
\p Options are:
|
|
- \p cuckoo::probeset_type - probeset type. Defaul is \p cuckoo::list
|
|
- \p cuckoo::store_hash - store hash values in the node or not. Default is 0 (no storing)
|
|
- \p opt::tag - a \ref cds_intrusive_hook_tag "tag"
|
|
*/
|
|
template < typename... Options >
|
|
struct base_hook: public hook< opt::base_hook_tag, Options... >
|
|
{};
|
|
|
|
/// Member hook
|
|
/**
|
|
\p MemberOffset defines offset in bytes of \ref node member into your structure.
|
|
Use \p offsetof macro to define \p MemberOffset
|
|
|
|
\p Options are:
|
|
- \p cuckoo::probeset_type - probeset type. Defaul is \p cuckoo::list
|
|
- \p cuckoo::store_hash - store hash values in the node or not. Default is 0 (no storing)
|
|
- \p opt::tag - a \ref cds_intrusive_hook_tag "tag"
|
|
*/
|
|
template < size_t MemberOffset, typename... Options >
|
|
struct member_hook: public hook< opt::member_hook_tag, Options... >
|
|
{
|
|
//@cond
|
|
static const size_t c_nMemberOffset = MemberOffset;
|
|
//@endcond
|
|
};
|
|
|
|
/// Traits hook
|
|
/**
|
|
\p NodeTraits defines type traits for node.
|
|
See \ref node_traits for \p NodeTraits interface description
|
|
|
|
\p Options are:
|
|
- \p cuckoo::probeset_type - probeset type. Defaul is \p cuckoo::list
|
|
- \p cuckoo::store_hash - store hash values in the node or not. Default is 0 (no storing)
|
|
- \p opt::tag - a \ref cds_intrusive_hook_tag "tag"
|
|
*/
|
|
template <typename NodeTraits, typename... Options >
|
|
struct traits_hook: public hook< opt::traits_hook_tag, Options... >
|
|
{
|
|
//@cond
|
|
typedef NodeTraits node_traits;
|
|
//@endcond
|
|
};
|
|
|
|
/// Internal statistics for \ref striping mutex policy
|
|
struct striping_stat {
|
|
typedef cds::atomicity::event_counter counter_type; ///< Counter type
|
|
|
|
counter_type m_nCellLockCount ; ///< Count of obtaining cell lock
|
|
counter_type m_nCellTryLockCount ; ///< Count of cell \p try_lock attempts
|
|
counter_type m_nFullLockCount ; ///< Count of obtaining full lock
|
|
counter_type m_nResizeLockCount ; ///< Count of obtaining resize lock
|
|
counter_type m_nResizeCount ; ///< Count of resize event
|
|
|
|
//@cond
|
|
void onCellLock() { ++m_nCellLockCount; }
|
|
void onCellTryLock() { ++m_nCellTryLockCount; }
|
|
void onFullLock() { ++m_nFullLockCount; }
|
|
void onResizeLock() { ++m_nResizeLockCount; }
|
|
void onResize() { ++m_nResizeCount; }
|
|
//@endcond
|
|
};
|
|
|
|
/// Dummy internal statistics for \ref striping mutex policy
|
|
struct empty_striping_stat {
|
|
//@cond
|
|
void onCellLock() const {}
|
|
void onCellTryLock() const {}
|
|
void onFullLock() const {}
|
|
void onResizeLock() const {}
|
|
void onResize() const {}
|
|
//@endcond
|
|
};
|
|
|
|
/// Lock striping concurrent access policy
|
|
/**
|
|
This is one of available opt::mutex_policy option type for CuckooSet
|
|
|
|
Lock striping is very simple technique.
|
|
The cuckoo set consists of the bucket tables and the array of locks.
|
|
There is single lock array for each bucket table, at least, the count of bucket table is 2.
|
|
Initially, the capacity of lock array and each bucket table is the same.
|
|
When set is resized, bucket table capacity will be doubled but lock array will not.
|
|
The lock \p i protects each bucket \p j, where <tt> j = i mod L </tt>,
|
|
where \p L - the size of lock array.
|
|
|
|
The policy contains an internal array of \p RecursiveLock locks.
|
|
|
|
Template arguments:
|
|
- \p RecursiveLock - the type of recursive mutex. The default is \p std::recursive_mutex. The mutex type should be default-constructible.
|
|
Note that a recursive spin-lock is not suitable for lock striping for performance reason.
|
|
- \p Arity - unsigned int constant that specifies an arity. The arity is the count of hash functors, i.e., the
|
|
count of lock arrays. Default value is 2.
|
|
- \p Alloc - allocator type used for lock array memory allocation. Default is \p CDS_DEFAULT_ALLOCATOR.
|
|
- \p Stat - internal statistics type. Note that this template argument is automatically selected by \ref CuckooSet
|
|
class according to its \p opt::stat option.
|
|
*/
|
|
template <
|
|
class RecursiveLock = std::recursive_mutex,
|
|
unsigned int Arity = 2,
|
|
class Alloc = CDS_DEFAULT_ALLOCATOR,
|
|
class Stat = empty_striping_stat
|
|
>
|
|
class striping
|
|
{
|
|
public:
|
|
typedef RecursiveLock lock_type ; ///< lock type
|
|
typedef Alloc allocator_type ; ///< allocator type
|
|
static unsigned int const c_nArity = Arity ; ///< the arity
|
|
typedef Stat statistics_type ; ///< Internal statistics type (\ref striping_stat or \ref empty_striping_stat)
|
|
|
|
//@cond
|
|
typedef striping_stat real_stat;
|
|
typedef empty_striping_stat empty_stat;
|
|
|
|
template <typename Stat2>
|
|
struct rebind_statistics {
|
|
typedef striping<lock_type, c_nArity, allocator_type, Stat2> other;
|
|
};
|
|
//@endcond
|
|
|
|
typedef cds::sync::lock_array< lock_type, cds::sync::pow2_select_policy, allocator_type > lock_array_type ; ///< lock array type
|
|
|
|
protected:
|
|
//@cond
|
|
class lock_array: public lock_array_type
|
|
{
|
|
public:
|
|
// placeholder ctor
|
|
lock_array(): lock_array_type( typename lock_array_type::select_cell_policy(2)) {}
|
|
|
|
// real ctor
|
|
lock_array( size_t nCapacity ): lock_array_type( nCapacity, typename lock_array_type::select_cell_policy(nCapacity)) {}
|
|
};
|
|
|
|
class scoped_lock: public std::unique_lock< lock_array_type >
|
|
{
|
|
typedef std::unique_lock< lock_array_type > base_class;
|
|
public:
|
|
scoped_lock( lock_array& arrLock, size_t nHash ): base_class( arrLock, nHash ) {}
|
|
};
|
|
//@endcond
|
|
|
|
protected:
|
|
//@cond
|
|
lock_array m_Locks[c_nArity] ; ///< array of \p lock_array_type
|
|
statistics_type m_Stat ; ///< internal statistics
|
|
//@endcond
|
|
|
|
public:
|
|
//@cond
|
|
class scoped_cell_lock {
|
|
lock_type * m_guard[c_nArity];
|
|
|
|
public:
|
|
scoped_cell_lock( striping& policy, size_t const* arrHash )
|
|
{
|
|
for ( unsigned int i = 0; i < c_nArity; ++i ) {
|
|
m_guard[i] = &( policy.m_Locks[i].at( policy.m_Locks[i].lock( arrHash[i] )));
|
|
}
|
|
policy.m_Stat.onCellLock();
|
|
}
|
|
|
|
~scoped_cell_lock()
|
|
{
|
|
for ( unsigned int i = 0; i < c_nArity; ++i )
|
|
m_guard[i]->unlock();
|
|
}
|
|
};
|
|
|
|
class scoped_cell_trylock
|
|
{
|
|
typedef typename lock_array_type::lock_type lock_type;
|
|
|
|
lock_type * m_guard[c_nArity];
|
|
bool m_bLocked;
|
|
|
|
public:
|
|
scoped_cell_trylock( striping& policy, size_t const* arrHash )
|
|
{
|
|
size_t nCell = policy.m_Locks[0].try_lock( arrHash[0] );
|
|
m_bLocked = nCell != lock_array_type::c_nUnspecifiedCell;
|
|
if ( m_bLocked ) {
|
|
m_guard[0] = &(policy.m_Locks[0].at(nCell));
|
|
for ( unsigned int i = 1; i < c_nArity; ++i ) {
|
|
m_guard[i] = &( policy.m_Locks[i].at( policy.m_Locks[i].lock( arrHash[i] )));
|
|
}
|
|
}
|
|
else {
|
|
std::fill( m_guard, m_guard + c_nArity, nullptr );
|
|
}
|
|
policy.m_Stat.onCellTryLock();
|
|
}
|
|
~scoped_cell_trylock()
|
|
{
|
|
if ( m_bLocked ) {
|
|
for ( unsigned int i = 0; i < c_nArity; ++i )
|
|
m_guard[i]->unlock();
|
|
}
|
|
}
|
|
|
|
bool locked() const
|
|
{
|
|
return m_bLocked;
|
|
}
|
|
};
|
|
|
|
class scoped_full_lock {
|
|
std::unique_lock< lock_array_type > m_guard;
|
|
public:
|
|
scoped_full_lock( striping& policy )
|
|
: m_guard( policy.m_Locks[0] )
|
|
{
|
|
policy.m_Stat.onFullLock();
|
|
}
|
|
|
|
/// Ctor for scoped_resize_lock - no statistics is incremented
|
|
scoped_full_lock( striping& policy, bool )
|
|
: m_guard( policy.m_Locks[0] )
|
|
{}
|
|
};
|
|
|
|
class scoped_resize_lock: public scoped_full_lock {
|
|
public:
|
|
scoped_resize_lock( striping& policy )
|
|
: scoped_full_lock( policy, false )
|
|
{
|
|
policy.m_Stat.onResizeLock();
|
|
}
|
|
};
|
|
//@endcond
|
|
|
|
public:
|
|
/// Constructor
|
|
striping(
|
|
size_t nLockCount ///< The size of lock array. Must be power of two.
|
|
)
|
|
{
|
|
// Trick: initialize the array of locks
|
|
for ( unsigned int i = 0; i < c_nArity; ++i ) {
|
|
lock_array * pArr = m_Locks + i;
|
|
pArr->lock_array::~lock_array();
|
|
new ( pArr ) lock_array( nLockCount );
|
|
}
|
|
}
|
|
|
|
/// Returns lock array size
|
|
/**
|
|
Lock array size is unchanged during \p striping object lifetime
|
|
*/
|
|
size_t lock_count() const
|
|
{
|
|
return m_Locks[0].size();
|
|
}
|
|
|
|
//@cond
|
|
void resize( size_t )
|
|
{
|
|
m_Stat.onResize();
|
|
}
|
|
//@endcond
|
|
|
|
/// Returns the arity of striping mutex policy
|
|
constexpr unsigned int arity() const noexcept
|
|
{
|
|
return c_nArity;
|
|
}
|
|
|
|
/// Returns internal statistics
|
|
statistics_type const& statistics() const
|
|
{
|
|
return m_Stat;
|
|
}
|
|
};
|
|
|
|
/// Internal statistics for \ref refinable mutex policy
|
|
struct refinable_stat {
|
|
typedef cds::atomicity::event_counter counter_type ; ///< Counter type
|
|
|
|
counter_type m_nCellLockCount ; ///< Count of obtaining cell lock
|
|
counter_type m_nCellLockWaitResizing ; ///< Count of loop iteration to wait for resizing
|
|
counter_type m_nCellLockArrayChanged ; ///< Count of event "Lock array has been changed when obtaining cell lock"
|
|
counter_type m_nCellLockFailed ; ///< Count of event "Cell lock failed because of the array is owned by other thread"
|
|
|
|
counter_type m_nSecondCellLockCount ; ///< Count of obtaining cell lock when another cell is already locked
|
|
counter_type m_nSecondCellLockFailed ; ///< Count of unsuccess obtaining cell lock when another cell is already locked
|
|
|
|
counter_type m_nFullLockCount ; ///< Count of obtaining full lock
|
|
counter_type m_nFullLockIter ; ///< Count of unsuccessfull iteration to obtain full lock
|
|
|
|
counter_type m_nResizeLockCount ; ///< Count of obtaining resize lock
|
|
counter_type m_nResizeLockIter ; ///< Count of unsuccessfull iteration to obtain resize lock
|
|
counter_type m_nResizeLockArrayChanged; ///< Count of event "Lock array has been changed when obtaining resize lock"
|
|
counter_type m_nResizeCount ; ///< Count of resize event
|
|
|
|
//@cond
|
|
void onCellLock() { ++m_nCellLockCount; }
|
|
void onCellWaitResizing() { ++m_nCellLockWaitResizing; }
|
|
void onCellArrayChanged() { ++m_nCellLockArrayChanged; }
|
|
void onCellLockFailed() { ++m_nCellLockFailed; }
|
|
void onSecondCellLock() { ++m_nSecondCellLockCount; }
|
|
void onSecondCellLockFailed() { ++m_nSecondCellLockFailed; }
|
|
void onFullLock() { ++m_nFullLockCount; }
|
|
void onFullLockIter() { ++m_nFullLockIter; }
|
|
void onResizeLock() { ++m_nResizeLockCount; }
|
|
void onResizeLockIter() { ++m_nResizeLockIter; }
|
|
void onResizeLockArrayChanged() { ++m_nResizeLockArrayChanged; }
|
|
void onResize() { ++m_nResizeCount; }
|
|
//@endcond
|
|
};
|
|
|
|
/// Dummy internal statistics for \ref refinable mutex policy
|
|
struct empty_refinable_stat {
|
|
//@cond
|
|
void onCellLock() const {}
|
|
void onCellWaitResizing() const {}
|
|
void onCellArrayChanged() const {}
|
|
void onCellLockFailed() const {}
|
|
void onSecondCellLock() const {}
|
|
void onSecondCellLockFailed() const {}
|
|
void onFullLock() const {}
|
|
void onFullLockIter() const {}
|
|
void onResizeLock() const {}
|
|
void onResizeLockIter() const {}
|
|
void onResizeLockArrayChanged() const {}
|
|
void onResize() const {}
|
|
//@endcond
|
|
};
|
|
|
|
/// Refinable concurrent access policy
|
|
/**
|
|
This is one of available \p opt::mutex_policy option type for \p CuckooSet
|
|
|
|
Refining is like a striping technique (see \p cuckoo::striping)
|
|
but it allows growing the size of lock array when resizing the hash table.
|
|
So, the sizes of hash table and lock array are equal.
|
|
|
|
Template arguments:
|
|
- \p RecursiveLock - the type of mutex. Reentrant (recursive) mutex is required.
|
|
The default is \p std::recursive_mutex. The mutex type should be default-constructible.
|
|
- \p Arity - unsigned int constant that specifies an arity. The arity is the count of hash functors, i.e., the
|
|
count of lock arrays. Default value is 2.
|
|
- \p BackOff - back-off strategy. Default is \p cds::backoff::Default
|
|
- \p Alloc - allocator type used for lock array memory allocation. Default is \p CDS_DEFAULT_ALLOCATOR.
|
|
- \p Stat - internal statistics type. Note that this template argument is automatically selected by \ref CuckooSet
|
|
class according to its \p opt::stat option.
|
|
*/
|
|
template <
|
|
class RecursiveLock = std::recursive_mutex,
|
|
unsigned int Arity = 2,
|
|
typename BackOff = cds::backoff::Default,
|
|
class Alloc = CDS_DEFAULT_ALLOCATOR,
|
|
class Stat = empty_refinable_stat
|
|
>
|
|
class refinable
|
|
{
|
|
public:
|
|
typedef RecursiveLock lock_type ; ///< lock type
|
|
typedef Alloc allocator_type ; ///< allocator type
|
|
typedef BackOff back_off ; ///< back-off strategy
|
|
typedef Stat statistics_type ; ///< internal statistics type
|
|
static unsigned int const c_nArity = Arity; ///< the arity
|
|
|
|
//@cond
|
|
typedef refinable_stat real_stat;
|
|
typedef empty_refinable_stat empty_stat;
|
|
|
|
template <typename Stat2>
|
|
struct rebind_statistics {
|
|
typedef refinable< lock_type, c_nArity, back_off, allocator_type, Stat2> other;
|
|
};
|
|
//@endcond
|
|
|
|
protected:
|
|
//@cond
|
|
typedef cds::sync::trivial_select_policy lock_selection_policy;
|
|
|
|
class lock_array_type
|
|
: public cds::sync::lock_array< lock_type, lock_selection_policy, allocator_type >
|
|
, public std::enable_shared_from_this< lock_array_type >
|
|
{
|
|
typedef cds::sync::lock_array< lock_type, lock_selection_policy, allocator_type > lock_array_base;
|
|
public:
|
|
lock_array_type( size_t nCapacity )
|
|
: lock_array_base( nCapacity )
|
|
{}
|
|
};
|
|
typedef std::shared_ptr< lock_array_type > lock_array_ptr;
|
|
typedef cds::details::Allocator< lock_array_type, allocator_type > lock_array_allocator;
|
|
|
|
typedef unsigned long long owner_t;
|
|
typedef cds::OS::ThreadId threadId_t;
|
|
|
|
typedef cds::sync::spin spinlock_type;
|
|
typedef std::unique_lock< spinlock_type > scoped_spinlock;
|
|
//@endcond
|
|
|
|
protected:
|
|
//@cond
|
|
static owner_t const c_nOwnerMask = (((owner_t) 1) << (sizeof(owner_t) * 8 - 1)) - 1;
|
|
|
|
atomics::atomic< owner_t > m_Owner ; ///< owner mark (thread id + boolean flag)
|
|
atomics::atomic<size_t> m_nCapacity ; ///< lock array capacity
|
|
lock_array_ptr m_arrLocks[ c_nArity ] ; ///< Lock array. The capacity of array is specified in constructor.
|
|
spinlock_type m_access ; ///< access to m_arrLocks
|
|
statistics_type m_Stat ; ///< internal statistics
|
|
//@endcond
|
|
|
|
protected:
|
|
//@cond
|
|
struct lock_array_disposer {
|
|
void operator()( lock_array_type * pArr )
|
|
{
|
|
// Seems, there is a false positive in std::shared_ptr deallocation in uninstrumented libc++
|
|
// see, for example, https://groups.google.com/forum/#!topic/thread-sanitizer/eHu4dE_z7Cc
|
|
// https://reviews.llvm.org/D21609
|
|
CDS_TSAN_ANNOTATE_IGNORE_WRITES_BEGIN;
|
|
lock_array_allocator().Delete( pArr );
|
|
CDS_TSAN_ANNOTATE_IGNORE_WRITES_END;
|
|
}
|
|
};
|
|
|
|
lock_array_ptr create_lock_array( size_t nCapacity )
|
|
{
|
|
return lock_array_ptr( lock_array_allocator().New( nCapacity ), lock_array_disposer());
|
|
}
|
|
|
|
void acquire( size_t const * arrHash, lock_array_ptr * pLockArr, lock_type ** parrLock )
|
|
{
|
|
owner_t me = (owner_t) cds::OS::get_current_thread_id();
|
|
owner_t who;
|
|
size_t cur_capacity;
|
|
|
|
back_off bkoff;
|
|
while ( true ) {
|
|
|
|
{
|
|
scoped_spinlock sl(m_access);
|
|
for ( unsigned int i = 0; i < c_nArity; ++i )
|
|
pLockArr[i] = m_arrLocks[i];
|
|
cur_capacity = m_nCapacity.load( atomics::memory_order_acquire );
|
|
}
|
|
|
|
// wait while resizing
|
|
while ( true ) {
|
|
who = m_Owner.load( atomics::memory_order_acquire );
|
|
if ( !( who & 1 ) || (who >> 1) == (me & c_nOwnerMask))
|
|
break;
|
|
bkoff();
|
|
m_Stat.onCellWaitResizing();
|
|
}
|
|
|
|
if ( cur_capacity == m_nCapacity.load( atomics::memory_order_acquire )) {
|
|
|
|
size_t const nMask = pLockArr[0]->size() - 1;
|
|
assert( cds::beans::is_power2( nMask + 1 ));
|
|
|
|
for ( unsigned int i = 0; i < c_nArity; ++i ) {
|
|
parrLock[i] = &( pLockArr[i]->at( arrHash[i] & nMask ));
|
|
parrLock[i]->lock();
|
|
}
|
|
|
|
who = m_Owner.load( atomics::memory_order_acquire );
|
|
if ( ( !(who & 1) || (who >> 1) == (me & c_nOwnerMask)) && cur_capacity == m_nCapacity.load( atomics::memory_order_acquire )) {
|
|
m_Stat.onCellLock();
|
|
return;
|
|
}
|
|
|
|
for ( unsigned int i = 0; i < c_nArity; ++i )
|
|
parrLock[i]->unlock();
|
|
|
|
m_Stat.onCellLockFailed();
|
|
}
|
|
else
|
|
m_Stat.onCellArrayChanged();
|
|
|
|
// clears pLockArr can lead to calling dtor for each item of pLockArr[i] that may be a heavy-weighted operation
|
|
// (each pLockArr[i] is a shared pointer to array of a ton of mutexes)
|
|
// It is better to do this before the next loop iteration where we will use spin-locked assignment to pLockArr
|
|
// However, destructing a lot of mutexes under spin-lock is a bad solution
|
|
for ( unsigned int i = 0; i < c_nArity; ++i )
|
|
pLockArr[i].reset();
|
|
}
|
|
}
|
|
|
|
bool try_second_acquire( size_t const * arrHash, lock_type ** parrLock )
|
|
{
|
|
// It is assumed that the current thread already has a lock
|
|
// and requires a second lock for other hash
|
|
|
|
size_t const nMask = m_nCapacity.load(atomics::memory_order_acquire) - 1;
|
|
size_t nCell = m_arrLocks[0]->try_lock( arrHash[0] & nMask);
|
|
if ( nCell == lock_array_type::c_nUnspecifiedCell ) {
|
|
m_Stat.onSecondCellLockFailed();
|
|
return false;
|
|
}
|
|
parrLock[0] = &(m_arrLocks[0]->at(nCell));
|
|
|
|
for ( unsigned int i = 1; i < c_nArity; ++i ) {
|
|
parrLock[i] = &( m_arrLocks[i]->at( m_arrLocks[i]->lock( arrHash[i] & nMask)));
|
|
}
|
|
|
|
m_Stat.onSecondCellLock();
|
|
return true;
|
|
}
|
|
|
|
void acquire_all()
|
|
{
|
|
owner_t me = (owner_t) cds::OS::get_current_thread_id();
|
|
|
|
back_off bkoff;
|
|
while ( true ) {
|
|
owner_t ownNull = 0;
|
|
if ( m_Owner.compare_exchange_strong( ownNull, (me << 1) | 1, atomics::memory_order_acq_rel, atomics::memory_order_relaxed )) {
|
|
m_arrLocks[0]->lock_all();
|
|
|
|
m_Stat.onFullLock();
|
|
return;
|
|
}
|
|
bkoff();
|
|
m_Stat.onFullLockIter();
|
|
}
|
|
}
|
|
|
|
void release_all()
|
|
{
|
|
m_arrLocks[0]->unlock_all();
|
|
m_Owner.store( 0, atomics::memory_order_release );
|
|
}
|
|
|
|
void acquire_resize( lock_array_ptr * pOldLocks )
|
|
{
|
|
owner_t me = (owner_t) cds::OS::get_current_thread_id();
|
|
size_t cur_capacity;
|
|
|
|
while ( true ) {
|
|
{
|
|
scoped_spinlock sl(m_access);
|
|
for ( unsigned int i = 0; i < c_nArity; ++i )
|
|
pOldLocks[i] = m_arrLocks[i];
|
|
cur_capacity = m_nCapacity.load( atomics::memory_order_acquire );
|
|
}
|
|
|
|
// global lock
|
|
owner_t ownNull = 0;
|
|
if ( m_Owner.compare_exchange_strong( ownNull, (me << 1) | 1, atomics::memory_order_acq_rel, atomics::memory_order_relaxed )) {
|
|
if ( cur_capacity == m_nCapacity.load( atomics::memory_order_acquire )) {
|
|
pOldLocks[0]->lock_all();
|
|
m_Stat.onResizeLock();
|
|
return;
|
|
}
|
|
|
|
m_Owner.store( 0, atomics::memory_order_release );
|
|
m_Stat.onResizeLockArrayChanged();
|
|
}
|
|
else
|
|
m_Stat.onResizeLockIter();
|
|
|
|
// clears pOldLocks can lead to calling dtor for each item of pOldLocks[i] that may be a heavy-weighted operation
|
|
// (each pOldLocks[i] is a shared pointer to array of a ton of mutexes)
|
|
// It is better to do this before the next loop iteration where we will use spin-locked assignment to pOldLocks
|
|
// However, destructing a lot of mutexes under spin-lock is a bad solution
|
|
for ( unsigned int i = 0; i < c_nArity; ++i )
|
|
pOldLocks[i].reset();
|
|
}
|
|
}
|
|
|
|
void release_resize( lock_array_ptr * pOldLocks )
|
|
{
|
|
m_Owner.store( 0, atomics::memory_order_release );
|
|
pOldLocks[0]->unlock_all();
|
|
}
|
|
//@endcond
|
|
|
|
public:
|
|
//@cond
|
|
class scoped_cell_lock {
|
|
lock_type * m_arrLock[ c_nArity ];
|
|
lock_array_ptr m_arrLockArr[ c_nArity ];
|
|
|
|
public:
|
|
scoped_cell_lock( refinable& policy, size_t const* arrHash )
|
|
{
|
|
policy.acquire( arrHash, m_arrLockArr, m_arrLock );
|
|
}
|
|
|
|
~scoped_cell_lock()
|
|
{
|
|
for ( unsigned int i = 0; i < c_nArity; ++i )
|
|
m_arrLock[i]->unlock();
|
|
}
|
|
};
|
|
|
|
class scoped_cell_trylock {
|
|
lock_type * m_arrLock[ c_nArity ];
|
|
bool m_bLocked;
|
|
|
|
public:
|
|
scoped_cell_trylock( refinable& policy, size_t const* arrHash )
|
|
{
|
|
m_bLocked = policy.try_second_acquire( arrHash, m_arrLock );
|
|
}
|
|
|
|
~scoped_cell_trylock()
|
|
{
|
|
if ( m_bLocked ) {
|
|
for ( unsigned int i = 0; i < c_nArity; ++i )
|
|
m_arrLock[i]->unlock();
|
|
}
|
|
}
|
|
|
|
bool locked() const
|
|
{
|
|
return m_bLocked;
|
|
}
|
|
};
|
|
|
|
class scoped_full_lock {
|
|
refinable& m_policy;
|
|
public:
|
|
scoped_full_lock( refinable& policy )
|
|
: m_policy( policy )
|
|
{
|
|
policy.acquire_all();
|
|
}
|
|
~scoped_full_lock()
|
|
{
|
|
m_policy.release_all();
|
|
}
|
|
};
|
|
|
|
class scoped_resize_lock
|
|
{
|
|
refinable& m_policy;
|
|
lock_array_ptr m_arrLocks[ c_nArity ];
|
|
public:
|
|
scoped_resize_lock( refinable& policy )
|
|
: m_policy(policy)
|
|
{
|
|
policy.acquire_resize( m_arrLocks );
|
|
}
|
|
~scoped_resize_lock()
|
|
{
|
|
m_policy.release_resize( m_arrLocks );
|
|
}
|
|
};
|
|
//@endcond
|
|
|
|
public:
|
|
/// Constructor
|
|
refinable(
|
|
size_t nLockCount ///< The size of lock array. Must be power of two.
|
|
) : m_Owner(0)
|
|
, m_nCapacity( nLockCount )
|
|
{
|
|
assert( cds::beans::is_power2( nLockCount ));
|
|
for ( unsigned int i = 0; i < c_nArity; ++i )
|
|
m_arrLocks[i] = create_lock_array( nLockCount );
|
|
}
|
|
|
|
//@cond
|
|
void resize( size_t nCapacity )
|
|
{
|
|
lock_array_ptr pNew[ c_nArity ];
|
|
for ( unsigned int i = 0; i < c_nArity; ++i )
|
|
pNew[i] = create_lock_array( nCapacity );
|
|
|
|
{
|
|
scoped_spinlock sl(m_access);
|
|
m_nCapacity.store( nCapacity, atomics::memory_order_release );
|
|
for ( unsigned int i = 0; i < c_nArity; ++i )
|
|
m_arrLocks[i] = pNew[i];
|
|
}
|
|
|
|
m_Stat.onResize();
|
|
}
|
|
//@endcond
|
|
|
|
/// Returns lock array size
|
|
/**
|
|
Lock array size is not a constant for \p refinable policy and can be changed when the set is resized.
|
|
*/
|
|
size_t lock_count() const
|
|
{
|
|
return m_nCapacity.load(atomics::memory_order_relaxed);
|
|
}
|
|
|
|
/// Returns the arity of \p refinable mutex policy
|
|
constexpr unsigned int arity() const noexcept
|
|
{
|
|
return c_nArity;
|
|
}
|
|
|
|
/// Returns internal statistics
|
|
statistics_type const& statistics() const
|
|
{
|
|
return m_Stat;
|
|
}
|
|
};
|
|
|
|
/// \p CuckooSet internal statistics
|
|
struct stat {
|
|
typedef cds::atomicity::event_counter counter_type ; ///< Counter type
|
|
|
|
counter_type m_nRelocateCallCount ; ///< Count of \p relocate() function call
|
|
counter_type m_nRelocateRoundCount ; ///< Count of attempts to relocate items
|
|
counter_type m_nFalseRelocateCount ; ///< Count of unneeded attempts of \p relocate call
|
|
counter_type m_nSuccessRelocateCount ; ///< Count of successful item relocating
|
|
counter_type m_nRelocateAboveThresholdCount; ///< Count of item relocating above probeset threshold
|
|
counter_type m_nFailedRelocateCount ; ///< Count of failed relocation attemp (when all probeset is full)
|
|
|
|
counter_type m_nResizeCallCount ; ///< Count of \p resize() function call
|
|
counter_type m_nFalseResizeCount ; ///< Count of false \p resize() function call (when other thread has been resized the set)
|
|
counter_type m_nResizeSuccessNodeMove; ///< Count of successful node moving when resizing
|
|
counter_type m_nResizeRelocateCall ; ///< Count of \p relocate() function call from \p resize function
|
|
|
|
counter_type m_nInsertSuccess ; ///< Count of successful \p insert() function call
|
|
counter_type m_nInsertFailed ; ///< Count of failed \p insert() function call
|
|
counter_type m_nInsertResizeCount ; ///< Count of \p resize() function call from \p insert()
|
|
counter_type m_nInsertRelocateCount ; ///< Count of \p relocate() function call from \p insert()
|
|
counter_type m_nInsertRelocateFault ; ///< Count of failed \p relocate() function call from \p insert()
|
|
|
|
counter_type m_nUpdateExistCount ; ///< Count of call \p update() function for existing node
|
|
counter_type m_nUpdateSuccessCount ; ///< Count of successful \p insert() function call for new node
|
|
counter_type m_nUpdateResizeCount ; ///< Count of \p resize() function call from \p update()
|
|
counter_type m_nUpdateRelocateCount ; ///< Count of \p relocate() function call from \p update()
|
|
counter_type m_nUpdateRelocateFault ; ///< Count of failed \p relocate() function call from \p update()
|
|
|
|
counter_type m_nUnlinkSuccess ; ///< Count of success \p unlink() function call
|
|
counter_type m_nUnlinkFailed ; ///< Count of failed \p unlink() function call
|
|
|
|
counter_type m_nEraseSuccess ; ///< Count of success \p erase() function call
|
|
counter_type m_nEraseFailed ; ///< Count of failed \p erase() function call
|
|
|
|
counter_type m_nFindSuccess ; ///< Count of success \p find() function call
|
|
counter_type m_nFindFailed ; ///< Count of failed \p find() function call
|
|
|
|
counter_type m_nFindEqualSuccess ; ///< Count of success \p find_equal() function call
|
|
counter_type m_nFindEqualFailed ; ///< Count of failed \p find_equal() function call
|
|
|
|
counter_type m_nFindWithSuccess ; ///< Count of success \p find_with() function call
|
|
counter_type m_nFindWithFailed ; ///< Count of failed \p find_with() function call
|
|
|
|
//@cond
|
|
void onRelocateCall() { ++m_nRelocateCallCount; }
|
|
void onRelocateRound() { ++m_nRelocateRoundCount; }
|
|
void onFalseRelocateRound() { ++m_nFalseRelocateCount; }
|
|
void onSuccessRelocateRound(){ ++m_nSuccessRelocateCount; }
|
|
void onRelocateAboveThresholdRound() { ++m_nRelocateAboveThresholdCount; }
|
|
void onFailedRelocate() { ++m_nFailedRelocateCount; }
|
|
|
|
void onResizeCall() { ++m_nResizeCallCount; }
|
|
void onFalseResizeCall() { ++m_nFalseResizeCount; }
|
|
void onResizeSuccessMove() { ++m_nResizeSuccessNodeMove; }
|
|
void onResizeRelocateCall() { ++m_nResizeRelocateCall; }
|
|
|
|
void onInsertSuccess() { ++m_nInsertSuccess; }
|
|
void onInsertFailed() { ++m_nInsertFailed; }
|
|
void onInsertResize() { ++m_nInsertResizeCount; }
|
|
void onInsertRelocate() { ++m_nInsertRelocateCount; }
|
|
void onInsertRelocateFault() { ++m_nInsertRelocateFault; }
|
|
|
|
void onUpdateExist() { ++m_nUpdateExistCount; }
|
|
void onUpdateSuccess() { ++m_nUpdateSuccessCount; }
|
|
void onUpdateResize() { ++m_nUpdateResizeCount; }
|
|
void onUpdateRelocate() { ++m_nUpdateRelocateCount; }
|
|
void onUpdateRelocateFault() { ++m_nUpdateRelocateFault; }
|
|
|
|
void onUnlinkSuccess() { ++m_nUnlinkSuccess; }
|
|
void onUnlinkFailed() { ++m_nUnlinkFailed; }
|
|
|
|
void onEraseSuccess() { ++m_nEraseSuccess; }
|
|
void onEraseFailed() { ++m_nEraseFailed; }
|
|
|
|
void onFindSuccess() { ++m_nFindSuccess; }
|
|
void onFindFailed() { ++m_nFindFailed; }
|
|
|
|
void onFindWithSuccess() { ++m_nFindWithSuccess; }
|
|
void onFindWithFailed() { ++m_nFindWithFailed; }
|
|
//@endcond
|
|
};
|
|
|
|
/// CuckooSet empty internal statistics
|
|
struct empty_stat {
|
|
//@cond
|
|
void onRelocateCall() const {}
|
|
void onRelocateRound() const {}
|
|
void onFalseRelocateRound() const {}
|
|
void onSuccessRelocateRound()const {}
|
|
void onRelocateAboveThresholdRound() const {}
|
|
void onFailedRelocate() const {}
|
|
|
|
void onResizeCall() const {}
|
|
void onFalseResizeCall() const {}
|
|
void onResizeSuccessMove() const {}
|
|
void onResizeRelocateCall() const {}
|
|
|
|
void onInsertSuccess() const {}
|
|
void onInsertFailed() const {}
|
|
void onInsertResize() const {}
|
|
void onInsertRelocate() const {}
|
|
void onInsertRelocateFault() const {}
|
|
|
|
void onUpdateExist() const {}
|
|
void onUpdateSuccess() const {}
|
|
void onUpdateResize() const {}
|
|
void onUpdateRelocate() const {}
|
|
void onUpdateRelocateFault() const {}
|
|
|
|
void onUnlinkSuccess() const {}
|
|
void onUnlinkFailed() const {}
|
|
|
|
void onEraseSuccess() const {}
|
|
void onEraseFailed() const {}
|
|
|
|
void onFindSuccess() const {}
|
|
void onFindFailed() const {}
|
|
|
|
void onFindWithSuccess() const {}
|
|
void onFindWithFailed() const {}
|
|
//@endcond
|
|
};
|
|
|
|
/// Type traits for CuckooSet class
|
|
struct traits
|
|
{
|
|
/// Hook used
|
|
/**
|
|
Possible values are: cuckoo::base_hook, cuckoo::member_hook, cuckoo::traits_hook.
|
|
*/
|
|
typedef base_hook<> hook;
|
|
|
|
/// Hash functors tuple
|
|
/**
|
|
This is mandatory type and has no predefined one.
|
|
|
|
At least, two hash functors should be provided. All hash functor
|
|
should be orthogonal (different): for each <tt> i,j: i != j => h[i](x) != h[j](x) </tt>.
|
|
The hash functors are defined as <tt> std::tuple< H1, H2, ... Hn > </tt>:
|
|
\@code cds::opt::hash< std::tuple< h1, h2 > > \@endcode
|
|
The number of hash functors specifies the number \p k - the count of hash tables in cuckoo hashing.
|
|
|
|
To specify hash tuple in traits you should use \p cds::opt::hash_tuple:
|
|
\code
|
|
struct my_traits: public cds::intrusive::cuckoo::traits {
|
|
typedef cds::opt::hash_tuple< hash1, hash2 > hash;
|
|
};
|
|
\endcode
|
|
*/
|
|
typedef cds::opt::none hash;
|
|
|
|
/// Concurrent access policy
|
|
/**
|
|
Available opt::mutex_policy types:
|
|
- \p cuckoo::striping - simple, but the lock array is not resizable
|
|
- \p cuckoo::refinable - resizable lock array, but more complex access to set data.
|
|
|
|
Default is \p cuckoo::striping.
|
|
*/
|
|
typedef cuckoo::striping<> mutex_policy;
|
|
|
|
/// Key equality functor
|
|
/**
|
|
Default is <tt>std::equal_to<T></tt>
|
|
*/
|
|
typedef opt::none equal_to;
|
|
|
|
/// Key comparing functor
|
|
/**
|
|
No default functor is provided. If the option is not specified, the \p less is used.
|
|
*/
|
|
typedef opt::none compare;
|
|
|
|
/// specifies binary predicate used for key comparison.
|
|
/**
|
|
Default is \p std::less<T>.
|
|
*/
|
|
typedef opt::none less;
|
|
|
|
/// Item counter
|
|
/**
|
|
The type for item counting feature.
|
|
Default is \p cds::atomicity::item_counter
|
|
|
|
Only atomic item counter type is allowed.
|
|
*/
|
|
typedef atomicity::item_counter item_counter;
|
|
|
|
/// Allocator type
|
|
/**
|
|
The allocator type for allocating bucket tables.
|
|
*/
|
|
typedef CDS_DEFAULT_ALLOCATOR allocator;
|
|
|
|
/// Disposer
|
|
/**
|
|
The disposer functor is used in \p CuckooSet::clear() member function
|
|
to free set's node.
|
|
*/
|
|
typedef intrusive::opt::v::empty_disposer disposer;
|
|
|
|
/// Internal statistics. Available statistics: \p cuckoo::stat, \p cuckoo::empty_stat
|
|
typedef empty_stat stat;
|
|
};
|
|
|
|
/// Metafunction converting option list to \p CuckooSet traits
|
|
/**
|
|
Template argument list \p Options... are:
|
|
- \p intrusive::opt::hook - hook used. Possible values are: \p cuckoo::base_hook, \p cuckoo::member_hook,
|
|
\p cuckoo::traits_hook.
|
|
If the option is not specified, <tt>%cuckoo::base_hook<></tt> is used.
|
|
- \p opt::hash - hash functor tuple, mandatory option. At least, two hash functors should be provided. All hash functor
|
|
should be orthogonal (different): for each <tt> i,j: i != j => h[i](x) != h[j](x) </tt>.
|
|
The hash functors are passed as <tt> std::tuple< H1, H2, ... Hn > </tt>. The number of hash functors specifies
|
|
the number \p k - the count of hash tables in cuckoo hashing.
|
|
- \p opt::mutex_policy - concurrent access policy.
|
|
Available policies: \p cuckoo::striping, \p cuckoo::refinable.
|
|
Default is \p %cuckoo::striping.
|
|
- \p opt::equal_to - key equality functor like \p std::equal_to.
|
|
If this functor is defined then the probe-set will be unordered.
|
|
If \p %opt::compare or \p %opt::less option is specified too, then the probe-set will be ordered
|
|
and \p %opt::equal_to will be ignored.
|
|
- \p opt::compare - key comparison functor. No default functor is provided.
|
|
If the option is not specified, the \p %opt::less is used.
|
|
If \p %opt::compare or \p %opt::less option is specified, then the probe-set will be ordered.
|
|
- \p opt::less - specifies binary predicate used for key comparison. Default is \p std::less<T>.
|
|
If \p %opt::compare or \p %opt::less option is specified, then the probe-set will be ordered.
|
|
- \p opt::item_counter - the type of item counting feature. Default is \p atomicity::item_counter
|
|
The item counter should be atomic.
|
|
- \p opt::allocator - the allocator type using for allocating bucket tables.
|
|
Default is \ref CDS_DEFAULT_ALLOCATOR
|
|
- \p intrusive::opt::disposer - the disposer type used in \p clear() member function for
|
|
freeing nodes. Default is \p intrusive::opt::v::empty_disposer
|
|
- \p opt::stat - internal statistics. Possibly types: \p cuckoo::stat, \p cuckoo::empty_stat.
|
|
Default is \p %cuckoo::empty_stat
|
|
|
|
The probe set traits \p cuckoo::probeset_type and \p cuckoo::store_hash are taken from \p node type
|
|
specified by \p opt::hook option.
|
|
*/
|
|
template <typename... Options>
|
|
struct make_traits {
|
|
typedef typename cds::opt::make_options<
|
|
typename cds::opt::find_type_traits< cuckoo::traits, Options... >::type
|
|
,Options...
|
|
>::type type ; ///< Result of metafunction
|
|
};
|
|
|
|
//@cond
|
|
namespace details {
|
|
template <typename Node, typename Probeset>
|
|
class bucket_entry;
|
|
|
|
template <typename Node>
|
|
class bucket_entry<Node, cuckoo::list>
|
|
{
|
|
public:
|
|
typedef Node node_type;
|
|
typedef cuckoo::list_probeset_class probeset_class;
|
|
typedef cuckoo::list probeset_type;
|
|
|
|
protected:
|
|
node_type * pHead;
|
|
unsigned int nSize;
|
|
|
|
public:
|
|
class iterator
|
|
{
|
|
node_type * pNode;
|
|
friend class bucket_entry;
|
|
|
|
public:
|
|
iterator()
|
|
: pNode( nullptr )
|
|
{}
|
|
iterator( node_type * p )
|
|
: pNode( p )
|
|
{}
|
|
iterator( iterator const& it)
|
|
: pNode( it.pNode )
|
|
{}
|
|
|
|
iterator& operator=( iterator const& it )
|
|
{
|
|
pNode = it.pNode;
|
|
return *this;
|
|
}
|
|
|
|
iterator& operator=( node_type * p )
|
|
{
|
|
pNode = p;
|
|
return *this;
|
|
}
|
|
|
|
node_type * operator->()
|
|
{
|
|
return pNode;
|
|
}
|
|
node_type& operator*()
|
|
{
|
|
assert( pNode != nullptr );
|
|
return *pNode;
|
|
}
|
|
|
|
// preinc
|
|
iterator& operator ++()
|
|
{
|
|
if ( pNode )
|
|
pNode = pNode->m_pNext;
|
|
return *this;
|
|
}
|
|
|
|
bool operator==(iterator const& it ) const
|
|
{
|
|
return pNode == it.pNode;
|
|
}
|
|
bool operator!=(iterator const& it ) const
|
|
{
|
|
return !( *this == it );
|
|
}
|
|
};
|
|
|
|
public:
|
|
bucket_entry()
|
|
: pHead( nullptr )
|
|
, nSize(0)
|
|
{
|
|
static_assert(( std::is_same<typename node_type::probeset_type, probeset_type>::value ), "Incompatible node type" );
|
|
}
|
|
|
|
iterator begin()
|
|
{
|
|
return iterator(pHead);
|
|
}
|
|
iterator end()
|
|
{
|
|
return iterator();
|
|
}
|
|
|
|
void insert_after( iterator it, node_type * p )
|
|
{
|
|
node_type * pPrev = it.pNode;
|
|
if ( pPrev ) {
|
|
p->m_pNext = pPrev->m_pNext;
|
|
pPrev->m_pNext = p;
|
|
}
|
|
else {
|
|
// insert as head
|
|
p->m_pNext = pHead;
|
|
pHead = p;
|
|
}
|
|
++nSize;
|
|
}
|
|
|
|
void remove( iterator itPrev, iterator itWhat )
|
|
{
|
|
node_type * pPrev = itPrev.pNode;
|
|
node_type * pWhat = itWhat.pNode;
|
|
assert( (!pPrev && pWhat == pHead) || (pPrev && pPrev->m_pNext == pWhat));
|
|
|
|
if ( pPrev )
|
|
pPrev->m_pNext = pWhat->m_pNext;
|
|
else {
|
|
assert( pWhat == pHead );
|
|
pHead = pHead->m_pNext;
|
|
}
|
|
pWhat->clear();
|
|
--nSize;
|
|
}
|
|
|
|
void clear()
|
|
{
|
|
node_type * pNext;
|
|
for ( node_type * pNode = pHead; pNode; pNode = pNext ) {
|
|
pNext = pNode->m_pNext;
|
|
pNode->clear();
|
|
}
|
|
|
|
nSize = 0;
|
|
pHead = nullptr;
|
|
}
|
|
|
|
template <typename Disposer>
|
|
void clear( Disposer disp )
|
|
{
|
|
node_type * pNext;
|
|
for ( node_type * pNode = pHead; pNode; pNode = pNext ) {
|
|
pNext = pNode->m_pNext;
|
|
pNode->clear();
|
|
disp( pNode );
|
|
}
|
|
|
|
nSize = 0;
|
|
pHead = nullptr;
|
|
}
|
|
|
|
unsigned int size() const
|
|
{
|
|
return nSize;
|
|
}
|
|
};
|
|
|
|
template <typename Node, unsigned int Capacity>
|
|
class bucket_entry<Node, cuckoo::vector<Capacity>>
|
|
{
|
|
public:
|
|
typedef Node node_type;
|
|
typedef cuckoo::vector_probeset_class probeset_class;
|
|
typedef cuckoo::vector<Capacity> probeset_type;
|
|
|
|
static unsigned int const c_nCapacity = probeset_type::c_nCapacity;
|
|
|
|
protected:
|
|
node_type * m_arrNode[c_nCapacity];
|
|
unsigned int m_nSize;
|
|
|
|
void shift_up( unsigned int nFrom )
|
|
{
|
|
assert( m_nSize < c_nCapacity );
|
|
|
|
if ( nFrom < m_nSize )
|
|
std::copy_backward( m_arrNode + nFrom, m_arrNode + m_nSize, m_arrNode + m_nSize + 1 );
|
|
}
|
|
|
|
void shift_down( node_type ** pFrom )
|
|
{
|
|
assert( m_arrNode <= pFrom && pFrom < m_arrNode + m_nSize);
|
|
std::copy( pFrom + 1, m_arrNode + m_nSize, pFrom );
|
|
}
|
|
public:
|
|
class iterator
|
|
{
|
|
node_type ** pArr;
|
|
friend class bucket_entry;
|
|
|
|
public:
|
|
iterator()
|
|
: pArr( nullptr )
|
|
{}
|
|
iterator( node_type ** p )
|
|
: pArr(p)
|
|
{}
|
|
iterator( iterator const& it)
|
|
: pArr( it.pArr )
|
|
{}
|
|
|
|
iterator& operator=( iterator const& it )
|
|
{
|
|
pArr = it.pArr;
|
|
return *this;
|
|
}
|
|
|
|
node_type * operator->()
|
|
{
|
|
assert( pArr != nullptr );
|
|
return *pArr;
|
|
}
|
|
node_type& operator*()
|
|
{
|
|
assert( pArr != nullptr );
|
|
assert( *pArr != nullptr );
|
|
return *(*pArr);
|
|
}
|
|
|
|
// preinc
|
|
iterator& operator ++()
|
|
{
|
|
++pArr;
|
|
return *this;
|
|
}
|
|
|
|
bool operator==(iterator const& it ) const
|
|
{
|
|
return pArr == it.pArr;
|
|
}
|
|
bool operator!=(iterator const& it ) const
|
|
{
|
|
return !( *this == it );
|
|
}
|
|
};
|
|
|
|
public:
|
|
bucket_entry()
|
|
: m_nSize(0)
|
|
{
|
|
memset( m_arrNode, 0, sizeof(m_arrNode));
|
|
static_assert(( std::is_same<typename node_type::probeset_type, probeset_type>::value ), "Incompatible node type" );
|
|
}
|
|
|
|
iterator begin()
|
|
{
|
|
return iterator(m_arrNode);
|
|
}
|
|
iterator end()
|
|
{
|
|
return iterator(m_arrNode + size());
|
|
}
|
|
|
|
void insert_after( iterator it, node_type * p )
|
|
{
|
|
assert( m_nSize < c_nCapacity );
|
|
assert( !it.pArr || (m_arrNode <= it.pArr && it.pArr <= m_arrNode + m_nSize));
|
|
|
|
if ( it.pArr ) {
|
|
shift_up( static_cast<unsigned int>(it.pArr - m_arrNode) + 1 );
|
|
it.pArr[1] = p;
|
|
}
|
|
else {
|
|
shift_up(0);
|
|
m_arrNode[0] = p;
|
|
}
|
|
++m_nSize;
|
|
}
|
|
|
|
void remove( iterator /*itPrev*/, iterator itWhat )
|
|
{
|
|
itWhat->clear();
|
|
shift_down( itWhat.pArr );
|
|
--m_nSize;
|
|
}
|
|
|
|
void clear()
|
|
{
|
|
m_nSize = 0;
|
|
}
|
|
|
|
template <typename Disposer>
|
|
void clear( Disposer disp )
|
|
{
|
|
for ( unsigned int i = 0; i < m_nSize; ++i ) {
|
|
disp( m_arrNode[i] );
|
|
}
|
|
m_nSize = 0;
|
|
}
|
|
|
|
unsigned int size() const
|
|
{
|
|
return m_nSize;
|
|
}
|
|
};
|
|
|
|
template <typename Node, unsigned int ArraySize>
|
|
struct hash_ops {
|
|
static void store( Node * pNode, size_t const* pHashes )
|
|
{
|
|
memcpy( pNode->m_arrHash, pHashes, sizeof(pHashes[0]) * ArraySize );
|
|
}
|
|
static bool equal_to( Node& node, unsigned int nTable, size_t nHash )
|
|
{
|
|
return node.m_arrHash[nTable] == nHash;
|
|
}
|
|
};
|
|
template <typename Node>
|
|
struct hash_ops<Node, 0>
|
|
{
|
|
static void store( Node * /*pNode*/, size_t * /*pHashes*/ )
|
|
{}
|
|
static bool equal_to( Node& /*node*/, unsigned int /*nTable*/, size_t /*nHash*/ )
|
|
{
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template <typename NodeTraits, bool Ordered>
|
|
struct contains;
|
|
|
|
template <typename NodeTraits>
|
|
struct contains<NodeTraits, true>
|
|
{
|
|
template <typename BucketEntry, typename Position, typename Q, typename Compare>
|
|
static bool find( BucketEntry& probeset, Position& pos, unsigned int /*nTable*/, size_t /*nHash*/, Q const& val, Compare cmp )
|
|
{
|
|
// Ordered version
|
|
typedef typename BucketEntry::iterator bucket_iterator;
|
|
|
|
bucket_iterator itPrev;
|
|
|
|
for ( bucket_iterator it = probeset.begin(), itEnd = probeset.end(); it != itEnd; ++it ) {
|
|
int cmpRes = cmp( *NodeTraits::to_value_ptr(*it), val );
|
|
if ( cmpRes >= 0 ) {
|
|
pos.itFound = it;
|
|
pos.itPrev = itPrev;
|
|
return cmpRes == 0;
|
|
}
|
|
|
|
itPrev = it;
|
|
}
|
|
|
|
pos.itPrev = itPrev;
|
|
pos.itFound = probeset.end();
|
|
return false;
|
|
}
|
|
};
|
|
|
|
template <typename NodeTraits>
|
|
struct contains<NodeTraits, false>
|
|
{
|
|
template <typename BucketEntry, typename Position, typename Q, typename EqualTo>
|
|
static bool find( BucketEntry& probeset, Position& pos, unsigned int nTable, size_t nHash, Q const& val, EqualTo eq )
|
|
{
|
|
// Unordered version
|
|
typedef typename BucketEntry::iterator bucket_iterator;
|
|
typedef typename BucketEntry::node_type node_type;
|
|
|
|
bucket_iterator itPrev;
|
|
|
|
for ( bucket_iterator it = probeset.begin(), itEnd = probeset.end(); it != itEnd; ++it ) {
|
|
if ( hash_ops<node_type, node_type::hash_array_size>::equal_to( *it, nTable, nHash ) && eq( *NodeTraits::to_value_ptr(*it), val )) {
|
|
pos.itFound = it;
|
|
pos.itPrev = itPrev;
|
|
return true;
|
|
}
|
|
itPrev = it;
|
|
}
|
|
|
|
pos.itPrev = itPrev;
|
|
pos.itFound = probeset.end();
|
|
return false;
|
|
}
|
|
};
|
|
|
|
} // namespace details
|
|
//@endcond
|
|
|
|
} // namespace cuckoo
|
|
|
|
/// Cuckoo hash set
|
|
/** @ingroup cds_intrusive_map
|
|
|
|
Source
|
|
- [2007] M.Herlihy, N.Shavit, M.Tzafrir "Concurrent Cuckoo Hashing. Technical report"
|
|
- [2008] Maurice Herlihy, Nir Shavit "The Art of Multiprocessor Programming"
|
|
|
|
<b>About Cuckoo hashing</b>
|
|
|
|
[From <i>"The Art of Multiprocessor Programming"</i>]
|
|
<a href="https://en.wikipedia.org/wiki/Cuckoo_hashing">Cuckoo hashing</a> is a hashing algorithm in which a newly added item displaces any earlier item
|
|
occupying the same slot. For brevity, a table is a k-entry array of items. For a hash set of size
|
|
N = 2k we use a two-entry array of tables, and two independent hash functions,
|
|
<tt> h0, h1: KeyRange -> 0,...,k-1</tt>
|
|
mapping the set of possible keys to entries in he array. To test whether a value \p x is in the set,
|
|
<tt>find(x)</tt> tests whether either <tt>table[0][h0(x)]</tt> or <tt>table[1][h1(x)]</tt> is
|
|
equal to \p x. Similarly, <tt>erase(x)</tt>checks whether \p x is in either <tt>table[0][h0(x)]</tt>
|
|
or <tt>table[1][h1(x)]</tt>, ad removes it if found.
|
|
|
|
The <tt>insert(x)</tt> successively "kicks out" conflicting items until every key has a slot.
|
|
To add \p x, the method swaps \p x with \p y, the current occupant of <tt>table[0][h0(x)]</tt>.
|
|
If the prior value was \p nullptr, it is done. Otherwise, it swaps the newly nest-less value \p y
|
|
for the current occupant of <tt>table[1][h1(y)]</tt> in the same way. As before, if the prior value
|
|
was \p nullptr, it is done. Otherwise, the method continues swapping entries (alternating tables)
|
|
until it finds an empty slot. We might not find an empty slot, either because the table is full,
|
|
or because the sequence of displacement forms a cycle. We therefore need an upper limit on the
|
|
number of successive displacements we are willing to undertake. When this limit is exceeded,
|
|
we resize the hash table, choose new hash functions and start over.
|
|
|
|
For concurrent cuckoo hashing, rather than organizing the set as a two-dimensional table of
|
|
items, we use two-dimensional table of probe sets, where a probe set is a constant-sized set
|
|
of items with the same hash code. Each probe set holds at most \p PROBE_SIZE items, but the algorithm
|
|
tries to ensure that when the set is quiescent (i.e no method call in progress) each probe set
|
|
holds no more than <tt>THRESHOLD < PROBE_SET</tt> items. While method calls are in-flight, a probe
|
|
set may temporarily hold more than \p THRESHOLD but never more than \p PROBE_SET items.
|
|
|
|
In current implementation, a probe set can be defined either as a (single-linked) list
|
|
or as a fixed-sized vector, optionally ordered.
|
|
|
|
In description above two-table cuckoo hashing (<tt>k = 2</tt>) has been considered.
|
|
We can generalize this approach for <tt>k >= 2</tt> when we have \p k hash functions
|
|
<tt>h[0], ... h[k-1]</tt> and \p k tables <tt>table[0], ... table[k-1]</tt>.
|
|
|
|
The search in probe set is linear, the complexity is <tt> O(PROBE_SET) </tt>.
|
|
The probe set may be ordered or not. Ordered probe set can be more efficient since
|
|
the average search complexity is <tt>O(PROBE_SET/2)</tt>.
|
|
However, the overhead of sorting can eliminate a gain of ordered search.
|
|
|
|
The probe set is ordered if \p compare or \p less is specified in \p Traits template
|
|
parameter. Otherwise, the probe set is unordered and \p Traits should provide
|
|
\p equal_to predicate.
|
|
|
|
The \p cds::intrusive::cuckoo namespace contains \p %CuckooSet-related declarations.
|
|
|
|
Template arguments:
|
|
- \p T - the type stored in the set. The type must be based on \p cuckoo::node (for \p cuckoo::base_hook)
|
|
or it must have a member of type %cuckoo::node (for \p cuckoo::member_hook),
|
|
or it must be convertible to \p %cuckoo::node (for \p cuckoo::traits_hook)
|
|
- \p Traits - type traits, default is \p cuckoo::traits. It is possible to declare option-based
|
|
set with \p cuckoo::make_traits metafunction result as \p Traits template argument.
|
|
|
|
<b>How to use</b>
|
|
|
|
You should incorporate \p cuckoo::node into your struct \p T and provide
|
|
appropriate \p cuckoo::traits::hook in your \p Traits template parameters.
|
|
Usually, for \p Traits you define a struct based on \p cuckoo::traits.
|
|
|
|
Example for base hook and list-based probe-set:
|
|
\code
|
|
#include <cds/intrusive/cuckoo_set.h>
|
|
|
|
// Data stored in cuckoo set
|
|
// We use list as probe-set container and store hash values in the node
|
|
// (since we use two hash functions we should store 2 hash values per node)
|
|
struct my_data: public cds::intrusive::cuckoo::node< cds::intrusive::cuckoo::list, 2 >
|
|
{
|
|
// key field
|
|
std::string strKey;
|
|
|
|
// other data
|
|
// ...
|
|
};
|
|
|
|
// Provide equal_to functor for my_data since we will use unordered probe-set
|
|
struct my_data_equal_to {
|
|
bool operator()( const my_data& d1, const my_data& d2 ) const
|
|
{
|
|
return d1.strKey.compare( d2.strKey ) == 0;
|
|
}
|
|
|
|
bool operator()( const my_data& d, const std::string& s ) const
|
|
{
|
|
return d.strKey.compare(s) == 0;
|
|
}
|
|
|
|
bool operator()( const std::string& s, const my_data& d ) const
|
|
{
|
|
return s.compare( d.strKey ) == 0;
|
|
}
|
|
};
|
|
|
|
// Provide two hash functor for my_data
|
|
struct hash1 {
|
|
size_t operator()(std::string const& s) const
|
|
{
|
|
return cds::opt::v::hash<std::string>( s );
|
|
}
|
|
size_t operator()( my_data const& d ) const
|
|
{
|
|
return (*this)( d.strKey );
|
|
}
|
|
};
|
|
|
|
struct hash2: private hash1 {
|
|
size_t operator()(std::string const& s) const
|
|
{
|
|
size_t h = ~( hash1::operator()(s));
|
|
return ~h + 0x9e3779b9 + (h << 6) + (h >> 2);
|
|
}
|
|
size_t operator()( my_data const& d ) const
|
|
{
|
|
return (*this)( d.strKey );
|
|
}
|
|
};
|
|
|
|
// Declare type traits
|
|
struct my_traits: public cds::intrusive::cuckoo::traits
|
|
{
|
|
typedef cds::intrusive::cuckoo::base_hook<
|
|
cds::intrusive::cuckoo::probeset_type< my_data::probeset_type >
|
|
,cds::intrusive::cuckoo::store_hash< my_data::hash_array_size >
|
|
> hook;
|
|
typedef my_data_equa_to equal_to;
|
|
typedef cds::opt::hash_tuple< hash1, hash2 > hash;
|
|
};
|
|
|
|
// Declare CuckooSet type
|
|
typedef cds::intrusive::CuckooSet< my_data, my_traits > my_cuckoo_set;
|
|
|
|
// Equal option-based declaration
|
|
typedef cds::intrusive::CuckooSet< my_data,
|
|
cds::intrusive::cuckoo::make_traits<
|
|
cds::intrusive::opt::hook< cds::intrusive::cuckoo::base_hook<
|
|
cds::intrusive::cuckoo::probeset_type< my_data::probeset_type >
|
|
,cds::intrusive::cuckoo::store_hash< my_data::hash_array_size >
|
|
> >
|
|
,cds::opt::hash< std::tuple< hash1, hash2 > >
|
|
,cds::opt::equal_to< my_data_equal_to >
|
|
>::type
|
|
> opt_cuckoo_set;
|
|
\endcode
|
|
|
|
If we provide \p compare function instead of \p equal_to for \p my_data
|
|
we get as a result a cuckoo set with ordered probe set that may improve
|
|
performance.
|
|
Example for base hook and ordered vector-based probe-set:
|
|
|
|
\code
|
|
#include <cds/intrusive/cuckoo_set.h>
|
|
|
|
// Data stored in cuckoo set
|
|
// We use a vector of capacity 4 as probe-set container and store hash values in the node
|
|
// (since we use two hash functions we should store 2 hash values per node)
|
|
struct my_data: public cds::intrusive::cuckoo::node< cds::intrusive::cuckoo::vector<4>, 2 >
|
|
{
|
|
// key field
|
|
std::string strKey;
|
|
|
|
// other data
|
|
// ...
|
|
};
|
|
|
|
// Provide compare functor for my_data since we want to use ordered probe-set
|
|
struct my_data_compare {
|
|
int operator()( const my_data& d1, const my_data& d2 ) const
|
|
{
|
|
return d1.strKey.compare( d2.strKey );
|
|
}
|
|
|
|
int operator()( const my_data& d, const std::string& s ) const
|
|
{
|
|
return d.strKey.compare(s);
|
|
}
|
|
|
|
int operator()( const std::string& s, const my_data& d ) const
|
|
{
|
|
return s.compare( d.strKey );
|
|
}
|
|
};
|
|
|
|
// Provide two hash functor for my_data
|
|
struct hash1 {
|
|
size_t operator()(std::string const& s) const
|
|
{
|
|
return cds::opt::v::hash<std::string>( s );
|
|
}
|
|
size_t operator()( my_data const& d ) const
|
|
{
|
|
return (*this)( d.strKey );
|
|
}
|
|
};
|
|
|
|
struct hash2: private hash1 {
|
|
size_t operator()(std::string const& s) const
|
|
{
|
|
size_t h = ~( hash1::operator()(s));
|
|
return ~h + 0x9e3779b9 + (h << 6) + (h >> 2);
|
|
}
|
|
size_t operator()( my_data const& d ) const
|
|
{
|
|
return (*this)( d.strKey );
|
|
}
|
|
};
|
|
|
|
// Declare type traits
|
|
struct my_traits: public cds::intrusive::cuckoo::traits
|
|
{
|
|
typedef cds::intrusive::cuckoo::base_hook<
|
|
cds::intrusive::cuckoo::probeset_type< my_data::probeset_type >
|
|
,cds::intrusive::cuckoo::store_hash< my_data::hash_array_size >
|
|
> hook;
|
|
typedef my_data_compare compare;
|
|
typedef cds::opt::hash_tuple< hash1, hash2 > hash;
|
|
};
|
|
|
|
// Declare CuckooSet type
|
|
typedef cds::intrusive::CuckooSet< my_data, my_traits > my_cuckoo_set;
|
|
|
|
// Equal option-based declaration
|
|
typedef cds::intrusive::CuckooSet< my_data,
|
|
cds::intrusive::cuckoo::make_traits<
|
|
cds::intrusive::opt::hook< cds::intrusive::cuckoo::base_hook<
|
|
cds::intrusive::cuckoo::probeset_type< my_data::probeset_type >
|
|
,cds::intrusive::cuckoo::store_hash< my_data::hash_array_size >
|
|
> >
|
|
,cds::opt::hash< std::tuple< hash1, hash2 > >
|
|
,cds::opt::compare< my_data_compare >
|
|
>::type
|
|
> opt_cuckoo_set;
|
|
\endcode
|
|
|
|
*/
|
|
template <typename T, typename Traits = cuckoo::traits>
|
|
class CuckooSet
|
|
{
|
|
public:
|
|
typedef T value_type; ///< The value type stored in the set
|
|
typedef Traits traits; ///< Set traits
|
|
|
|
typedef typename traits::hook hook; ///< hook type
|
|
typedef typename hook::node_type node_type; ///< node type
|
|
typedef typename get_node_traits< value_type, node_type, hook>::type node_traits; ///< node traits
|
|
|
|
typedef typename traits::hash hash; ///< hash functor tuple wrapped for internal use
|
|
typedef typename hash::hash_tuple_type hash_tuple_type; ///< Type of hash tuple
|
|
|
|
typedef typename traits::stat stat; ///< internal statistics type
|
|
|
|
typedef typename traits::mutex_policy original_mutex_policy; ///< Concurrent access policy, see \p cuckoo::traits::mutex_policy
|
|
|
|
//@cond
|
|
typedef typename original_mutex_policy::template rebind_statistics<
|
|
typename std::conditional<
|
|
std::is_same< stat, cuckoo::empty_stat >::value
|
|
,typename original_mutex_policy::empty_stat
|
|
,typename original_mutex_policy::real_stat
|
|
>::type
|
|
>::other mutex_policy;
|
|
//@endcond
|
|
|
|
/// Probe set should be ordered or not
|
|
/**
|
|
If \p Traits specifies \p cmpare or \p less functor then the set is ordered.
|
|
Otherwise, it is unordered and \p Traits should provide \p equal_to functor.
|
|
*/
|
|
static bool const c_isSorted = !( std::is_same< typename traits::compare, opt::none >::value
|
|
&& std::is_same< typename traits::less, opt::none >::value );
|
|
static size_t const c_nArity = hash::size ; ///< the arity of cuckoo hashing: the number of hash functors provided; minimum 2.
|
|
|
|
/// Key equality functor; used only for unordered probe-set
|
|
typedef typename opt::details::make_equal_to< value_type, traits, !c_isSorted>::type key_equal_to;
|
|
|
|
/// key comparing functor based on \p opt::compare and \p opt::less option setter. Used only for ordered probe set
|
|
typedef typename opt::details::make_comparator< value_type, traits >::type key_comparator;
|
|
|
|
/// allocator type
|
|
typedef typename traits::allocator allocator;
|
|
|
|
/// item counter type
|
|
typedef typename traits::item_counter item_counter;
|
|
|
|
/// node disposer
|
|
typedef typename traits::disposer disposer;
|
|
|
|
protected:
|
|
//@cond
|
|
typedef typename node_type::probeset_class probeset_class;
|
|
typedef typename node_type::probeset_type probeset_type;
|
|
static unsigned int const c_nNodeHashArraySize = node_type::hash_array_size;
|
|
|
|
typedef typename mutex_policy::scoped_cell_lock scoped_cell_lock;
|
|
typedef typename mutex_policy::scoped_cell_trylock scoped_cell_trylock;
|
|
typedef typename mutex_policy::scoped_full_lock scoped_full_lock;
|
|
typedef typename mutex_policy::scoped_resize_lock scoped_resize_lock;
|
|
|
|
typedef cuckoo::details::bucket_entry< node_type, probeset_type > bucket_entry;
|
|
typedef typename bucket_entry::iterator bucket_iterator;
|
|
typedef cds::details::Allocator< bucket_entry, allocator > bucket_table_allocator;
|
|
|
|
typedef size_t hash_array[c_nArity] ; ///< hash array
|
|
|
|
struct position {
|
|
bucket_iterator itPrev;
|
|
bucket_iterator itFound;
|
|
};
|
|
|
|
typedef cuckoo::details::contains< node_traits, c_isSorted > contains_action;
|
|
|
|
template <typename Predicate>
|
|
using predicate_wrapper = typename std::conditional< c_isSorted, cds::opt::details::make_comparator_from_less<Predicate>, Predicate>::type;
|
|
|
|
typedef typename std::conditional< c_isSorted, key_comparator, key_equal_to >::type key_predicate;
|
|
//@endcond
|
|
|
|
public:
|
|
static unsigned int const c_nDefaultProbesetSize = 4; ///< default probeset size
|
|
static size_t const c_nDefaultInitialSize = 16; ///< default initial size
|
|
static unsigned int const c_nRelocateLimit = c_nArity * 2 - 1; ///< Count of attempts to relocate before giving up
|
|
|
|
protected:
|
|
bucket_entry * m_BucketTable[ c_nArity ] ; ///< Bucket tables
|
|
|
|
atomics::atomic<size_t> m_nBucketMask ; ///< Hash bitmask; bucket table size minus 1.
|
|
unsigned int const m_nProbesetSize ; ///< Probe set size
|
|
unsigned int const m_nProbesetThreshold ; ///< Probe set threshold
|
|
|
|
hash m_Hash ; ///< Hash functor tuple
|
|
mutex_policy m_MutexPolicy ; ///< concurrent access policy
|
|
item_counter m_ItemCounter ; ///< item counter
|
|
mutable stat m_Stat ; ///< internal statistics
|
|
|
|
protected:
|
|
//@cond
|
|
static void check_common_constraints()
|
|
{
|
|
static_assert( (c_nArity == mutex_policy::c_nArity), "The count of hash functors must be equal to mutex_policy arity" );
|
|
}
|
|
|
|
void check_probeset_properties() const
|
|
{
|
|
assert( m_nProbesetThreshold < m_nProbesetSize );
|
|
|
|
// if probe set type is cuckoo::vector<N> then m_nProbesetSize == N
|
|
assert( node_type::probeset_size == 0 || node_type::probeset_size == m_nProbesetSize );
|
|
}
|
|
|
|
template <typename Q>
|
|
void hashing( size_t * pHashes, Q const& v ) const
|
|
{
|
|
m_Hash( pHashes, v );
|
|
}
|
|
|
|
void copy_hash( size_t * pHashes, value_type const& v ) const
|
|
{
|
|
constexpr_if ( c_nNodeHashArraySize != 0 )
|
|
memcpy( pHashes, node_traits::to_node_ptr( v )->get_hash(), sizeof( pHashes[0] ) * c_nNodeHashArraySize );
|
|
else
|
|
hashing( pHashes, v );
|
|
}
|
|
|
|
bucket_entry& bucket( unsigned int nTable, size_t nHash )
|
|
{
|
|
assert( nTable < c_nArity );
|
|
return m_BucketTable[nTable][nHash & m_nBucketMask.load( atomics::memory_order_relaxed ) ];
|
|
}
|
|
|
|
static void store_hash( node_type * pNode, size_t * pHashes )
|
|
{
|
|
cuckoo::details::hash_ops< node_type, c_nNodeHashArraySize >::store( pNode, pHashes );
|
|
}
|
|
|
|
static bool equal_hash( node_type& node, unsigned int nTable, size_t nHash )
|
|
{
|
|
return cuckoo::details::hash_ops< node_type, c_nNodeHashArraySize >::equal_to( node, nTable, nHash );
|
|
}
|
|
|
|
void allocate_bucket_tables( size_t nSize )
|
|
{
|
|
assert( cds::beans::is_power2( nSize ));
|
|
|
|
m_nBucketMask.store( nSize - 1, atomics::memory_order_release );
|
|
bucket_table_allocator alloc;
|
|
for ( unsigned int i = 0; i < c_nArity; ++i )
|
|
m_BucketTable[i] = alloc.NewArray( nSize );
|
|
}
|
|
|
|
static void free_bucket_tables( bucket_entry ** pTable, size_t nCapacity )
|
|
{
|
|
bucket_table_allocator alloc;
|
|
for ( unsigned int i = 0; i < c_nArity; ++i ) {
|
|
alloc.Delete( pTable[i], nCapacity );
|
|
pTable[i] = nullptr;
|
|
}
|
|
}
|
|
void free_bucket_tables()
|
|
{
|
|
free_bucket_tables( m_BucketTable, m_nBucketMask.load( atomics::memory_order_relaxed ) + 1 );
|
|
}
|
|
|
|
static constexpr unsigned int const c_nUndefTable = (unsigned int) -1;
|
|
template <typename Q, typename Predicate >
|
|
unsigned int contains( position * arrPos, size_t * arrHash, Q const& val, Predicate pred )
|
|
{
|
|
// Buckets must be locked
|
|
|
|
for ( unsigned int i = 0; i < c_nArity; ++i ) {
|
|
bucket_entry& probeset = bucket( i, arrHash[i] );
|
|
if ( contains_action::find( probeset, arrPos[i], i, arrHash[i], val, pred ))
|
|
return i;
|
|
}
|
|
return c_nUndefTable;
|
|
}
|
|
|
|
template <typename Q, typename Predicate, typename Func>
|
|
value_type * erase_( Q const& val, Predicate pred, Func f )
|
|
{
|
|
hash_array arrHash;
|
|
hashing( arrHash, val );
|
|
position arrPos[ c_nArity ];
|
|
|
|
{
|
|
scoped_cell_lock guard( m_MutexPolicy, arrHash );
|
|
|
|
unsigned int nTable = contains( arrPos, arrHash, val, pred );
|
|
if ( nTable != c_nUndefTable ) {
|
|
node_type& node = *arrPos[nTable].itFound;
|
|
f( *node_traits::to_value_ptr(node));
|
|
bucket( nTable, arrHash[nTable]).remove( arrPos[nTable].itPrev, arrPos[nTable].itFound );
|
|
--m_ItemCounter;
|
|
m_Stat.onEraseSuccess();
|
|
return node_traits::to_value_ptr( node );
|
|
}
|
|
}
|
|
|
|
m_Stat.onEraseFailed();
|
|
return nullptr;
|
|
}
|
|
|
|
template <typename Q, typename Predicate, typename Func>
|
|
bool find_( Q& val, Predicate pred, Func f )
|
|
{
|
|
hash_array arrHash;
|
|
position arrPos[ c_nArity ];
|
|
hashing( arrHash, val );
|
|
scoped_cell_lock sl( m_MutexPolicy, arrHash );
|
|
|
|
unsigned int nTable = contains( arrPos, arrHash, val, pred );
|
|
if ( nTable != c_nUndefTable ) {
|
|
f( *node_traits::to_value_ptr( *arrPos[nTable].itFound ), val );
|
|
m_Stat.onFindSuccess();
|
|
return true;
|
|
}
|
|
|
|
m_Stat.onFindFailed();
|
|
return false;
|
|
}
|
|
|
|
bool relocate( unsigned int nTable, size_t * arrGoalHash )
|
|
{
|
|
// arrGoalHash contains hash values for relocating element
|
|
// Relocating element is first one from bucket( nTable, arrGoalHash[nTable] ) probeset
|
|
|
|
m_Stat.onRelocateCall();
|
|
|
|
hash_array arrHash;
|
|
value_type * pVal;
|
|
for ( unsigned int nRound = 0; nRound < c_nRelocateLimit; ++nRound ) {
|
|
m_Stat.onRelocateRound();
|
|
|
|
while ( true ) {
|
|
scoped_cell_lock guard( m_MutexPolicy, arrGoalHash );
|
|
|
|
bucket_entry& refBucket = bucket( nTable, arrGoalHash[nTable] );
|
|
if ( refBucket.size() < m_nProbesetThreshold ) {
|
|
// probeset is not above the threshold
|
|
m_Stat.onFalseRelocateRound();
|
|
return true;
|
|
}
|
|
|
|
pVal = node_traits::to_value_ptr( *refBucket.begin());
|
|
copy_hash( arrHash, *pVal );
|
|
|
|
scoped_cell_trylock guard2( m_MutexPolicy, arrHash );
|
|
if ( !guard2.locked())
|
|
continue ; // try one more time
|
|
|
|
refBucket.remove( typename bucket_entry::iterator(), refBucket.begin());
|
|
|
|
unsigned int i = (nTable + 1) % c_nArity;
|
|
|
|
// try insert into free probeset
|
|
while ( i != nTable ) {
|
|
bucket_entry& bkt = bucket( i, arrHash[i] );
|
|
if ( bkt.size() < m_nProbesetThreshold ) {
|
|
position pos;
|
|
contains_action::find( bkt, pos, i, arrHash[i], *pVal, key_predicate()) ; // must return false!
|
|
bkt.insert_after( pos.itPrev, node_traits::to_node_ptr( pVal ));
|
|
m_Stat.onSuccessRelocateRound();
|
|
return true;
|
|
}
|
|
i = ( i + 1 ) % c_nArity;
|
|
}
|
|
|
|
// try insert into partial probeset
|
|
i = (nTable + 1) % c_nArity;
|
|
while ( i != nTable ) {
|
|
bucket_entry& bkt = bucket( i, arrHash[i] );
|
|
if ( bkt.size() < m_nProbesetSize ) {
|
|
position pos;
|
|
contains_action::find( bkt, pos, i, arrHash[i], *pVal, key_predicate()) ; // must return false!
|
|
bkt.insert_after( pos.itPrev, node_traits::to_node_ptr( pVal ));
|
|
nTable = i;
|
|
memcpy( arrGoalHash, arrHash, sizeof(arrHash));
|
|
m_Stat.onRelocateAboveThresholdRound();
|
|
goto next_iteration;
|
|
}
|
|
i = (i + 1) % c_nArity;
|
|
}
|
|
|
|
// all probeset is full, relocating fault
|
|
refBucket.insert_after( typename bucket_entry::iterator(), node_traits::to_node_ptr( pVal ));
|
|
m_Stat.onFailedRelocate();
|
|
return false;
|
|
}
|
|
|
|
next_iteration:;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void resize()
|
|
{
|
|
m_Stat.onResizeCall();
|
|
|
|
size_t nOldCapacity = bucket_count( atomics::memory_order_acquire );
|
|
bucket_entry* pOldTable[ c_nArity ];
|
|
{
|
|
scoped_resize_lock guard( m_MutexPolicy );
|
|
|
|
if ( nOldCapacity != bucket_count()) {
|
|
m_Stat.onFalseResizeCall();
|
|
return;
|
|
}
|
|
|
|
size_t nCapacity = nOldCapacity * 2;
|
|
|
|
m_MutexPolicy.resize( nCapacity );
|
|
memcpy( pOldTable, m_BucketTable, sizeof(pOldTable));
|
|
allocate_bucket_tables( nCapacity );
|
|
|
|
hash_array arrHash;
|
|
position arrPos[ c_nArity ];
|
|
|
|
for ( unsigned int nTable = 0; nTable < c_nArity; ++nTable ) {
|
|
bucket_entry * pTable = pOldTable[nTable];
|
|
for ( size_t k = 0; k < nOldCapacity; ++k ) {
|
|
bucket_iterator itNext;
|
|
for ( bucket_iterator it = pTable[k].begin(), itEnd = pTable[k].end(); it != itEnd; it = itNext ) {
|
|
itNext = it;
|
|
++itNext;
|
|
|
|
value_type& val = *node_traits::to_value_ptr( *it );
|
|
copy_hash( arrHash, val );
|
|
CDS_VERIFY_EQ( contains( arrPos, arrHash, val, key_predicate()), c_nUndefTable );
|
|
|
|
for ( unsigned int i = 0; i < c_nArity; ++i ) {
|
|
bucket_entry& refBucket = bucket( i, arrHash[i] );
|
|
if ( refBucket.size() < m_nProbesetThreshold ) {
|
|
refBucket.insert_after( arrPos[i].itPrev, &*it );
|
|
m_Stat.onResizeSuccessMove();
|
|
goto do_next;
|
|
}
|
|
}
|
|
|
|
for ( unsigned int i = 0; i < c_nArity; ++i ) {
|
|
bucket_entry& refBucket = bucket( i, arrHash[i] );
|
|
if ( refBucket.size() < m_nProbesetSize ) {
|
|
refBucket.insert_after( arrPos[i].itPrev, &*it );
|
|
assert( refBucket.size() > 1 );
|
|
copy_hash( arrHash, *node_traits::to_value_ptr( *refBucket.begin()));
|
|
m_Stat.onResizeRelocateCall();
|
|
relocate( i, arrHash );
|
|
break;
|
|
}
|
|
}
|
|
do_next:;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
free_bucket_tables( pOldTable, nOldCapacity );
|
|
}
|
|
|
|
constexpr static unsigned int calc_probeset_size( unsigned int nProbesetSize ) noexcept
|
|
{
|
|
return std::is_same< probeset_class, cuckoo::vector_probeset_class >::value
|
|
? node_type::probeset_size
|
|
: (nProbesetSize
|
|
? nProbesetSize
|
|
: ( node_type::probeset_size ? node_type::probeset_size : c_nDefaultProbesetSize ));
|
|
}
|
|
//@endcond
|
|
|
|
public:
|
|
/// Default constructor
|
|
/**
|
|
Initial size = \ref c_nDefaultInitialSize
|
|
|
|
Probe set size:
|
|
- \p c_nDefaultProbesetSize if \p probeset_type is \p cuckoo::list
|
|
- \p Capacity if \p probeset_type is <tt> cuckoo::vector<Capacity> </tt>
|
|
|
|
Probe set threshold = probe set size - 1
|
|
*/
|
|
CuckooSet()
|
|
: m_nProbesetSize( calc_probeset_size(0))
|
|
, m_nProbesetThreshold( m_nProbesetSize - 1 )
|
|
, m_MutexPolicy( c_nDefaultInitialSize )
|
|
{
|
|
check_common_constraints();
|
|
check_probeset_properties();
|
|
|
|
allocate_bucket_tables( c_nDefaultInitialSize );
|
|
}
|
|
|
|
/// Constructs the set object with given probe set size and threshold
|
|
/**
|
|
If probe set type is <tt> cuckoo::vector<Capacity> </tt> vector
|
|
then \p nProbesetSize is ignored since it should be equal to vector's \p Capacity.
|
|
*/
|
|
CuckooSet(
|
|
size_t nInitialSize ///< Initial set size; if 0 - use default initial size \p c_nDefaultInitialSize
|
|
, unsigned int nProbesetSize ///< probe set size
|
|
, unsigned int nProbesetThreshold = 0 ///< probe set threshold, <tt>nProbesetThreshold < nProbesetSize</tt>. If 0, <tt>nProbesetThreshold = nProbesetSize - 1</tt>
|
|
)
|
|
: m_nProbesetSize( calc_probeset_size(nProbesetSize))
|
|
, m_nProbesetThreshold( nProbesetThreshold ? nProbesetThreshold : m_nProbesetSize - 1 )
|
|
, m_MutexPolicy( cds::beans::ceil2(nInitialSize ? nInitialSize : c_nDefaultInitialSize ))
|
|
{
|
|
check_common_constraints();
|
|
check_probeset_properties();
|
|
|
|
allocate_bucket_tables( nInitialSize ? cds::beans::ceil2( nInitialSize ) : c_nDefaultInitialSize );
|
|
}
|
|
|
|
/// Constructs the set object with given hash functor tuple
|
|
/**
|
|
The probe set size and threshold are set as default, see \p CuckooSet()
|
|
*/
|
|
CuckooSet(
|
|
hash_tuple_type const& h ///< hash functor tuple of type <tt>std::tuple<H1, H2, ... Hn></tt> where <tt> n == \ref c_nArity </tt>
|
|
)
|
|
: m_nProbesetSize( calc_probeset_size(0))
|
|
, m_nProbesetThreshold( m_nProbesetSize -1 )
|
|
, m_Hash( h )
|
|
, m_MutexPolicy( c_nDefaultInitialSize )
|
|
{
|
|
check_common_constraints();
|
|
check_probeset_properties();
|
|
|
|
allocate_bucket_tables( c_nDefaultInitialSize );
|
|
}
|
|
|
|
/// Constructs the set object with given probe set properties and hash functor tuple
|
|
/**
|
|
If probe set type is <tt> cuckoo::vector<Capacity> </tt> vector
|
|
then \p nProbesetSize should be equal to vector's \p Capacity.
|
|
*/
|
|
CuckooSet(
|
|
size_t nInitialSize ///< Initial set size; if 0 - use default initial size \p c_nDefaultInitialSize
|
|
, unsigned int nProbesetSize ///< probe set size, positive integer
|
|
, unsigned int nProbesetThreshold ///< probe set threshold, <tt>nProbesetThreshold < nProbesetSize</tt>. If 0, <tt>nProbesetThreshold = nProbesetSize - 1</tt>
|
|
, hash_tuple_type const& h ///< hash functor tuple of type <tt>std::tuple<H1, H2, ... Hn></tt> where <tt> n == \ref c_nArity </tt>
|
|
)
|
|
: m_nProbesetSize( calc_probeset_size(nProbesetSize))
|
|
, m_nProbesetThreshold( nProbesetThreshold ? nProbesetThreshold : m_nProbesetSize - 1)
|
|
, m_Hash( h )
|
|
, m_MutexPolicy( cds::beans::ceil2(nInitialSize ? nInitialSize : c_nDefaultInitialSize ))
|
|
{
|
|
check_common_constraints();
|
|
check_probeset_properties();
|
|
|
|
allocate_bucket_tables( nInitialSize ? cds::beans::ceil2( nInitialSize ) : c_nDefaultInitialSize );
|
|
}
|
|
|
|
/// Constructs the set object with given hash functor tuple (move semantics)
|
|
/**
|
|
The probe set size and threshold are set as default, see \p CuckooSet()
|
|
*/
|
|
CuckooSet(
|
|
hash_tuple_type&& h ///< hash functor tuple of type <tt>std::tuple<H1, H2, ... Hn></tt> where <tt> n == \ref c_nArity </tt>
|
|
)
|
|
: m_nProbesetSize( calc_probeset_size(0))
|
|
, m_nProbesetThreshold( m_nProbesetSize / 2 )
|
|
, m_Hash( std::forward<hash_tuple_type>(h))
|
|
, m_MutexPolicy( c_nDefaultInitialSize )
|
|
{
|
|
check_common_constraints();
|
|
check_probeset_properties();
|
|
|
|
allocate_bucket_tables( c_nDefaultInitialSize );
|
|
}
|
|
|
|
/// Constructs the set object with given probe set properties and hash functor tuple (move semantics)
|
|
/**
|
|
If probe set type is <tt> cuckoo::vector<Capacity> </tt> vector
|
|
then \p nProbesetSize should be equal to vector's \p Capacity.
|
|
*/
|
|
CuckooSet(
|
|
size_t nInitialSize ///< Initial set size; if 0 - use default initial size \p c_nDefaultInitialSize
|
|
, unsigned int nProbesetSize ///< probe set size, positive integer
|
|
, unsigned int nProbesetThreshold ///< probe set threshold, <tt>nProbesetThreshold < nProbesetSize</tt>. If 0, <tt>nProbesetThreshold = nProbesetSize - 1</tt>
|
|
, hash_tuple_type&& h ///< hash functor tuple of type <tt>std::tuple<H1, H2, ... Hn></tt> where <tt> n == \ref c_nArity </tt>
|
|
)
|
|
: m_nProbesetSize( calc_probeset_size(nProbesetSize))
|
|
, m_nProbesetThreshold( nProbesetThreshold ? nProbesetThreshold : m_nProbesetSize - 1)
|
|
, m_Hash( std::forward<hash_tuple_type>(h))
|
|
, m_MutexPolicy( cds::beans::ceil2(nInitialSize ? nInitialSize : c_nDefaultInitialSize ))
|
|
{
|
|
check_common_constraints();
|
|
check_probeset_properties();
|
|
|
|
allocate_bucket_tables( nInitialSize ? cds::beans::ceil2( nInitialSize ) : c_nDefaultInitialSize );
|
|
}
|
|
|
|
/// Destructor
|
|
~CuckooSet()
|
|
{
|
|
free_bucket_tables();
|
|
}
|
|
|
|
public:
|
|
/// Inserts new node
|
|
/**
|
|
The function inserts \p val in the set if it does not contain an item with key equal to \p val.
|
|
|
|
Returns \p true if \p val is inserted into the set, \p false otherwise.
|
|
*/
|
|
bool insert( value_type& val )
|
|
{
|
|
return insert( val, []( value_type& ) {} );
|
|
}
|
|
|
|
/// Inserts new node
|
|
/**
|
|
The function allows to split creating of new item into two part:
|
|
- create item with key only
|
|
- insert new item into the set
|
|
- if inserting is success, calls \p f functor to initialize value-field of \p val.
|
|
|
|
The functor signature is:
|
|
\code
|
|
void func( value_type& val );
|
|
\endcode
|
|
where \p val is the item inserted.
|
|
|
|
The user-defined functor is called only if the inserting is success.
|
|
*/
|
|
template <typename Func>
|
|
bool insert( value_type& val, Func f )
|
|
{
|
|
hash_array arrHash;
|
|
position arrPos[ c_nArity ];
|
|
unsigned int nGoalTable;
|
|
|
|
hashing( arrHash, val );
|
|
node_type * pNode = node_traits::to_node_ptr( val );
|
|
store_hash( pNode, arrHash );
|
|
|
|
while (true) {
|
|
{
|
|
scoped_cell_lock guard( m_MutexPolicy, arrHash );
|
|
|
|
if ( contains( arrPos, arrHash, val, key_predicate()) != c_nUndefTable ) {
|
|
m_Stat.onInsertFailed();
|
|
return false;
|
|
}
|
|
|
|
for ( unsigned int i = 0; i < c_nArity; ++i ) {
|
|
bucket_entry& refBucket = bucket( i, arrHash[i] );
|
|
if ( refBucket.size() < m_nProbesetThreshold ) {
|
|
refBucket.insert_after( arrPos[i].itPrev, pNode );
|
|
f( val );
|
|
++m_ItemCounter;
|
|
m_Stat.onInsertSuccess();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for ( unsigned int i = 0; i < c_nArity; ++i ) {
|
|
bucket_entry& refBucket = bucket( i, arrHash[i] );
|
|
if ( refBucket.size() < m_nProbesetSize ) {
|
|
refBucket.insert_after( arrPos[i].itPrev, pNode );
|
|
f( val );
|
|
++m_ItemCounter;
|
|
nGoalTable = i;
|
|
assert( refBucket.size() > 1 );
|
|
copy_hash( arrHash, *node_traits::to_value_ptr( *refBucket.begin()));
|
|
goto do_relocate;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_Stat.onInsertResize();
|
|
resize();
|
|
}
|
|
|
|
do_relocate:
|
|
m_Stat.onInsertRelocate();
|
|
if ( !relocate( nGoalTable, arrHash )) {
|
|
m_Stat.onInsertRelocateFault();
|
|
m_Stat.onInsertResize();
|
|
resize();
|
|
}
|
|
|
|
m_Stat.onInsertSuccess();
|
|
return true;
|
|
}
|
|
|
|
/// Updates the node
|
|
/**
|
|
The operation performs inserting or changing data with lock-free manner.
|
|
|
|
If the item \p val is not found in the set, then \p val is inserted into the set
|
|
iff \p bAllowInsert is \p true.
|
|
Otherwise, the functor \p func is called with item found.
|
|
The functor \p func signature is:
|
|
\code
|
|
void func( bool bNew, value_type& item, value_type& val );
|
|
\endcode
|
|
with arguments:
|
|
- \p bNew - \p true if the item has been inserted, \p false otherwise
|
|
- \p item - item of the set
|
|
- \p val - argument \p val passed into the \p %update() function
|
|
If new item has been inserted (i.e. \p bNew is \p true) then \p item and \p val arguments
|
|
refer to the same thing.
|
|
|
|
The functor may change non-key fields of the \p item.
|
|
|
|
Returns std::pair<bool, bool> where \p first is \p true if operation is successful,
|
|
i.e. the node has been inserted or updated,
|
|
\p second is \p true if new item has been added or \p false if the item with \p key
|
|
already exists.
|
|
*/
|
|
template <typename Func>
|
|
std::pair<bool, bool> update( value_type& val, Func func, bool bAllowInsert = true )
|
|
{
|
|
hash_array arrHash;
|
|
position arrPos[ c_nArity ];
|
|
unsigned int nGoalTable;
|
|
|
|
hashing( arrHash, val );
|
|
node_type * pNode = node_traits::to_node_ptr( val );
|
|
store_hash( pNode, arrHash );
|
|
|
|
while (true) {
|
|
{
|
|
scoped_cell_lock guard( m_MutexPolicy, arrHash );
|
|
|
|
unsigned int nTable = contains( arrPos, arrHash, val, key_predicate());
|
|
if ( nTable != c_nUndefTable ) {
|
|
func( false, *node_traits::to_value_ptr( *arrPos[nTable].itFound ), val );
|
|
m_Stat.onUpdateExist();
|
|
return std::make_pair( true, false );
|
|
}
|
|
|
|
if ( !bAllowInsert )
|
|
return std::make_pair( false, false );
|
|
|
|
//node_type * pNode = node_traits::to_node_ptr( val );
|
|
//store_hash( pNode, arrHash );
|
|
|
|
for ( unsigned int i = 0; i < c_nArity; ++i ) {
|
|
bucket_entry& refBucket = bucket( i, arrHash[i] );
|
|
if ( refBucket.size() < m_nProbesetThreshold ) {
|
|
refBucket.insert_after( arrPos[i].itPrev, pNode );
|
|
func( true, val, val );
|
|
++m_ItemCounter;
|
|
m_Stat.onUpdateSuccess();
|
|
return std::make_pair( true, true );
|
|
}
|
|
}
|
|
|
|
for ( unsigned int i = 0; i < c_nArity; ++i ) {
|
|
bucket_entry& refBucket = bucket( i, arrHash[i] );
|
|
if ( refBucket.size() < m_nProbesetSize ) {
|
|
refBucket.insert_after( arrPos[i].itPrev, pNode );
|
|
func( true, val, val );
|
|
++m_ItemCounter;
|
|
nGoalTable = i;
|
|
assert( refBucket.size() > 1 );
|
|
copy_hash( arrHash, *node_traits::to_value_ptr( *refBucket.begin()));
|
|
goto do_relocate;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_Stat.onUpdateResize();
|
|
resize();
|
|
}
|
|
|
|
do_relocate:
|
|
m_Stat.onUpdateRelocate();
|
|
if ( !relocate( nGoalTable, arrHash )) {
|
|
m_Stat.onUpdateRelocateFault();
|
|
m_Stat.onUpdateResize();
|
|
resize();
|
|
}
|
|
|
|
m_Stat.onUpdateSuccess();
|
|
return std::make_pair( true, true );
|
|
}
|
|
//@cond
|
|
template <typename Func>
|
|
CDS_DEPRECATED("ensure() is deprecated, use update()")
|
|
std::pair<bool, bool> ensure( value_type& val, Func func )
|
|
{
|
|
return update( val, func, true );
|
|
}
|
|
//@endcond
|
|
|
|
/// Unlink the item \p val from the set
|
|
/**
|
|
The function searches the item \p val in the set and unlink it
|
|
if it is found and is equal to \p val (here, the equality means that
|
|
\p val belongs to the set: if \p item is an item found then
|
|
unlink is successful iif <tt>&val == &item</tt>)
|
|
|
|
The function returns \p true if success and \p false otherwise.
|
|
*/
|
|
bool unlink( value_type& val )
|
|
{
|
|
hash_array arrHash;
|
|
hashing( arrHash, val );
|
|
position arrPos[ c_nArity ];
|
|
|
|
{
|
|
scoped_cell_lock guard( m_MutexPolicy, arrHash );
|
|
|
|
unsigned int nTable = contains( arrPos, arrHash, val, key_predicate());
|
|
if ( nTable != c_nUndefTable && node_traits::to_value_ptr(*arrPos[nTable].itFound) == &val ) {
|
|
bucket( nTable, arrHash[nTable]).remove( arrPos[nTable].itPrev, arrPos[nTable].itFound );
|
|
--m_ItemCounter;
|
|
m_Stat.onUnlinkSuccess();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
m_Stat.onUnlinkFailed();
|
|
return false;
|
|
}
|
|
|
|
/// Deletes the item from the set
|
|
/** \anchor cds_intrusive_CuckooSet_erase
|
|
The function searches an item with key equal to \p val in the set,
|
|
unlinks it from the set, and returns a pointer to unlinked item.
|
|
|
|
If the item with key equal to \p val is not found the function return \p nullptr.
|
|
|
|
Note the hash functor should accept a parameter of type \p Q that can be not the same as \p value_type.
|
|
*/
|
|
template <typename Q>
|
|
value_type * erase( Q const& val )
|
|
{
|
|
return erase( val, [](value_type const&) {} );
|
|
}
|
|
|
|
/// Deletes the item from the set using \p pred predicate for searching
|
|
/**
|
|
The function is an analog of \ref cds_intrusive_CuckooSet_erase "erase(Q const&)"
|
|
but \p pred is used for key comparing.
|
|
If cuckoo set is ordered, then \p Predicate should have the interface and semantics like \p std::less.
|
|
If cuckoo set is unordered, then \p Predicate should have the interface and semantics like \p std::equal_to.
|
|
\p Predicate must imply the same element order as the comparator used for building the set.
|
|
*/
|
|
template <typename Q, typename Predicate>
|
|
value_type * erase_with( Q const& val, Predicate pred )
|
|
{
|
|
CDS_UNUSED( pred );
|
|
return erase_( val, predicate_wrapper<Predicate>(), [](value_type const&) {} );
|
|
}
|
|
|
|
/// Delete the item from the set
|
|
/** \anchor cds_intrusive_CuckooSet_erase_func
|
|
The function searches an item with key equal to \p val in the set,
|
|
call \p f functor with item found, unlinks it from the set, and returns a pointer to unlinked item.
|
|
|
|
The \p Func interface is
|
|
\code
|
|
struct functor {
|
|
void operator()( value_type const& item );
|
|
};
|
|
\endcode
|
|
|
|
If the item with key equal to \p val is not found the function return \p nullptr.
|
|
|
|
Note the hash functor should accept a parameter of type \p Q that can be not the same as \p value_type.
|
|
*/
|
|
template <typename Q, typename Func>
|
|
value_type * erase( Q const& val, Func f )
|
|
{
|
|
return erase_( val, key_predicate(), f );
|
|
}
|
|
|
|
/// Deletes the item from the set using \p pred predicate for searching
|
|
/**
|
|
The function is an analog of \ref cds_intrusive_CuckooSet_erase_func "erase(Q const&, Func)"
|
|
but \p pred is used for key comparing.
|
|
If you use ordered cuckoo set, then \p Predicate should have the interface and semantics like \p std::less.
|
|
If you use unordered cuckoo set, then \p Predicate should have the interface and semantics like \p std::equal_to.
|
|
\p Predicate must imply the same element order as the comparator used for building the set.
|
|
*/
|
|
template <typename Q, typename Predicate, typename Func>
|
|
value_type * erase_with( Q const& val, Predicate pred, Func f )
|
|
{
|
|
CDS_UNUSED( pred );
|
|
return erase_( val, predicate_wrapper<Predicate>(), f );
|
|
}
|
|
|
|
/// Find the key \p val
|
|
/** \anchor cds_intrusive_CuckooSet_find_func
|
|
The function searches the item with key equal to \p val and calls the functor \p f for item found.
|
|
The interface of \p Func functor is:
|
|
\code
|
|
struct functor {
|
|
void operator()( value_type& item, Q& val );
|
|
};
|
|
\endcode
|
|
where \p item is the item found, \p val is the <tt>find</tt> function argument.
|
|
|
|
The functor may change non-key fields of \p item.
|
|
|
|
The \p val argument is non-const since it can be used as \p f functor destination i.e., the functor
|
|
may modify both arguments.
|
|
|
|
Note the hash functor specified for class \p Traits template parameter
|
|
should accept a parameter of type \p Q that can be not the same as \p value_type.
|
|
|
|
The function returns \p true if \p val is found, \p false otherwise.
|
|
*/
|
|
template <typename Q, typename Func>
|
|
bool find( Q& val, Func f )
|
|
{
|
|
return find_( val, key_predicate(), f );
|
|
}
|
|
//@cond
|
|
template <typename Q, typename Func>
|
|
bool find( Q const& val, Func f )
|
|
{
|
|
return find_( val, key_predicate(), f );
|
|
}
|
|
//@endcond
|
|
|
|
/// Find the key \p val using \p pred predicate for comparing
|
|
/**
|
|
The function is an analog of \ref cds_intrusive_CuckooSet_find_func "find(Q&, Func)"
|
|
but \p pred is used for key comparison.
|
|
If you use ordered cuckoo set, then \p Predicate should have the interface and semantics like \p std::less.
|
|
If you use unordered cuckoo set, then \p Predicate should have the interface and semantics like \p std::equal_to.
|
|
\p pred must imply the same element order as the comparator used for building the set.
|
|
*/
|
|
template <typename Q, typename Predicate, typename Func>
|
|
bool find_with( Q& val, Predicate pred, Func f )
|
|
{
|
|
CDS_UNUSED( pred );
|
|
return find_( val, predicate_wrapper<Predicate>(), f );
|
|
}
|
|
//@cond
|
|
template <typename Q, typename Predicate, typename Func>
|
|
bool find_with( Q const& val, Predicate pred, Func f )
|
|
{
|
|
CDS_UNUSED( pred );
|
|
return find_( val, predicate_wrapper<Predicate>(), f );
|
|
}
|
|
//@endcond
|
|
|
|
/// Checks whether the set contains \p key
|
|
/**
|
|
The function searches the item with key equal to \p key
|
|
and returns \p true if it is found, and \p false otherwise.
|
|
*/
|
|
template <typename Q>
|
|
bool contains( Q const& key )
|
|
{
|
|
return find( key, [](value_type&, Q const& ) {} );
|
|
}
|
|
//@cond
|
|
template <typename Q>
|
|
CDS_DEPRECATED("deprecated, use contains()")
|
|
bool find( Q const& key )
|
|
{
|
|
return contains( key );
|
|
}
|
|
//@endcond
|
|
|
|
/// Checks whether the set contains \p key using \p pred predicate for searching
|
|
/**
|
|
The function is similar to <tt>contains( key )</tt> but \p pred is used for key comparing.
|
|
If the set is unordered, \p Predicate has semantics like \p std::equal_to.
|
|
For ordered set \p Predicate has \p std::less semantics. In that case \p pred
|
|
must imply the same element order as the comparator used for building the set.
|
|
*/
|
|
template <typename Q, typename Predicate>
|
|
bool contains( Q const& key, Predicate pred )
|
|
{
|
|
CDS_UNUSED( pred );
|
|
return find_with( key, predicate_wrapper<Predicate>(), [](value_type& , Q const& ) {} );
|
|
}
|
|
//@cond
|
|
template <typename Q, typename Predicate>
|
|
CDS_DEPRECATED("deprecated, use contains()")
|
|
bool find_with( Q const& key, Predicate pred )
|
|
{
|
|
return contains( key, pred );
|
|
}
|
|
//@endcond
|
|
|
|
/// Clears the set
|
|
/**
|
|
The function unlinks all items from the set.
|
|
For any item \p Traits::disposer is called
|
|
*/
|
|
void clear()
|
|
{
|
|
clear_and_dispose( disposer());
|
|
}
|
|
|
|
/// Clears the set and calls \p disposer for each item
|
|
/**
|
|
The function unlinks all items from the set calling \p oDisposer for each item.
|
|
\p Disposer functor interface is:
|
|
\code
|
|
struct Disposer{
|
|
void operator()( value_type * p );
|
|
};
|
|
\endcode
|
|
|
|
The \p Traits::disposer is not called.
|
|
*/
|
|
template <typename Disposer>
|
|
void clear_and_dispose( Disposer oDisposer )
|
|
{
|
|
// locks entire array
|
|
scoped_full_lock sl( m_MutexPolicy );
|
|
|
|
for ( unsigned int i = 0; i < c_nArity; ++i ) {
|
|
bucket_entry * pEntry = m_BucketTable[i];
|
|
bucket_entry * pEnd = pEntry + m_nBucketMask.load( atomics::memory_order_relaxed ) + 1;
|
|
for ( ; pEntry != pEnd ; ++pEntry ) {
|
|
pEntry->clear( [&oDisposer]( node_type * pNode ){ oDisposer( node_traits::to_value_ptr( pNode )) ; } );
|
|
}
|
|
}
|
|
m_ItemCounter.reset();
|
|
}
|
|
|
|
/// Checks if the set is empty
|
|
/**
|
|
Emptiness is checked by item counting: if item count is zero then the set is empty.
|
|
*/
|
|
bool empty() const
|
|
{
|
|
return size() == 0;
|
|
}
|
|
|
|
/// Returns item count in the set
|
|
size_t size() const
|
|
{
|
|
return m_ItemCounter;
|
|
}
|
|
|
|
/// Returns the size of hash table
|
|
/**
|
|
The hash table size is non-constant and can be increased via resizing.
|
|
*/
|
|
size_t bucket_count() const
|
|
{
|
|
return m_nBucketMask.load( atomics::memory_order_relaxed ) + 1;
|
|
}
|
|
//@cond
|
|
size_t bucket_count( atomics::memory_order load_mo ) const
|
|
{
|
|
return m_nBucketMask.load( load_mo ) + 1;
|
|
}
|
|
//@endcond
|
|
|
|
/// Returns lock array size
|
|
size_t lock_count() const
|
|
{
|
|
return m_MutexPolicy.lock_count();
|
|
}
|
|
|
|
/// Returns const reference to internal statistics
|
|
stat const& statistics() const
|
|
{
|
|
return m_Stat;
|
|
}
|
|
|
|
/// Returns const reference to mutex policy internal statistics
|
|
typename mutex_policy::statistics_type const& mutex_policy_statistics() const
|
|
{
|
|
return m_MutexPolicy.statistics();
|
|
}
|
|
};
|
|
}} // namespace cds::intrusive
|
|
|
|
#endif // #ifndef CDSLIB_INTRUSIVE_CUCKOO_SET_H
|