//@endcond
};
+ /// \p IterableList internal statistics
+ template <typename EventCounter = cds::atomicity::event_counter>
+ struct stat {
+ typedef EventCounter event_counter; ///< Event counter type
+
+ event_counter m_nInsertSuccess; ///< Number of success \p insert() operations
+ event_counter m_nInsertFailed; ///< Number of failed \p insert() operations
+ event_counter m_nInsertRetry; ///< Number of attempts to insert new item
+ event_counter m_nUpdateNew; ///< Number of new item inserted for \p update()
+ event_counter m_nUpdateExisting; ///< Number of existing item updates
+ event_counter m_nUpdateFailed; ///< Number of failed \p update() call
+ event_counter m_nUpdateRetry; ///< Number of attempts to update the item
+ event_counter m_nEraseSuccess; ///< Number of successful \p erase(), \p unlink(), \p extract() operations
+ event_counter m_nEraseFailed; ///< Number of failed \p erase(), \p unlink(), \p extract() operations
+ event_counter m_nEraseRetry; ///< Number of attempts to \p erase() an item
+ event_counter m_nFindSuccess; ///< Number of successful \p find() and \p get() operations
+ event_counter m_nFindFailed; ///< Number of failed \p find() and \p get() operations
+
+ event_counter m_nNodeCreated; ///< Number of created internal nodes
+ event_counter m_nNodeRemoved; ///< Number of removed internal nodes
+
+ //@cond
+ void onInsertSuccess() { ++m_nInsertSuccess; }
+ void onInsertFailed() { ++m_nInsertFailed; }
+ void onInsertRetry() { ++m_nInsertRetry; }
+ void onUpdateNew() { ++m_nUpdateNew; }
+ void onUpdateExisting() { ++m_nUpdateExisting; }
+ void onUpdateFailed() { ++m_nUpdateFailed; }
+ void onUpdateRetry() { ++m_nUpdateRetry; }
+ void onEraseSuccess() { ++m_nEraseSuccess; }
+ void onEraseFailed() { ++m_nEraseFailed; }
+ void onEraseRetry() { ++m_nEraseRetry; }
+ void onFindSuccess() { ++m_nFindSuccess; }
+ void onFindFailed() { ++m_nFindFailed; }
+
+ void onNodeCreated() { ++m_nNodeCreated; }
+ void onNodeRemoved() { ++m_nNodeRemoved; }
+ //@endcond
+ };
+
+ /// \p IterableList empty internal statistics
+ struct empty_stat {
+ //@cond
+ void onInsertSuccess() const {}
+ void onInsertFailed() const {}
+ void onInsertRetry() const {}
+ void onUpdateNew() const {}
+ void onUpdateExisting() const {}
+ void onUpdateFailed() const {}
+ void onUpdateRetry() const {}
+ void onEraseSuccess() const {}
+ void onEraseFailed() const {}
+ void onEraseRetry() const {}
+ void onFindSuccess() const {}
+ void onFindFailed() const {}
+
+ void onNodeCreated() const {}
+ void onNodeRemoved() const {}
+ //@endcond
+ };
+
+ //@cond
+ template <typename Stat = iterable_list::stat<>>
+ struct wrapped_stat {
+ typedef Stat stat_type;
+
+ wrapped_stat( stat_type& st )
+ : m_stat( st )
+ {}
+
+ void onInsertSuccess() { m_stat.onInsertSuccess(); }
+ void onInsertFailed() { m_stat.onInsertFailed(); }
+ void onInsertRetry() { m_stat.onInsertRetry(); }
+ void onUpdateNew() { m_stat.onUpdateNew(); }
+ void onUpdateExisting() { m_stat.onUpdateExisting();}
+ void onUpdateFailed() { m_stat.onUpdateFailed(); }
+ void onUpdateRetry() { m_stat.onUpdateRetry(); }
+ void onEraseSuccess() { m_stat.onEraseSuccess(); }
+ void onEraseFailed() { m_stat.onEraseFailed(); }
+ void onEraseRetry() { m_stat.onEraseRetry(); }
+ void onFindSuccess() { m_stat.onFindSuccess(); }
+ void onFindFailed() { m_stat.onFindFailed(); }
+
+ void onNodeCreated() { m_stat.onNodeCreated(); }
+ void onNodeRemoved() { m_stat.onNodeRemoved(); }
+
+ stat_type& m_stat;
+ };
+ //@endcond
+
+
/// \p IterableList traits
struct traits
{
/// Disposer for removing items
typedef opt::v::empty_disposer disposer;
+ /// Internal statistics
+ /**
+ By default, internal statistics is disabled (\p iterable_list::empty_stat).
+ Use \p iterable_list::stat to enable it.
+ */
+ typedef empty_stat stat;
+
/// Item counting feature; by default, disabled. Use \p cds::atomicity::item_counter to enable item counting
typedef atomicity::empty_item_counter item_counter;
of GC schema the disposer may be called asynchronously.
- \p opt::item_counter - the type of item counting feature. Default is disabled (\p atomicity::empty_item_counter).
To enable item counting use \p atomicity::item_counter.
+ - \p opt::stat - internal statistics. By default, it is disabled (\p iterable_list::empty_stat).
+ To enable it use \p iterable_list::stat
- \p opt::memory_model - C++ memory ordering model. Can be \p opt::v::relaxed_ordering (relaxed memory model, the default)
or \p opt::v::sequential_consistent (sequentially consistent memory model).
- \p opt::rcu_check_deadlock - a deadlock checking policy for \ref cds_intrusive_IterableList_rcu "RCU-based IterableList"
any hook in \p T to be stored in the list.
Usually, ordered single-linked list is used as a building block for the hash table implementation.
+ Iterable list is suitable for almost append-only hash table because the list doesn't delete
+ its internal node when erasing a key but it is marked them as empty to be reused in the future.
+ However, plenty of empty nodes degrades performance.
+ Separation of internal nodes and user data implies the need for an allocator for internal node
+ so the iterable list is not fully intrusive. Nevertheless, if you need thread-safe iterator,
+ the iterable list is good choice.
+
The complexity of searching is <tt>O(N)</tt>.
Template arguments:
typedef typename traits::item_counter item_counter; ///< Item counting policy used
typedef typename traits::memory_model memory_model; ///< Memory ordering. See \p cds::opt::memory_model option
typedef typename traits::node_allocator node_allocator; ///< Node allocator
+ typedef typename traits::stat stat; ///< Internal statistics
typedef typename gc::template guarded_ptr< value_type > guarded_ptr; ///< Guarded pointer
atomic_node_ptr m_pHead; ///< Head pointer
item_counter m_ItemCounter; ///< Item counter
+ mutable stat m_Stat; ///< Internal statistics
//@cond
typedef cds::details::Allocator< node_type, node_allocator > cxx_node_allocator;
: m_pHead( nullptr )
{}
+ //@cond
+ template <typename Stat, typename = std::enable_if<std::is_same<stat, iterable_list::wrapped_stat<Stat>>::value >>
+ explicit IterableList( Stat& st )
+ : m_pHead( nullptr )
+ , m_Stat( st )
+ {}
+ //@endcond
+
/// Destroys the list object
~IterableList()
{
If the item \p val is not found in the list, then \p val is inserted
iff \p bInsert is \p true.
- Otherwise, the current element is changed to \p val, the element will be retired later
+ Otherwise, the current element is changed to \p val, the old element will be retired later
by call \p Traits::disposer.
Returns std::pair<bool, bool> where \p first is \p true if operation is successful,
position pos;
while ( true ) {
- if ( search( refHead, val, pos, key_comparator() ) )
+ if ( search( refHead, val, pos, key_comparator() )) {
+ m_Stat.onInsertFailed();
return false;
+ }
if ( link_node( &val, pos ) ) {
++m_ItemCounter;
+ m_Stat.onInsertSuccess();
return true;
}
+
+ m_Stat.onInsertRetry();
}
}
guard.assign( &val );
while ( true ) {
- if ( search( refHead, val, pos, key_comparator() ) )
+ if ( search( refHead, val, pos, key_comparator() ) ) {
+ m_Stat.onInsertFailed();
return false;
+ }
if ( link_node( &val, pos ) ) {
f( val );
++m_ItemCounter;
+ m_Stat.onInsertSuccess();
return true;
}
+
+ m_Stat.onInsertRetry();
}
}
retire_data( pos.pFound );
func( val, pos.pFound );
}
+ m_Stat.onUpdateExisting();
return std::make_pair( true, false );
}
}
else {
- if ( !bInsert )
+ if ( !bInsert ) {
+ m_Stat.onUpdateFailed();
return std::make_pair( false, false );
+ }
- if ( link_node( &val, pos ) ) {
+ if ( link_node( &val, pos )) {
func( val, static_cast<value_type*>( nullptr ));
++m_ItemCounter;
+ m_Stat.onUpdateNew();
return std::make_pair( true, true );
}
}
+
+ m_Stat.onUpdateRetry();
}
}
if ( pos.pFound == &val ) {
if ( unlink_node( pos )) {
--m_ItemCounter;
+ m_Stat.onEraseSuccess();
return true;
}
else
}
else
break;
+
+ m_Stat.onEraseRetry();
}
+
+ m_Stat.onEraseFailed();
return false;
}
if ( unlink_node( pos )) {
f( *pos.pFound );
--m_ItemCounter;
+ m_Stat.onEraseSuccess();
return true;
}
else
bkoff();
+
+ m_Stat.onEraseRetry();
}
+
+ m_Stat.onEraseFailed();
return false;
}
if ( unlink_node( pos )) {
dest.set( pos.pFound );
--m_ItemCounter;
+ m_Stat.onEraseSuccess();
return true;
}
else
bkoff();
+
+ m_Stat.onEraseRetry();
}
+
+ m_Stat.onEraseFailed();
return false;
}
bool find_at( atomic_node_ptr const& refHead, Q const& val, Compare cmp ) const
{
position pos;
- return search( refHead, val, pos, cmp );
+ if ( search( refHead, val, pos, cmp ) ) {
+ m_Stat.onFindSuccess();
+ return true;
+ }
+
+ m_Stat.onFindFailed();
+ return false;
}
template <typename Q, typename Compare, typename Func>
if ( search( refHead, val, pos, cmp )) {
assert( pos.pFound != nullptr );
f( *pos.pFound, val );
+ m_Stat.onFindSuccess();
return true;
}
+
+ m_Stat.onFindFailed();
return false;
}
if ( search( refHead, val, pos, cmp )) {
assert( pos.pCur != nullptr );
assert( pos.pFound != nullptr );
+ m_Stat.onFindSuccess();
return iterator( pos.pCur, pos.pFound );
}
+
+ m_Stat.onFindFailed();
return iterator{};
}
position pos;
if ( search( refHead, val, pos, cmp )) {
guard.set( pos.pFound );
+ m_Stat.onFindSuccess();
return true;
}
+
+ m_Stat.onFindFailed();
return false;
}
//@endcond
private:
//@cond
- static node_type * alloc_node( value_type * pVal )
+ node_type * alloc_node( value_type * pVal )
{
+ m_Stat.onNodeCreated();
return cxx_node_allocator().New( pVal );
}
- static void delete_node( node_type * pNode )
+ void delete_node( node_type * pNode )
{
+ m_Stat.onNodeRemoved();
cxx_node_allocator().Delete( pNode );
}
}
}
- static bool link_node( value_type * pVal, position& pos )
+ bool link_node( value_type * pVal, position& pos )
{
if ( pos.pPrev ) {
if ( pos.pPrev->data.load( memory_model::memory_order_relaxed ) == nullptr ) {
test_hp( l );
}
+ TEST_F( IntrusiveIterableList_DHP, stat )
+ {
+ struct traits: public ci::iterable_list::traits {
+ typedef mock_disposer disposer;
+ typedef intrusive_iterable_list::less< item_type > less;
+ typedef cds::atomicity::item_counter item_counter;
+ typedef cds::intrusive::iterable_list::stat<> stat;
+ };
+ typedef ci::IterableList< gc_type, item_type, traits > list_type;
+
+ list_type l;
+ test_common( l );
+ test_ordered_iterator( l );
+ test_hp( l );
+ }
+
+ TEST_F( IntrusiveIterableList_DHP, wrapped_stat )
+ {
+ struct traits: public ci::iterable_list::traits {
+ typedef mock_disposer disposer;
+ typedef intrusive_iterable_list::less< item_type > less;
+ typedef cds::atomicity::item_counter item_counter;
+ typedef cds::intrusive::iterable_list::wrapped_stat<> stat;
+ };
+ typedef ci::IterableList< gc_type, item_type, traits > list_type;
+
+ traits::stat::stat_type st;
+ list_type l( st );
+ test_common( l );
+ test_ordered_iterator( l );
+ test_hp( l );
+ }
+
} // namespace
test_hp( l );
}
+ TEST_F( IntrusiveIterableList_HP, stat )
+ {
+ struct traits: public ci::iterable_list::traits {
+ typedef mock_disposer disposer;
+ typedef intrusive_iterable_list::less< item_type > less;
+ typedef cds::atomicity::item_counter item_counter;
+ typedef cds::intrusive::iterable_list::stat<> stat;
+ };
+ typedef ci::IterableList< gc_type, item_type, traits > list_type;
+
+ list_type l;
+ test_common( l );
+ test_ordered_iterator( l );
+ test_hp( l );
+ }
+
+ TEST_F( IntrusiveIterableList_HP, wrapped_stat )
+ {
+ struct traits: public ci::iterable_list::traits {
+ typedef mock_disposer disposer;
+ typedef intrusive_iterable_list::less< item_type > less;
+ typedef cds::atomicity::item_counter item_counter;
+ typedef cds::intrusive::iterable_list::wrapped_stat<> stat;
+ };
+ typedef ci::IterableList< gc_type, item_type, traits > list_type;
+
+ traits::stat::stat_type st;
+
+ list_type l( st );
+ test_common( l );
+ test_ordered_iterator( l );
+ test_hp( l );
+ }
+
} // namespace