LCOV - code coverage report
Current view: top level - plugins/unittest - bihash_test.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 200 228 87.7 %
Date: 2023-07-05 22:20:52 Functions: 12 13 92.3 %

          Line data    Source code
       1             : /*
       2             :  * Copyright (c) 2015 Cisco and/or its affiliates.
       3             :  * Licensed under the Apache License, Version 2.0 (the "License");
       4             :  * you may not use this file except in compliance with the License.
       5             :  * You may obtain a copy of the License at:
       6             :  *
       7             :  *     http://www.apache.org/licenses/LICENSE-2.0
       8             :  *
       9             :  * Unless required by applicable law or agreed to in writing, software
      10             :  * distributed under the License is distributed on an "AS IS" BASIS,
      11             :  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             :  * See the License for the specific language governing permissions and
      13             :  * limitations under the License.
      14             :  */
      15             : #include <vlib/vlib.h>
      16             : #include <vppinfra/time.h>
      17             : #include <vppinfra/cache.h>
      18             : #include <vppinfra/error.h>
      19             : #include <sys/resource.h>
      20             : #include <stdio.h>
      21             : #include <pthread.h>
      22             : 
      23             : #include <vppinfra/bihash_8_8_stats.h>
      24             : #include <vppinfra/bihash_template.h>
      25             : 
      26             : #include <vppinfra/bihash_template.c>
      27             : 
      28             : typedef struct
      29             : {
      30             :   u64 alloc_add;
      31             :   u64 add;
      32             :   u64 split_add;
      33             :   u64 replace;
      34             :   u64 update;
      35             :   u64 del;
      36             :   u64 del_free;
      37             :   u64 linear;
      38             :   u64 resplit;
      39             :   u64 working_copy_lost;
      40             :   u64 *splits;
      41             : } bihash_stats_t;
      42             : 
      43             : typedef struct
      44             : {
      45             :   volatile u32 thread_barrier;
      46             :   volatile u32 threads_running;
      47             :   volatile u64 sequence_number;
      48             :   u64 seed;
      49             :   u32 nbuckets;
      50             :   u32 nitems;
      51             :   u32 ncycles;
      52             :   u32 report_every_n;
      53             :   u32 search_iter;
      54             :   int careful_delete_tests;
      55             :   int verbose;
      56             :   int non_random_keys;
      57             :   u32 nthreads;
      58             :   uword *key_hash;
      59             :   u64 *keys;
      60             :   uword hash_memory_size;
      61             :     BVT (clib_bihash) hash;
      62             :   clib_time_t clib_time;
      63             :   void *global_heap;
      64             : 
      65             :   unformat_input_t *input;
      66             : 
      67             :   /* convenience */
      68             :   vlib_main_t *vlib_main;
      69             : 
      70             :     CLIB_CACHE_LINE_ALIGN_MARK (stat_align);
      71             :   bihash_stats_t stats;
      72             : 
      73             : } bihash_test_main_t;
      74             : 
      75             : static bihash_test_main_t bihash_test_main;
      76             : 
      77             : u8 *
      78           2 : format_bihash_stats (u8 * s, va_list * args)
      79             : {
      80           2 :   BVT (clib_bihash) * h = va_arg (*args, BVT (clib_bihash) *);
      81           2 :   int verbose = va_arg (*args, int);
      82             :   int i;
      83           2 :   bihash_stats_t *sp = h->inc_stats_context;
      84             : 
      85             : #define _(a) s = format (s, "%20s: %lld\n", #a, sp->a);
      86           2 :   foreach_bihash_stat;
      87             : #undef _
      88           7 :   for (i = 0; i < vec_len (sp->splits); i++)
      89             :     {
      90           5 :       if (sp->splits[i] > 0 || verbose)
      91           5 :         s = format (s, "    splits[%d]: %lld\n", 1 << i, sp->splits[i]);
      92             :     }
      93           2 :   return s;
      94             : }
      95             : 
      96             : 
      97             : static void
      98     6201220 : inc_stats_callback (BVT (clib_bihash) * h, int stat_id, u64 count)
      99             : {
     100     6201220 :   uword *statp = h->inc_stats_context;
     101             :   bihash_stats_t *for_splits;
     102             : 
     103     6201220 :   if (PREDICT_TRUE (stat_id * sizeof (u64)
     104             :                     < STRUCT_OFFSET_OF (bihash_stats_t, splits)))
     105             :     {
     106     6050260 :       statp[stat_id] += count;
     107     6050260 :       return;
     108             :     }
     109             : 
     110      150950 :   for_splits = h->inc_stats_context;
     111      150950 :   vec_validate (for_splits->splits, count);
     112      150950 :   for_splits->splits[count] += 1;
     113             : }
     114             : 
     115             : 
     116             : static clib_error_t *
     117           1 : test_bihash_vec64 (bihash_test_main_t * tm)
     118             : {
     119           1 :   u32 user_buckets = 1228800;
     120           1 :   u32 user_memory_size = 209715200;
     121             :   BVT (clib_bihash_kv) kv;
     122             :   int i, j;
     123             :   f64 before;
     124           1 :   f64 *cum_times = 0;
     125             :   BVT (clib_bihash) * h;
     126             : 
     127           1 :   h = &tm->hash;
     128             : 
     129           1 :   BV (clib_bihash_init) (h, "test", user_buckets, user_memory_size);
     130           1 :   BV (clib_bihash_set_stats_callback) (h, inc_stats_callback, &tm->stats);
     131             : 
     132           1 :   before = clib_time_now (&tm->clib_time);
     133             : 
     134          11 :   for (j = 0; j < 10; j++)
     135             :     {
     136       45020 :       for (i = 1; i <= j * 1000 + 1; i++)
     137             :         {
     138       45010 :           kv.key = i;
     139       45010 :           kv.value = 1;
     140             : 
     141       45010 :           BV (clib_bihash_add_del) (h, &kv, 1 /* is_add */ );
     142             :         }
     143             : 
     144          10 :       vec_add1 (cum_times, clib_time_now (&tm->clib_time) - before);
     145             :     }
     146             : 
     147          11 :   for (j = 0; j < vec_len (cum_times); j++)
     148          10 :     fformat (stdout, "Cum time for %d: %.4f (us)\n", (j + 1) * 1000,
     149          10 :              cum_times[j] * 1e6);
     150             : 
     151           1 :   return 0;
     152             : }
     153             : 
     154             : void *
     155           2 : test_bihash_thread_fn (void *arg)
     156             : {
     157             :   BVT (clib_bihash) * h;
     158             :   BVT (clib_bihash_kv) kv;
     159           2 :   bihash_test_main_t *tm = &bihash_test_main;
     160             :   int i, j;
     161           2 :   u32 my_thread_index = (uword) arg;
     162             : 
     163           2 :   while (tm->thread_barrier)
     164             :     ;
     165             : 
     166           2 :   h = &tm->hash;
     167             : 
     168          22 :   for (i = 0; i < tm->ncycles; i++)
     169             :     {
     170     2000020 :       for (j = 0; j < tm->nitems; j++)
     171             :         {
     172     2000000 :           kv.key = ((u64) my_thread_index << 32) | (u64) j;
     173     2000000 :           kv.value = ((u64) my_thread_index << 32) | (u64) j;
     174     2000000 :           (void) __atomic_add_fetch (&tm->sequence_number, 1,
     175             :                                      __ATOMIC_ACQUIRE);
     176     2000000 :           BV (clib_bihash_add_del) (h, &kv, 1 /* is_add */ );
     177             :         }
     178     2000020 :       for (j = 0; j < tm->nitems; j++)
     179             :         {
     180     2000000 :           kv.key = ((u64) my_thread_index << 32) | (u64) j;
     181     2000000 :           kv.value = ((u64) my_thread_index << 32) | (u64) j;
     182     2000000 :           (void) __atomic_add_fetch (&tm->sequence_number, 1,
     183             :                                      __ATOMIC_ACQUIRE);
     184     2000000 :           BV (clib_bihash_add_del) (h, &kv, 0 /* is_add */ );
     185             :         }
     186             :     }
     187             : 
     188           2 :   (void) __atomic_sub_fetch (&tm->threads_running, 1, __ATOMIC_ACQUIRE);
     189           2 :   pthread_exit (0);
     190             :   return (0);                   /* not so much */
     191             : }
     192             : 
     193             : static clib_error_t *
     194           1 : test_bihash_threads (bihash_test_main_t * tm)
     195             : {
     196             :   int i;
     197             :   pthread_t handle;
     198             :   BVT (clib_bihash) * h;
     199             :   int rv;
     200             :   f64 before, after, delta;
     201             : 
     202           1 :   h = &tm->hash;
     203             : 
     204           1 :   BV (clib_bihash_init) (h, "test", tm->nbuckets, tm->hash_memory_size);
     205           1 :   BV (clib_bihash_set_stats_callback) (h, inc_stats_callback, &tm->stats);
     206             : 
     207           1 :   tm->thread_barrier = 1;
     208             : 
     209             :   /* Start the worker threads */
     210           1 :   tm->threads_running = 0;
     211           3 :   for (i = 0; i < tm->nthreads; i++)
     212             :     {
     213           2 :       rv = pthread_create (&handle, NULL, test_bihash_thread_fn,
     214           2 :                            (void *) (uword) i);
     215           2 :       if (rv)
     216           0 :         clib_unix_warning ("pthread_create returned %d", rv);
     217             :       else
     218           2 :         tm->threads_running++;
     219             :     }
     220           1 :   tm->sequence_number = 0;
     221           1 :   CLIB_MEMORY_BARRIER ();
     222             : 
     223             :   /* start the workers */
     224           1 :   before = vlib_time_now (tm->vlib_main);
     225           1 :   tm->thread_barrier = 0;
     226             : 
     227    19602000 :   while (tm->threads_running > 0)
     228    19602000 :     CLIB_PAUSE ();
     229             : 
     230           1 :   after = vlib_time_now (tm->vlib_main);
     231           1 :   delta = after - before;
     232             : 
     233           1 :   clib_warning ("%lld items in %.2f seconds, %.2f items/sec",
     234             :                 (u64) tm->nthreads * (u64) tm->nitems, delta,
     235             :                 delta >
     236             :                 0.0 ? ((f64) ((u64) tm->nthreads * (u64) tm->nitems)) /
     237             :                 delta : 0.0);
     238             : 
     239           1 :   fformat (stdout, "Stats:\n%U", format_bihash_stats, h, 1 /* verbose */ );
     240           1 :   BV (clib_bihash_free) (h);
     241           1 :   return 0;
     242             : }
     243             : 
     244             : /*
     245             :  * Callback to blow up spectacularly if anything remains in the table
     246             :  */
     247             : static int
     248           0 : count_items (BVT (clib_bihash_kv) * kvp, void *notused)
     249             : {
     250           0 :   _clib_error (CLIB_ERROR_ABORT, 0, 0,
     251             :                "bihash test FAILED, items left in table!");
     252           0 :   return (BIHASH_WALK_CONTINUE);
     253             : }
     254             : 
     255             : static clib_error_t *
     256           1 : test_bihash (bihash_test_main_t * tm)
     257             : {
     258             :   int i, j;
     259             :   uword *p;
     260             :   uword total_searches;
     261             :   f64 before, delta;
     262             :   BVT (clib_bihash) * h;
     263             :   BVT (clib_bihash_kv) kv;
     264             :   u32 acycle;
     265             : 
     266           1 :   h = &tm->hash;
     267             : 
     268           1 :   BV (clib_bihash_init) (h, "test", tm->nbuckets, tm->hash_memory_size);
     269           1 :   BV (clib_bihash_set_stats_callback) (h, inc_stats_callback, &tm->stats);
     270             : 
     271          11 :   for (acycle = 0; acycle < tm->ncycles; acycle++)
     272             :     {
     273          10 :       if ((acycle % tm->report_every_n) == 0)
     274             :         {
     275           1 :           fformat (stdout, "Cycle %lld out of %lld...\n",
     276             :                    acycle, tm->ncycles);
     277             : 
     278           1 :           fformat (stdout, "Pick %lld unique %s keys...\n",
     279           1 :                    tm->nitems, tm->non_random_keys ? "non-random" : "random");
     280             :         }
     281             : 
     282     1000010 :       for (i = 0; i < tm->nitems; i++)
     283             :         {
     284             :           u64 rndkey;
     285             : 
     286     1000000 :           if (tm->non_random_keys == 0)
     287             :             {
     288             : 
     289     1000000 :             again:
     290     1000000 :               rndkey = random_u64 (&tm->seed);
     291             : 
     292     1000000 :               p = hash_get (tm->key_hash, rndkey);
     293     1000000 :               if (p)
     294           0 :                 goto again;
     295             :             }
     296             :           else
     297           0 :             rndkey = (u64) (i + 1) << 16;
     298     1000000 :           rndkey += acycle;
     299             : 
     300     1000000 :           hash_set (tm->key_hash, rndkey, i + 1);
     301     1000000 :           vec_add1 (tm->keys, rndkey);
     302             :         }
     303             : 
     304             : 
     305          10 :       if ((acycle % tm->report_every_n) == 0)
     306           1 :         fformat (stdout, "Add items...\n");
     307             : 
     308     1000010 :       for (i = 0; i < tm->nitems; i++)
     309             :         {
     310     1000000 :           kv.key = tm->keys[i];
     311     1000000 :           kv.value = i + 1;
     312             : 
     313     1000000 :           BV (clib_bihash_add_del) (h, &kv, 1 /* is_add */ );
     314             : 
     315     1000000 :           if (tm->verbose > 1)
     316             :             {
     317           0 :               fformat (stdout, "--------------------\n");
     318           0 :               fformat (stdout, "After adding key %llu value %lld...\n",
     319           0 :                        tm->keys[i], (u64) (i + 1));
     320           0 :               fformat (stdout, "%U", BV (format_bihash), h,
     321             :                        2 /* very verbose */ );
     322             :             }
     323             :         }
     324             : 
     325          10 :       if ((acycle % tm->report_every_n) == 0)
     326             :         {
     327           1 :           fformat (stdout, "%U", BV (format_bihash), h,
     328             :                    0 /* very verbose */ );
     329             : 
     330           1 :           fformat (stdout, "Search for items %d times...\n", tm->search_iter);
     331             :         }
     332             : 
     333          10 :       before = clib_time_now (&tm->clib_time);
     334             : 
     335          20 :       for (j = 0; j < tm->search_iter; j++)
     336             :         {
     337     1000010 :           for (i = 0; i < tm->nitems; i++)
     338             :             {
     339     1000000 :               kv.key = tm->keys[i];
     340     1000000 :               if (BV (clib_bihash_search) (h, &kv, &kv) < 0)
     341             :                 {
     342           0 :                   if (BV (clib_bihash_search) (h, &kv, &kv) < 0)
     343             :                     {
     344           0 :                       return clib_error_return (
     345             :                         0, "[%d] search for key %lld failed unexpectedly\n", i,
     346             :                         tm->keys[i]);
     347             :                     }
     348             :                 }
     349     1000000 :               if (kv.value != (u64) (i + 1))
     350           0 :                 return clib_error_return (
     351             :                   0, "[%d] search for key %lld returned %lld, not %lld\n", i,
     352             :                   tm->keys, kv.value, (u64) (i + 1));
     353             :             }
     354             :         }
     355             : 
     356          10 :       if ((acycle % tm->report_every_n) == 0)
     357             :         {
     358           1 :           delta = clib_time_now (&tm->clib_time) - before;
     359           1 :           total_searches = (uword) tm->search_iter * (uword) tm->nitems;
     360             : 
     361           1 :           if (delta > 0)
     362           1 :             fformat (stdout, "%.f searches per second\n",
     363           1 :                      ((f64) total_searches) / delta);
     364             : 
     365           1 :           fformat (stdout, "%lld searches in %.6f seconds\n", total_searches,
     366             :                    delta);
     367             : 
     368           1 :           fformat (stdout, "Standard E-hash search for items %d times...\n",
     369             :                    tm->search_iter);
     370             :         }
     371             : 
     372          10 :       before = clib_time_now (&tm->clib_time);
     373             : 
     374          20 :       for (j = 0; j < tm->search_iter; j++)
     375             :         {
     376     1000010 :           for (i = 0; i < tm->nitems; i++)
     377             :             {
     378     1000000 :               p = hash_get (tm->key_hash, tm->keys[i]);
     379     1000000 :               if (p == 0 || p[0] != (uword) (i + 1))
     380           0 :                 return clib_error_return (0, "ugh, couldn't find %lld\n",
     381             :                                           tm->keys[i]);
     382             :             }
     383             :         }
     384             : 
     385          10 :       delta = clib_time_now (&tm->clib_time) - before;
     386          10 :       total_searches = (uword) tm->search_iter * (uword) tm->nitems;
     387             : 
     388          10 :       if ((acycle % tm->report_every_n) == 0)
     389             :         {
     390           1 :           fformat (stdout, "%lld searches in %.6f seconds\n",
     391             :                    total_searches, delta);
     392             : 
     393           1 :           if (delta > 0)
     394           1 :             fformat (stdout, "%.f searches per second\n",
     395           1 :                      ((f64) total_searches) / delta);
     396           1 :           fformat (stdout, "Delete items...\n");
     397             :         }
     398             : 
     399     1000010 :       for (i = 0; i < tm->nitems; i++)
     400             :         {
     401             :           int j;
     402             :           int rv;
     403             : 
     404     1000000 :           kv.key = tm->keys[i];
     405     1000000 :           kv.value = (u64) (i + 1);
     406     1000000 :           rv = BV (clib_bihash_add_del) (h, &kv, 0 /* is_add */ );
     407             : 
     408     1000000 :           if (rv < 0)
     409           0 :             return clib_error_return (
     410             :               0, "delete key %lld not ok but should be", tm->keys[i]);
     411             : 
     412     1000000 :           if (tm->careful_delete_tests)
     413             :             {
     414           0 :               for (j = 0; j < tm->nitems; j++)
     415             :                 {
     416           0 :                   kv.key = tm->keys[j];
     417           0 :                   rv = BV (clib_bihash_search) (h, &kv, &kv);
     418           0 :                   if (j <= i && rv >= 0)
     419             :                     {
     420           0 :                       return clib_error_return (
     421             :                         0, "i %d j %d search ok but should not be, value %lld",
     422             :                         i, j, kv.value);
     423             :                     }
     424           0 :                   if (j > i && rv < 0)
     425             :                     {
     426           0 :                       return clib_error_return (
     427             :                         0, "i %d j %d search not ok but should be", i, j);
     428             :                     }
     429             :                 }
     430             :             }
     431             :         }
     432          10 :       if ((acycle % tm->report_every_n) == 0)
     433             :         {
     434             :           struct rusage r_usage;
     435           1 :           getrusage (RUSAGE_SELF, &r_usage);
     436           1 :           fformat (stdout, "Kernel RSS: %ld bytes\n", r_usage.ru_maxrss);
     437           1 :           fformat (stdout, "%U\n", BV (format_bihash), h, 0 /* verbose */ );
     438             :         }
     439             : 
     440             :       /* Clean up side-bet hash table and random key vector */
     441          10 :       hash_free (tm->key_hash);
     442          10 :       vec_reset_length (tm->keys);
     443             :       /* Recreate hash table if we're going to need it again */
     444          10 :       if (acycle != (tm->ncycles - 1))
     445           9 :         tm->key_hash = hash_create (tm->nitems, sizeof (uword));
     446             :     }
     447             : 
     448           1 :   fformat (stdout, "End of run, should be empty...\n");
     449             : 
     450           1 :   fformat (stdout, "%U", BV (format_bihash), h, 0 /* very verbose */ );
     451             : 
     452             :   /* ASSERTs if any items remain */
     453           1 :   BV (clib_bihash_foreach_key_value_pair) (h, count_items, 0);
     454             : 
     455           1 :   fformat (stdout, "Stats:\n%U", format_bihash_stats, h, 1 /* verbose */ );
     456             : 
     457           1 :   BV (clib_bihash_free) (h);
     458             : 
     459           1 :   vec_free (tm->keys);
     460           1 :   hash_free (tm->key_hash);
     461             : 
     462           1 :   return 0;
     463             : }
     464             : 
     465             : static clib_error_t *
     466           3 : test_bihash_command_fn (vlib_main_t * vm,
     467             :                         unformat_input_t * input, vlib_cli_command_t * cmd)
     468             : {
     469           3 :   bihash_test_main_t *tm = &bihash_test_main;
     470             :   clib_error_t *error;
     471           3 :   int which = 0;
     472             : 
     473           3 :   tm->hash_memory_size = 32ULL << 20;
     474           3 :   tm->nbuckets = 32000;
     475           3 :   tm->nitems = 100000;
     476           3 :   tm->ncycles = 10;
     477           3 :   tm->report_every_n = 50000;
     478           3 :   tm->seed = 0x1badf00d;
     479           3 :   tm->search_iter = 1;
     480             : 
     481           3 :   memset (&tm->stats, 0, sizeof (tm->stats));
     482             : 
     483          10 :   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
     484             :     {
     485           7 :       if (unformat (input, "seed %u", &tm->seed))
     486             :         ;
     487             : 
     488           7 :       else if (unformat (input, "nbuckets %d", &tm->nbuckets))
     489             :         ;
     490           6 :       else if (unformat (input, "non-random-keys"))
     491           0 :         tm->non_random_keys = 1;
     492           6 :       else if (unformat (input, "nitems %d", &tm->nitems))
     493             :         ;
     494           6 :       else if (unformat (input, "ncycles %d", &tm->ncycles))
     495             :         ;
     496           6 :       else if (unformat (input, "careful %d", &tm->careful_delete_tests))
     497             :         ;
     498           4 :       else if (unformat (input, "verbose %d", &tm->verbose))
     499             :         ;
     500           2 :       else if (unformat (input, "search %d", &tm->search_iter))
     501             :         ;
     502           2 :       else if (unformat (input, "report-every %d", &tm->report_every_n))
     503             :         ;
     504           2 :       else if (unformat (input, "memory-size %U",
     505             :                          unformat_memory_size, &tm->hash_memory_size))
     506             :         ;
     507           2 :       else if (unformat (input, "vec64"))
     508           1 :         which = 1;
     509           1 :       else if (unformat (input, "threads %u", &tm->nthreads))
     510           1 :         which = 2;
     511           0 :       else if (unformat (input, "verbose"))
     512           0 :         tm->verbose = 1;
     513             :       else
     514           0 :         return clib_error_return (0, "unknown input '%U'",
     515             :                                   format_unformat_error, input);
     516             :     }
     517             : 
     518             :   /* Preallocate hash table, key vector */
     519           3 :   tm->key_hash = hash_create (tm->nitems, sizeof (uword));
     520           3 :   vec_validate (tm->keys, tm->nitems - 1);
     521           3 :   vec_set_len (tm->keys, 0);
     522             : 
     523           3 :   switch (which)
     524             :     {
     525           1 :     case 0:
     526           1 :       error = test_bihash (tm);
     527           1 :       break;
     528             : 
     529           1 :     case 1:
     530           1 :       error = test_bihash_vec64 (tm);
     531           1 :       break;
     532             : 
     533           1 :     case 2:
     534           1 :       error = test_bihash_threads (tm);
     535           1 :       break;
     536             : 
     537           0 :     default:
     538           0 :       return clib_error_return (0, "no such test?");
     539             :     }
     540             : 
     541           3 :   return error;
     542             : }
     543             : 
     544             : /* *INDENT-OFF* */
     545       16239 : VLIB_CLI_COMMAND (test_bihash_command, static) =
     546             : {
     547             :   .path = "test bihash",
     548             :   .short_help = "test bihash",
     549             :   .function = test_bihash_command_fn,
     550             : };
     551             : /* *INDENT-ON* */
     552             : 
     553             : static clib_error_t *
     554         559 : bihash_test_init (vlib_main_t * vm)
     555             : {
     556         559 :   bihash_test_main_t *tm = &bihash_test_main;
     557             : 
     558         559 :   clib_time_init (&tm->clib_time);
     559         559 :   tm->vlib_main = vm;
     560         559 :   return (0);
     561             : }
     562             : 
     563        1119 : VLIB_INIT_FUNCTION (bihash_test_init);
     564             : 
     565             : /*
     566             :  * fd.io coding-style-patch-verification: ON
     567             :  *
     568             :  * Local Variables:
     569             :  * eval: (c-set-style "gnu")
     570             :  * End:
     571             :  */

Generated by: LCOV version 1.14