8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-23 12:43:03 +01:00
firebird-mirror/extern/libcds/cds/container/feldman_hashset_rcu.h
2022-10-08 20:46:39 +03:00

574 lines
23 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_CONTAINER_FELDMAN_HASHSET_RCU_H
#define CDSLIB_CONTAINER_FELDMAN_HASHSET_RCU_H
#include <cds/intrusive/feldman_hashset_rcu.h>
#include <cds/container/details/feldman_hashset_base.h>
namespace cds { namespace container {
/// Hash set based on multi-level array, \ref cds_urcu_desc "RCU" specialization
/** @ingroup cds_nonintrusive_set
@anchor cds_container_FeldmanHashSet_rcu
Source:
- [2013] Steven Feldman, Pierre LaBorde, Damian Dechev "Concurrent Multi-level Arrays:
Wait-free Extensible Hash Maps"
See algorithm short description @ref cds_intrusive_FeldmanHashSet_hp "here"
@note Two important things you should keep in mind when you're using \p %FeldmanHashSet:
- all keys must be fixed-size. It means that you cannot use \p std::string as a key for \p %FeldmanHashSet.
Instead, for the strings you should use well-known hashing algorithms like <a href="https://en.wikipedia.org/wiki/Secure_Hash_Algorithm">SHA1, SHA2</a>,
<a href="https://en.wikipedia.org/wiki/MurmurHash">MurmurHash</a>, <a href="https://en.wikipedia.org/wiki/CityHash">CityHash</a>
or its successor <a href="https://code.google.com/p/farmhash/">FarmHash</a> and so on, which
converts variable-length strings to fixed-length bit-strings, and use that hash as a key in \p %FeldmanHashSet.
- \p %FeldmanHashSet uses a perfect hashing. It means that if two different keys, for example, of type \p std::string,
have identical hash then you cannot insert both that keys in the set. \p %FeldmanHashSet does not maintain the key,
it maintains its fixed-size hash value.
The set supports @ref cds_container_FeldmanHashSet_iterators "bidirectional thread-safe iterators".
Template parameters:
- \p RCU - one of \ref cds_urcu_gc "RCU type"
- \p T - a value type to be stored in the set
- \p Traits - type traits, the structure based on \p feldman_hashset::traits or result of \p feldman_hashset::make_traits metafunction.
\p Traits is the mandatory argument because it has one mandatory type - an @ref feldman_hashset::traits::hash_accessor "accessor"
to hash value of \p T. The set algorithm does not calculate that hash value.
@note Before including <tt><cds/intrusive/feldman_hashset_rcu.h></tt> you should include appropriate RCU header file,
see \ref cds_urcu_gc "RCU type" for list of existing RCU class and corresponding header files.
The set supports @ref cds_container_FeldmanHashSet_rcu_iterators "bidirectional thread-safe iterators"
with some restrictions.
*/
template <
class RCU
, typename T
#ifdef CDS_DOXYGEN_INVOKED
, class Traits = feldman_hashset::traits
#else
, class Traits
#endif
>
class FeldmanHashSet< cds::urcu::gc< RCU >, T, Traits >
#ifdef CDS_DOXYGEN_INVOKED
: protected cds::intrusive::FeldmanHashSet< cds::urcu::gc< RCU >, T, Traits >
#else
: protected cds::container::details::make_feldman_hashset< cds::urcu::gc< RCU >, T, Traits >::type
#endif
{
//@cond
typedef cds::container::details::make_feldman_hashset< cds::urcu::gc< RCU >, T, Traits > maker;
typedef typename maker::type base_class;
//@endcond
public:
typedef cds::urcu::gc< RCU > gc; ///< RCU garbage collector
typedef T value_type; ///< type of value stored in the set
typedef Traits traits; ///< Traits template parameter, see \p feldman_hashset::traits
typedef typename base_class::hash_accessor hash_accessor; ///< Hash accessor functor
typedef typename base_class::hash_type hash_type; ///< Hash type deduced from \p hash_accessor return type
typedef typename base_class::hash_comparator hash_comparator; ///< hash compare functor based on \p opt::compare and \p opt::less option setter
typedef typename traits::item_counter item_counter; ///< Item counter type
typedef typename traits::allocator allocator; ///< Element allocator
typedef typename traits::node_allocator node_allocator; ///< Array node allocator
typedef typename traits::memory_model memory_model; ///< Memory model
typedef typename traits::back_off back_off; ///< Backoff strategy
typedef typename traits::stat stat; ///< Internal statistics type
typedef typename traits::rcu_check_deadlock rcu_check_deadlock; ///< Deadlock checking policy
typedef typename gc::scoped_lock rcu_lock; ///< RCU scoped lock
static constexpr const bool c_bExtractLockExternal = false; ///< Group of \p extract_xxx functions does not require external locking
typedef typename base_class::exempt_ptr exempt_ptr; ///< pointer to extracted node
/// The size of hash_type in bytes, see \p feldman_hashset::traits::hash_size for explanation
static constexpr size_t const c_hash_size = base_class::c_hash_size;
/// Level statistics
typedef feldman_hashset::level_statistics level_statistics;
protected:
//@cond
typedef typename maker::cxx_node_allocator cxx_node_allocator;
typedef std::unique_ptr< value_type, typename maker::node_disposer > scoped_node_ptr;
//@endcond
public:
/// Creates empty set
/**
@param head_bits - 2<sup>head_bits</sup> specifies the size of head array, minimum is 4.
@param array_bits - 2<sup>array_bits</sup> specifies the size of array node, minimum is 2.
Equation for \p head_bits and \p array_bits:
\code
sizeof(hash_type) * 8 == head_bits + N * array_bits
\endcode
where \p N is multi-level array depth.
*/
FeldmanHashSet( size_t head_bits = 8, size_t array_bits = 4 )
: base_class( head_bits, array_bits )
{}
/// Destructs the set and frees all data
~FeldmanHashSet()
{}
/// Inserts new element
/**
The function creates an element with copy of \p val value and then inserts it into the set.
The type \p Q should contain as minimum the complete hash for the element.
The object of \ref value_type should be constructible from a value of type \p Q.
In trivial case, \p Q is equal to \ref value_type.
Returns \p true if \p val is inserted into the set, \p false otherwise.
The function locks RCU internally.
*/
template <typename Q>
bool insert( Q const& val )
{
scoped_node_ptr sp( cxx_node_allocator().New( val ));
if ( base_class::insert( *sp )) {
sp.release();
return true;
}
return false;
}
/// Inserts new element
/**
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-fields of \p val.
The functor signature is:
\code
void func( value_type& val );
\endcode
where \p val is the item inserted. User-defined functor \p f should guarantee that during changing
\p val no any other changes could be made on this set's item by concurrent threads.
The user-defined functor is called only if the inserting is success.
The function locks RCU internally.
*/
template <typename Q, typename Func>
bool insert( Q const& val, Func f )
{
scoped_node_ptr sp( cxx_node_allocator().New( val ));
if ( base_class::insert( *sp, f )) {
sp.release();
return true;
}
return false;
}
/// Updates the element
/**
The operation performs inserting or replacing with lock-free manner.
If the \p val key not found in the set, then the new item created from \p val
will be inserted into the set iff \p bInsert is \p true.
Otherwise, if \p val is found, it is replaced with new item created from \p val
and previous item is disposed.
In both cases \p func functor is called.
The functor \p Func signature:
\code
struct my_functor {
void operator()( value_type& cur, value_type * prev );
};
\endcode
where:
- \p cur - current element
- \p prev - pointer to previous element with such hash. \p prev is \p nullptr
if \p cur was just inserted.
The functor may change non-key fields of the \p item; however, \p func must guarantee
that during changing no any other modifications could be made on this item by concurrent threads.
Returns <tt> std::pair<bool, bool> </tt> where \p first is \p true if operation is successful,
i.e. the item has been inserted or updated,
\p second is \p true if the new item has been added or \p false if the item with key equal to \p val
already exists.
*/
template <typename Q, typename Func>
std::pair<bool, bool> update( Q const& val, Func func, bool bInsert = true )
{
scoped_node_ptr sp( cxx_node_allocator().New( val ));
std::pair<bool, bool> bRes = base_class::do_update( *sp, func, bInsert );
if ( bRes.first )
sp.release();
return bRes;
}
/// Inserts data of type \p value_type created in-place from <tt>std::forward<Args>(args)...</tt>
/**
Returns \p true if inserting successful, \p false otherwise.
*/
template <typename... Args>
bool emplace( Args&&... args )
{
scoped_node_ptr sp( cxx_node_allocator().MoveNew( std::forward<Args>(args)... ));
if ( base_class::insert( *sp )) {
sp.release();
return true;
}
return false;
}
/// Deletes the item from the set
/**
The function searches \p hash in the set,
deletes the item found, and returns \p true.
If that item is not found the function returns \p false.
RCU should not be locked. The function locks RCU internally.
*/
bool erase( hash_type const& hash )
{
return base_class::erase( hash );
}
/// Deletes the item from the set
/**
The function searches \p hash in the set,
call \p f functor with item found, and deltes the element from the set.
The \p Func interface is
\code
struct functor {
void operator()( value_type& item );
};
\endcode
If \p hash is not found the function returns \p false.
RCU should not be locked. The function locks RCU internally.
*/
template <typename Func>
bool erase( hash_type const& hash, Func f )
{
return base_class::erase( hash, f );
}
/// Extracts the item with specified \p hash
/**
The function searches \p hash in the set,
unlinks it from the set, and returns \ref cds::urcu::exempt_ptr "exempt_ptr" pointer to the item found.
If the item with key equal to \p key is not found the function returns an empty \p exempt_ptr.
RCU \p synchronize method can be called. RCU should NOT be locked.
The function does not call the disposer for the item found.
The disposer will be implicitly invoked when the returned object is destroyed or when
its \p release() member function is called.
Example:
\code
typedef cds::container::FeldmanHashSet< cds::urcu::gc< cds::urcu::general_buffered<> >, foo, my_traits > set_type;
set_type theSet;
// ...
typename set_type::exempt_ptr ep( theSet.extract( 5 ));
if ( ep ) {
// Deal with ep
//...
// Dispose returned item.
ep.release();
}
\endcode
*/
exempt_ptr extract( hash_type const& hash )
{
return base_class::extract( hash );
}
/// Finds an item by it's \p hash
/**
The function searches the item by \p hash and calls the functor \p f for item found.
The interface of \p Func functor is:
\code
struct functor {
void operator()( value_type& item );
};
\endcode
where \p item is the item found.
The functor may change non-key fields of \p item. Note that the functor is only guarantee
that \p item cannot be disposed during the functor is executing.
The functor does not serialize simultaneous access to the set's \p item. If such access is
possible you must provide your own synchronization schema on item level to prevent unsafe item modifications.
The function returns \p true if \p hash is found, \p false otherwise.
*/
template <typename Func>
bool find( hash_type const& hash, Func f )
{
return base_class::find( hash, f );
}
/// Checks whether the set contains \p hash
/**
The function searches the item by its \p hash
and returns \p true if it is found, or \p false otherwise.
*/
bool contains( hash_type const& hash )
{
return base_class::contains( hash );
}
/// Finds an item by it's \p hash and returns the item found
/**
The function searches the item by its \p hash
and returns the pointer to the item found.
If \p hash is not found the function returns \p nullptr.
RCU should be locked before the function invocation.
Returned pointer is valid only while RCU is locked.
Usage:
\code
typedef cds::container::FeldmanHashSet< your_template_params > my_set;
my_set theSet;
// ...
{
// lock RCU
my_set::rcu_lock lock;
foo * p = theSet.get( 5 );
if ( p ) {
// Deal with p
//...
}
}
\endcode
*/
value_type * get( hash_type const& hash )
{
return base_class::get( hash );
}
/// Clears the set (non-atomic)
/**
The function unlink all data node from the set.
The function is not atomic but is thread-safe.
After \p %clear() the set may not be empty because another threads may insert items.
*/
void clear()
{
base_class::clear();
}
/// Checks if the set is empty
/**
Emptiness is checked by item counting: if item count is zero then the set is empty.
Thus, the correct item counting feature is an important part of the set implementation.
*/
bool empty() const
{
return base_class::empty();
}
/// Returns item count in the set
size_t size() const
{
return base_class::size();
}
/// Returns const reference to internal statistics
stat const& statistics() const
{
return base_class::statistics();
}
/// Returns the size of head node
size_t head_size() const
{
return base_class::head_size();
}
/// Returns the size of the array node
size_t array_node_size() const
{
return base_class::array_node_size();
}
/// Collects tree level statistics into \p stat
/**
The function traverses the set and collects statistics for each level of the tree
into \p feldman_hashset::level_statistics struct. The element of \p stat[i]
represents statistics for level \p i, level 0 is head array.
The function is thread-safe and may be called in multi-threaded environment.
Result can be useful for estimating efficiency of hash functor you use.
*/
void get_level_statistics(std::vector< feldman_hashset::level_statistics>& stat) const
{
base_class::get_level_statistics(stat);
}
public:
///@name Thread-safe iterators
///@{
/// Bidirectional iterator
/** @anchor cds_container_FeldmanHashSet_rcu_iterators
The set supports thread-safe iterators: you may iterate over the set in multi-threaded environment
under explicit RCU lock.
RCU lock requirement means that inserting or searching is allowed but you must not erase the items from the set
since erasing under RCU lock can lead to a deadlock. However, another thread can call \p erase() safely
while your thread is iterating.
A typical example is:
\code
struct foo {
uint32_t hash;
// ... other fields
uint32_t payload; // only for example
};
struct set_traits: cds::container::feldman_hashset::traits
{
struct hash_accessor {
uint32_t operator()( foo const& src ) const
{
retur src.hash;
}
};
};
typedef cds::urcu::gc< cds::urcu::general_buffered<>> rcu;
typedef cds::container::FeldmanHashSet< rcu, foo, set_traits > set_type;
set_type s;
// ...
// iterate over the set
{
// lock the RCU.
typename set_type::rcu_lock l; // scoped RCU lock
// traverse the set
for ( auto i = s.begin(); i != s.end(); ++i ) {
// deal with i. Remember, erasing is prohibited here!
i->payload++;
}
} // at this point RCU lock is released
\endcode
Each iterator object supports the common interface:
- dereference operators:
@code
value_type [const] * operator ->() noexcept
value_type [const] & operator *() noexcept
@endcode
- pre-increment and pre-decrement. Post-operators is not supported
- equality operators <tt>==</tt> and <tt>!=</tt>.
Iterators are equal iff they point to the same cell of the same array node.
Note that for two iterators \p it1 and \p it2 the condition <tt> it1 == it2 </tt>
does not entail <tt> &(*it1) == &(*it2) </tt>: welcome to concurrent containers
@note It is possible the item can be iterated more that once, for example, if an iterator points to the item
in an array node that is being splitted.
*/
typedef typename base_class::iterator iterator;
typedef typename base_class::const_iterator const_iterator; ///< @ref cds_container_FeldmanHashSet_rcu_iterators "bidirectional const iterator" type
typedef typename base_class::reverse_iterator reverse_iterator; ///< @ref cds_container_FeldmanHashSet_rcu_iterators "bidirectional reverse iterator" type
typedef typename base_class::const_reverse_iterator const_reverse_iterator; ///< @ref cds_container_FeldmanHashSet_rcu_iterators "bidirectional reverse const iterator" type
/// Returns an iterator to the beginning of the set
iterator begin()
{
return base_class::begin();
}
/// Returns an const iterator to the beginning of the set
const_iterator begin() const
{
return base_class::begin();
}
/// Returns an const iterator to the beginning of the set
const_iterator cbegin()
{
return base_class::cbegin();
}
/// Returns an iterator to the element following the last element of the set. This element acts as a placeholder; attempting to access it results in undefined behavior.
iterator end()
{
return base_class::end();
}
/// Returns a const iterator to the element following the last element of the set. This element acts as a placeholder; attempting to access it results in undefined behavior.
const_iterator end() const
{
return base_class::end();
}
/// Returns a const iterator to the element following the last element of the set. This element acts as a placeholder; attempting to access it results in undefined behavior.
const_iterator cend()
{
return base_class::cend();
}
/// Returns a reverse iterator to the first element of the reversed set
reverse_iterator rbegin()
{
return base_class::rbegin();
}
/// Returns a const reverse iterator to the first element of the reversed set
const_reverse_iterator rbegin() const
{
return base_class::rbegin();
}
/// Returns a const reverse iterator to the first element of the reversed set
const_reverse_iterator crbegin()
{
return base_class::crbegin();
}
/// Returns a reverse iterator to the element following the last element of the reversed set
/**
It corresponds to the element preceding the first element of the non-reversed container.
This element acts as a placeholder, attempting to access it results in undefined behavior.
*/
reverse_iterator rend()
{
return base_class::rend();
}
/// Returns a const reverse iterator to the element following the last element of the reversed set
/**
It corresponds to the element preceding the first element of the non-reversed container.
This element acts as a placeholder, attempting to access it results in undefined behavior.
*/
const_reverse_iterator rend() const
{
return base_class::rend();
}
/// Returns a const reverse iterator to the element following the last element of the reversed set
/**
It corresponds to the element preceding the first element of the non-reversed container.
This element acts as a placeholder, attempting to access it results in undefined behavior.
*/
const_reverse_iterator crend()
{
return base_class::crend();
}
///@}
};
}} // namespace cds::container
#endif // #ifndef CDSLIB_CONTAINER_FELDMAN_HASHSET_RCU_H