Test tuning
[libcds.git] / tests / unit / map2 / map_insdel_func.h
1 //$$CDS-header$$
2
3 #include <functional>
4 #include <mutex>    //unique_lock
5 #include "map2/map_type.h"
6 #include "cppunit/thread.h"
7
8 #include <cds/sync/spinlock.h>
9 #include <vector>
10
11 namespace map2 {
12
13 #define TEST_CASE(TAG, X)  void X();
14
15     class Map_InsDel_func: public CppUnitMini::TestCase
16     {
17     public:
18         size_t c_nMapSize = 1000000;      // map size
19         size_t c_nInsertThreadCount = 4;  // count of insertion thread
20         size_t c_nDeleteThreadCount = 4;  // count of deletion thread
21         size_t c_nUpdateThreadCount = 4;  // count of updating thread
22         size_t c_nThreadPassCount   = 4;  // pass count for each thread
23         size_t c_nMaxLoadFactor = 8;      // maximum load factor
24         bool   c_bPrintGCState = true;
25
26         size_t c_nCuckooInitialSize = 1024;// initial size for CuckooMap
27         size_t c_nCuckooProbesetSize = 16; // CuckooMap probeset size (only for list-based probeset)
28         size_t c_nCuckooProbesetThreshold = 0; // CUckooMap probeset threshold (o - use default)
29
30         size_t c_nFeldmanMap_HeadBits = 10;
31         size_t c_nFeldmanMap_ArrayBits = 4;
32
33         size_t  c_nLoadFactor;  // current load factor
34
35     private:
36         typedef size_t  key_type;
37         struct value_type {
38             size_t      nKey;
39             size_t      nData;
40             atomics::atomic<size_t> nUpdateCall;
41             atomics::atomic<bool>   bInitialized;
42             cds::OS::ThreadId       threadId;   // inserter thread id
43
44             typedef cds::sync::spin_lock< cds::backoff::pause > lock_type;
45             mutable lock_type   m_access;
46
47             value_type()
48                 : nKey(0)
49                 , nData(0)
50                 , nUpdateCall(0)
51                 , bInitialized( false )
52                 , threadId( cds::OS::get_current_thread_id())
53             {}
54
55             value_type( value_type const& s )
56                 : nKey(s.nKey)
57                 , nData(s.nData)
58                 , nUpdateCall(s.nUpdateCall.load(atomics::memory_order_relaxed))
59                 , bInitialized( s.bInitialized.load(atomics::memory_order_relaxed))
60                 , threadId( cds::OS::get_current_thread_id())
61             {}
62
63             // boost::container::flat_map requires operator =
64             value_type& operator=( value_type const& v )
65             {
66                 nKey = v.nKey;
67                 nData = v.nData;
68                 nUpdateCall.store( v.nUpdateCall.load(atomics::memory_order_relaxed), atomics::memory_order_relaxed );
69                 bInitialized.store(v.bInitialized.load(atomics::memory_order_relaxed), atomics::memory_order_relaxed);
70
71                 return *this;
72             }
73         };
74
75         typedef std::vector<key_type>   key_array;
76         key_array                       m_arrValues;
77
78         template <class Map>
79         class Inserter: public CppUnitMini::TestThread
80         {
81             Map&     m_Map;
82
83             virtual Inserter *    clone()
84             {
85                 return new Inserter( *this );
86             }
87
88             struct insert_functor {
89                 size_t nTestFunctorRef;
90
91                 insert_functor()
92                     : nTestFunctorRef(0)
93                 {}
94
95                 template <typename Pair>
96                 void operator()( Pair& val )
97                 {
98                     operator()( val.first, val.second );
99                 }
100
101                 template <typename Key, typename Val >
102                 void operator()( Key const& key, Val& v )
103                 {
104                     std::unique_lock< typename value_type::lock_type> ac( v.m_access );
105
106                     v.nKey  = key;
107                     v.nData = key * 8;
108
109                     ++nTestFunctorRef;
110                     v.bInitialized.store( true, atomics::memory_order_relaxed);
111                 }
112             };
113
114         public:
115             size_t  m_nInsertSuccess;
116             size_t  m_nInsertFailed;
117
118             size_t  m_nTestFunctorRef;
119
120         public:
121             Inserter( CppUnitMini::ThreadPool& pool, Map& rMap )
122                 : CppUnitMini::TestThread( pool )
123                 , m_Map( rMap )
124             {}
125             Inserter( Inserter& src )
126                 : CppUnitMini::TestThread( src )
127                 , m_Map( src.m_Map )
128             {}
129
130             Map_InsDel_func&  getTest()
131             {
132                 return reinterpret_cast<Map_InsDel_func&>( m_Pool.m_Test );
133             }
134
135             virtual void init() { cds::threading::Manager::attachThread()   ; }
136             virtual void fini() { cds::threading::Manager::detachThread()   ; }
137
138             virtual void test()
139             {
140                 Map& rMap = m_Map;
141
142                 m_nInsertSuccess =
143                     m_nInsertFailed =
144                     m_nTestFunctorRef = 0;
145
146                 // func is passed by reference
147                 insert_functor  func;
148                 key_array const& arr = getTest().m_arrValues;
149                 size_t const nPassCount = getTest().c_nThreadPassCount;
150
151                 if ( m_nThreadNo & 1 ) {
152                     for ( size_t nPass = 0; nPass < nPassCount; ++nPass ) {
153                         for ( key_array::const_iterator it = arr.begin(), itEnd = arr.end(); it != itEnd; ++it ) {
154                             if ( rMap.insert_with( *it, std::ref(func)))
155                                 ++m_nInsertSuccess;
156                             else
157                                 ++m_nInsertFailed;
158                         }
159                     }
160                 }
161                 else {
162                     for ( size_t nPass = 0; nPass < nPassCount; ++nPass ) {
163                         for ( key_array::const_reverse_iterator it = arr.rbegin(), itEnd = arr.rend(); it != itEnd; ++it ) {
164                             if ( rMap.insert_with( *it, std::ref(func)))
165                                 ++m_nInsertSuccess;
166                             else
167                                 ++m_nInsertFailed;
168                         }
169                     }
170                 }
171
172                 m_nTestFunctorRef = func.nTestFunctorRef;
173             }
174         };
175
176         template <class Map>
177         class Updater: public CppUnitMini::TestThread
178         {
179             Map&     m_Map;
180
181             virtual Updater *    clone()
182             {
183                 return new Updater( *this );
184             }
185
186             struct update_functor {
187                 size_t  nCreated;
188                 size_t  nModified;
189
190                 update_functor()
191                     : nCreated(0)
192                     , nModified(0)
193                 {}
194
195                 template <typename Key, typename Val>
196                 void operator()( bool bNew, Key const& key, Val& v )
197                 {
198                     std::unique_lock<typename value_type::lock_type> ac( v.m_access );
199                     if ( bNew ) {
200                         ++nCreated;
201                         v.nKey = key;
202                         v.nData = key * 8;
203                         v.bInitialized.store( true, atomics::memory_order_relaxed);
204                     }
205                     else {
206                         assert( v.bInitialized.load( atomics::memory_order_relaxed ));
207                         v.nUpdateCall.fetch_add( 1, atomics::memory_order_relaxed );
208                         ++nModified;
209                     }
210                 }
211
212                 template <typename Pair>
213                 void operator()( bool bNew, Pair& val )
214                 {
215                     operator()( bNew, val.first, val.second );
216                 }
217
218                 // For FeldmanHashMap
219                 template <typename Val>
220                 void operator()( Val& cur, Val * old )
221                 {
222                     if ( old )
223                         cur.second.bInitialized.store( true, atomics::memory_order_release );
224                     operator()( old == nullptr, cur.first, cur.second );
225                 }
226
227             private:
228                 update_functor(const update_functor& ) = delete;
229             };
230
231         public:
232             size_t  m_nUpdateFailed;
233             size_t  m_nUpdateCreated;
234             size_t  m_nUpdateExisted;
235             size_t  m_nFunctorCreated;
236             size_t  m_nFunctorModified;
237
238         public:
239             Updater( CppUnitMini::ThreadPool& pool, Map& rMap )
240                 : CppUnitMini::TestThread( pool )
241                 , m_Map( rMap )
242             {}
243             Updater( Updater& src )
244                 : CppUnitMini::TestThread( src )
245                 , m_Map( src.m_Map )
246             {}
247
248             Map_InsDel_func&  getTest()
249             {
250                 return reinterpret_cast<Map_InsDel_func&>( m_Pool.m_Test );
251             }
252
253             virtual void init() { cds::threading::Manager::attachThread()   ; }
254             virtual void fini() { cds::threading::Manager::detachThread()   ; }
255
256             virtual void test()
257             {
258                 Map& rMap = m_Map;
259
260                 m_nUpdateCreated =
261                     m_nUpdateExisted =
262                     m_nUpdateFailed = 0;
263
264                 update_functor func;
265
266                 key_array const& arr = getTest().m_arrValues;
267                 size_t const nPassCount = getTest().c_nThreadPassCount;
268
269                 if ( m_nThreadNo & 1 ) {
270                     for ( size_t nPass = 0; nPass < nPassCount; ++nPass ) {
271                         for ( key_array::const_iterator it = arr.begin(), itEnd = arr.end(); it != itEnd; ++it ) {
272                             std::pair<bool, bool> ret = rMap.update( *it, std::ref( func ));
273                             if ( ret.first  ) {
274                                 if ( ret.second )
275                                     ++m_nUpdateCreated;
276                                 else
277                                     ++m_nUpdateExisted;
278                             }
279                             else
280                                 ++m_nUpdateFailed;
281                         }
282                     }
283                 }
284                 else {
285                     for ( size_t nPass = 0; nPass < nPassCount; ++nPass ) {
286                         for ( key_array::const_reverse_iterator it = arr.rbegin(), itEnd = arr.rend(); it != itEnd; ++it ) {
287                             std::pair<bool, bool> ret = rMap.update( *it, std::ref( func ));
288                             if ( ret.first  ) {
289                                 if ( ret.second )
290                                     ++m_nUpdateCreated;
291                                 else
292                                     ++m_nUpdateExisted;
293                             }
294                             else
295                                 ++m_nUpdateFailed;
296                         }
297                     }
298                 }
299
300                 m_nFunctorCreated = func.nCreated;
301                 m_nFunctorModified = func.nModified;
302             }
303         };
304
305         template <class Map>
306         class Deleter: public CppUnitMini::TestThread
307         {
308             Map&     m_Map;
309             typedef typename Map::mapped_type value_type;
310
311             virtual Deleter *    clone()
312             {
313                 return new Deleter( *this );
314             }
315
316             struct value_container
317             {
318                 size_t      nKeyExpected;
319
320                 size_t      nSuccessItem;
321                 size_t      nFailedItem;
322
323                 value_container()
324                     : nSuccessItem(0)
325                     , nFailedItem(0)
326                 {}
327             };
328
329             struct erase_functor {
330                 value_container     m_cnt;
331
332                 template <typename Key, typename Val>
333                 void operator()( Key const& /*key*/, Val& v )
334                 {
335                     while ( true ) {
336                         if ( v.bInitialized.load( atomics::memory_order_relaxed )) {
337                             std::unique_lock< typename value_type::lock_type>    ac( v.m_access );
338
339                             if ( m_cnt.nKeyExpected == v.nKey && m_cnt.nKeyExpected * 8 == v.nData )
340                                 ++m_cnt.nSuccessItem;
341                             else
342                                 ++m_cnt.nFailedItem;
343                             v.nData++;
344                             v.nKey = 0;
345                             break;
346                         }
347                         else
348                             cds::backoff::yield()();
349                     }
350                 }
351
352                 template <typename Pair>
353                 void operator ()( Pair& item )
354                 {
355                     operator()( item.first, item.second );
356                 }
357             };
358
359         public:
360             size_t  m_nDeleteSuccess;
361             size_t  m_nDeleteFailed;
362
363             size_t  m_nValueSuccess;
364             size_t  m_nValueFailed;
365
366         public:
367             Deleter( CppUnitMini::ThreadPool& pool, Map& rMap )
368                 : CppUnitMini::TestThread( pool )
369                 , m_Map( rMap )
370             {}
371             Deleter( Deleter& src )
372                 : CppUnitMini::TestThread( src )
373                 , m_Map( src.m_Map )
374             {}
375
376             Map_InsDel_func&  getTest()
377             {
378                 return reinterpret_cast<Map_InsDel_func&>( m_Pool.m_Test );
379             }
380
381             virtual void init() { cds::threading::Manager::attachThread()   ; }
382             virtual void fini() { cds::threading::Manager::detachThread()   ; }
383
384             virtual void test()
385             {
386                 Map& rMap = m_Map;
387
388                 m_nDeleteSuccess =
389                     m_nDeleteFailed = 0;
390
391                 erase_functor   func;
392                 key_array const& arr = getTest().m_arrValues;
393                 size_t const nPassCount = getTest().c_nThreadPassCount;
394
395                 if ( m_nThreadNo & 1 ) {
396                     for ( size_t nPass = 0; nPass < nPassCount; ++nPass ) {
397                         for ( key_array::const_iterator it = arr.begin(), itEnd = arr.end(); it != itEnd; ++it ) {
398                             func.m_cnt.nKeyExpected = *it;
399                             if ( rMap.erase( *it, std::ref(func)))
400                                 ++m_nDeleteSuccess;
401                             else
402                                 ++m_nDeleteFailed;
403                         }
404                     }
405                 }
406                 else {
407                     for ( size_t nPass = 0; nPass < nPassCount; ++nPass ) {
408                         for ( key_array::const_reverse_iterator it = arr.rbegin(), itEnd = arr.rend(); it != itEnd; ++it ) {
409                             func.m_cnt.nKeyExpected = *it;
410                             if ( rMap.erase( *it, std::ref(func)))
411                                 ++m_nDeleteSuccess;
412                             else
413                                 ++m_nDeleteFailed;
414                         }
415                     }
416                 }
417
418                 m_nValueSuccess = func.m_cnt.nSuccessItem;
419                 m_nValueFailed = func.m_cnt.nFailedItem;
420             }
421         };
422
423     protected:
424
425         template <class Map>
426         void do_test( Map& testMap )
427         {
428             typedef Inserter<Map>       InserterThread;
429             typedef Deleter<Map>        DeleterThread;
430             typedef Updater<Map>        UpdaterThread;
431             cds::OS::Timer    timer;
432
433             m_arrValues.clear();
434             m_arrValues.reserve( c_nMapSize );
435             for ( size_t i = 0; i < c_nMapSize; ++i )
436                 m_arrValues.push_back( i );
437             shuffle( m_arrValues.begin(), m_arrValues.end());
438
439             CppUnitMini::ThreadPool pool( *this );
440             pool.add( new InserterThread( pool, testMap ), c_nInsertThreadCount );
441             pool.add( new DeleterThread( pool, testMap ), c_nDeleteThreadCount );
442             pool.add( new UpdaterThread( pool, testMap ), c_nUpdateThreadCount );
443             pool.run();
444             CPPUNIT_MSG( "   Duration=" << pool.avgDuration());
445
446             size_t nInsertSuccess = 0;
447             size_t nInsertFailed = 0;
448             size_t nDeleteSuccess = 0;
449             size_t nDeleteFailed = 0;
450             size_t nDelValueSuccess = 0;
451             size_t nDelValueFailed = 0;
452             size_t nUpdateFailed = 0;
453             size_t nUpdateCreated = 0;
454             size_t nUpdateModified = 0;
455             size_t nEnsFuncCreated = 0;
456             size_t nEnsFuncModified = 0;
457             size_t nInsFuncCalled = 0;
458
459             for ( CppUnitMini::ThreadPool::iterator it = pool.begin(); it != pool.end(); ++it ) {
460                 InserterThread * pThread = dynamic_cast<InserterThread *>( *it );
461                 if ( pThread ) {
462                     nInsertSuccess += pThread->m_nInsertSuccess;
463                     nInsertFailed += pThread->m_nInsertFailed;
464                     nInsFuncCalled += pThread->m_nTestFunctorRef;
465                 }
466                 else {
467                     DeleterThread * p = dynamic_cast<DeleterThread *>( *it );
468                     if ( p ) {
469                         nDeleteSuccess += p->m_nDeleteSuccess;
470                         nDeleteFailed += p->m_nDeleteFailed;
471                         nDelValueSuccess += p->m_nValueSuccess;
472                         nDelValueFailed += p->m_nValueFailed;
473                     }
474                     else {
475                         UpdaterThread * pEns = static_cast<UpdaterThread *>( *it );
476                         nUpdateCreated += pEns->m_nUpdateCreated;
477                         nUpdateModified += pEns->m_nUpdateExisted;
478                         nUpdateFailed += pEns->m_nUpdateFailed;
479                         nEnsFuncCreated += pEns->m_nFunctorCreated;
480                         nEnsFuncModified += pEns->m_nFunctorModified;
481                     }
482                 }
483             }
484
485             CPPUNIT_MSG( "    Totals: Ins succ=" << nInsertSuccess
486                 << " Del succ=" << nDeleteSuccess << "\n"
487                 << "          : Ins fail=" << nInsertFailed
488                 << " Del fail=" << nDeleteFailed << "\n"
489                 << "          : Update succ=" << (nUpdateCreated + nUpdateModified) << " fail=" << nUpdateFailed
490                 << " create=" << nUpdateCreated << " modify=" << nUpdateModified << "\n"
491                 << "          Map size=" << testMap.size()
492                 );
493
494             CPPUNIT_CHECK_EX( nDelValueFailed == 0, "Functor del failed=" << nDelValueFailed );
495             CPPUNIT_CHECK_EX( nDelValueSuccess == nDeleteSuccess,  "Delete success=" << nDeleteSuccess << " functor=" << nDelValueSuccess );
496
497             CPPUNIT_CHECK( nUpdateFailed == 0 );
498
499             CPPUNIT_CHECK_EX( nUpdateCreated == nEnsFuncCreated, "Update created=" << nUpdateCreated << " functor=" << nEnsFuncCreated );
500             CPPUNIT_CHECK_EX( nUpdateModified == nEnsFuncModified, "Update modified=" << nUpdateModified << " functor=" << nEnsFuncModified );
501
502             // nInsFuncCalled is call count of insert functor
503             CPPUNIT_CHECK_EX( nInsFuncCalled == nInsertSuccess, "nInsertSuccess=" << nInsertSuccess << " functor nInsFuncCalled=" << nInsFuncCalled );
504
505             check_before_cleanup( testMap );
506
507             CPPUNIT_MSG( "  Clear map (single-threaded)..." );
508             timer.reset();
509             for ( size_t nItem = 0; nItem < c_nMapSize; ++nItem ) {
510                 testMap.erase( nItem );
511             }
512             CPPUNIT_MSG( "   Duration=" << timer.duration());
513             CPPUNIT_CHECK( testMap.empty());
514
515             additional_check( testMap );
516             print_stat( testMap );
517             additional_cleanup( testMap );
518         }
519
520         template <class Map>
521         void run_test()
522         {
523             CPPUNIT_MSG( "Thread count: insert=" << c_nInsertThreadCount
524                 << " delete=" << c_nDeleteThreadCount
525                 << " update=" << c_nUpdateThreadCount
526                 << " pass count=" << c_nThreadPassCount
527                 << " map size=" << c_nMapSize
528                 );
529
530             if ( Map::c_bLoadFactorDepended ) {
531                 for ( c_nLoadFactor = 1; c_nLoadFactor <= c_nMaxLoadFactor; c_nLoadFactor *= 2 ) {
532                     CPPUNIT_MSG( "Load factor=" << c_nLoadFactor );
533                     Map  testMap( *this );
534                     do_test( testMap );
535                     if ( c_bPrintGCState )
536                         print_gc_state();
537                 }
538             }
539             else {
540                 Map testMap( *this );
541                 do_test( testMap );
542                 if ( c_bPrintGCState )
543                     print_gc_state();
544             }
545         }
546
547         void setUpParams( const CppUnitMini::TestCfg& cfg );
548
549 #   include "map2/map_defs.h"
550         CDSUNIT_DECLARE_MichaelMap
551         CDSUNIT_DECLARE_SplitList
552         CDSUNIT_DECLARE_SkipListMap
553         CDSUNIT_DECLARE_EllenBinTreeMap
554         CDSUNIT_DECLARE_BronsonAVLTreeMap
555         CDSUNIT_DECLARE_FeldmanHashMap_fixed
556         CDSUNIT_DECLARE_FeldmanHashMap_city
557         CDSUNIT_DECLARE_StripedMap
558         CDSUNIT_DECLARE_RefinableMap
559         CDSUNIT_DECLARE_CuckooMap
560
561         CPPUNIT_TEST_SUITE(Map_InsDel_func)
562             CDSUNIT_TEST_MichaelMap
563             CDSUNIT_TEST_SplitList
564             CDSUNIT_TEST_SkipListMap
565             CDSUNIT_TEST_EllenBinTreeMap
566             CDSUNIT_TEST_BronsonAVLTreeMap
567             CDSUNIT_TEST_FeldmanHashMap_fixed
568             CDSUNIT_TEST_FeldmanHashMap_city
569             CDSUNIT_TEST_CuckooMap
570             CDSUNIT_TEST_StripedMap
571             CDSUNIT_TEST_RefinableMap
572         CPPUNIT_TEST_SUITE_END();
573
574     };
575 } // namespace map2