bihash: add support for reuse of expired entry when bucket is full (VPP-1272)
[vpp.git] / src / vppinfra / test_bihash_template.c
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 <vppinfra/time.h>
16 #include <vppinfra/cache.h>
17 #include <vppinfra/error.h>
18 #include <sys/resource.h>
19 #include <stdio.h>
20 #include <pthread.h>
21
22 #include <vppinfra/bihash_8_8.h>
23 #include <vppinfra/bihash_template.h>
24
25 #include <vppinfra/bihash_template.c>
26
27 typedef struct
28 {
29   volatile u32 thread_barrier;
30   volatile u32 threads_running;
31   volatile u64 sequence_number;
32   u64 seed;
33   u32 nbuckets;
34   u32 nitems;
35   u32 ncycles;
36   u32 report_every_n;
37   u32 search_iter;
38   u32 noverwritten;
39   int careful_delete_tests;
40   int verbose;
41   int non_random_keys;
42   u32 nthreads;
43   uword *key_hash;
44   u64 *keys;
45   uword hash_memory_size;
46     BVT (clib_bihash) hash;
47   clib_time_t clib_time;
48   void *global_heap;
49
50   unformat_input_t *input;
51
52 } test_main_t;
53
54 test_main_t test_main;
55
56 uword
57 vl (void *v)
58 {
59   return vec_len (v);
60 }
61
62 static clib_error_t *
63 test_bihash_vec64 (test_main_t * tm)
64 {
65   u32 user_buckets = 1228800;
66   u32 user_memory_size = 209715200;
67   BVT (clib_bihash_kv) kv;
68   int i, j;
69   f64 before;
70   f64 *cum_times = 0;
71   BVT (clib_bihash) * h;
72
73   h = &tm->hash;
74
75   BV (clib_bihash_init) (h, "test", user_buckets, user_memory_size);
76
77   before = clib_time_now (&tm->clib_time);
78
79   for (j = 0; j < 10; j++)
80     {
81       for (i = 1; i <= j * 1000 + 1; i++)
82         {
83           kv.key = i;
84           kv.value = 1;
85
86           BV (clib_bihash_add_del) (h, &kv, 1 /* is_add */ );
87         }
88
89       vec_add1 (cum_times, clib_time_now (&tm->clib_time) - before);
90     }
91
92   for (j = 0; j < vec_len (cum_times); j++)
93     fformat (stdout, "Cum time for %d: %.4f (us)\n", (j + 1) * 1000,
94              cum_times[j] * 1e6);
95
96   return 0;
97 }
98
99 static int
100 stale_cb (BVT (clib_bihash_kv) * kv, void *ctx)
101 {
102   test_main_t *tm = ctx;
103
104   tm->noverwritten++;
105
106   return 1;
107 }
108
109 static clib_error_t *
110 test_bihash_stale_overwrite (test_main_t * tm)
111 {
112   BVT (clib_bihash) * h;
113   BVT (clib_bihash_kv) kv;
114   int i;
115   tm->noverwritten = 0;
116
117   h = &tm->hash;
118
119   BV (clib_bihash_init) (h, "test", tm->nbuckets, tm->hash_memory_size);
120
121   fformat (stdout, "Add %d items to %d buckets\n", tm->nitems, tm->nbuckets);
122
123   for (i = 0; i < tm->nitems; i++)
124     {
125       kv.key = i;
126       kv.value = 1;
127
128       BV (clib_bihash_add_or_overwrite_stale) (h, &kv, stale_cb, tm);
129     }
130
131   fformat (stdout, "%d items overwritten\n", tm->noverwritten);
132   fformat (stdout, "%U", BV (format_bihash), h, 0);
133
134   return 0;
135 }
136
137 void *
138 test_bihash_thread_fn (void *arg)
139 {
140   BVT (clib_bihash) * h;
141   BVT (clib_bihash_kv) kv;
142   test_main_t *tm = &test_main;
143
144   int i, j;
145
146   u32 my_thread_index = (u32) (u64) arg;
147   __os_thread_index = my_thread_index;
148   clib_mem_set_per_cpu_heap (tm->global_heap);
149
150   while (tm->thread_barrier)
151     ;
152
153   h = &tm->hash;
154
155   for (i = 0; i < tm->ncycles; i++)
156     {
157       for (j = 0; j < tm->nitems; j++)
158         {
159           kv.key = ((u64) my_thread_index << 32) | (u64) j;
160           kv.value = ((u64) my_thread_index << 32) | (u64) j;
161           (void) __atomic_add_fetch (&tm->sequence_number, 1,
162                                      __ATOMIC_ACQUIRE);
163           BV (clib_bihash_add_del) (h, &kv, 1 /* is_add */ );
164         }
165       for (j = 0; j < tm->nitems; j++)
166         {
167           kv.key = ((u64) my_thread_index << 32) | (u64) j;
168           kv.value = ((u64) my_thread_index << 32) | (u64) j;
169           (void) __atomic_add_fetch (&tm->sequence_number, 1,
170                                      __ATOMIC_ACQUIRE);
171           BV (clib_bihash_add_del) (h, &kv, 0 /* is_add */ );
172         }
173     }
174
175   (void) __atomic_sub_fetch (&tm->threads_running, 1, __ATOMIC_ACQUIRE);
176   while (1)
177     {
178       struct timespec ts, tsrem;
179       ts.tv_sec = 1;
180       ts.tv_nsec = 0;
181
182       while (nanosleep (&ts, &tsrem) < 0)
183         ts = tsrem;
184     }
185   return (0);                   /* not so much */
186 }
187
188 static clib_error_t *
189 test_bihash_threads (test_main_t * tm)
190 {
191   int i;
192   pthread_t handle;
193   BVT (clib_bihash) * h;
194   int rv;
195
196   h = &tm->hash;
197
198   BV (clib_bihash_init) (h, "test", tm->nbuckets, tm->hash_memory_size);
199
200   tm->thread_barrier = 1;
201
202   /* Start the worker threads */
203   for (i = 0; i < tm->nthreads; i++)
204     {
205       rv = pthread_create (&handle, NULL, test_bihash_thread_fn,
206                            (void *) (u64) i);
207       if (rv)
208         {
209           clib_unix_warning ("pthread_create returned %d", rv);
210         }
211     }
212   tm->threads_running = i;
213   tm->sequence_number = 0;
214   CLIB_MEMORY_BARRIER ();
215
216   /* start the workers */
217   tm->thread_barrier = 0;
218
219   while (tm->threads_running)
220     {
221       struct timespec ts, tsrem;
222       ts.tv_sec = 0;
223       ts.tv_nsec = 20 * 1000 * 1000;    /* sleep for 20ms at a time */
224
225       while (nanosleep (&ts, &tsrem) < 0)
226         ts = tsrem;
227     }
228
229   return 0;
230 }
231
232
233 static clib_error_t *
234 test_bihash (test_main_t * tm)
235 {
236   int i, j;
237   uword *p;
238   uword total_searches;
239   f64 before, delta;
240   BVT (clib_bihash) * h;
241   BVT (clib_bihash_kv) kv;
242   u32 acycle;
243
244   h = &tm->hash;
245
246   BV (clib_bihash_init) (h, "test", tm->nbuckets, tm->hash_memory_size);
247
248   for (acycle = 0; acycle < tm->ncycles; acycle++)
249     {
250       if ((acycle % tm->report_every_n) == 0)
251         {
252           fformat (stdout, "Cycle %lld out of %lld...\n",
253                    acycle, tm->ncycles);
254
255           fformat (stdout, "Pick %lld unique %s keys...\n",
256                    tm->nitems, tm->non_random_keys ? "non-random" : "random");
257         }
258
259       for (i = 0; i < tm->nitems; i++)
260         {
261           u64 rndkey;
262
263           if (tm->non_random_keys == 0)
264             {
265
266             again:
267               rndkey = random_u64 (&tm->seed);
268
269               p = hash_get (tm->key_hash, rndkey);
270               if (p)
271                 goto again;
272             }
273           else
274             rndkey = (u64) (i + 1) << 16;
275           rndkey += acycle;
276
277           hash_set (tm->key_hash, rndkey, i + 1);
278           vec_add1 (tm->keys, rndkey);
279         }
280
281
282       if ((acycle % tm->report_every_n) == 0)
283         fformat (stdout, "Add items...\n");
284
285       for (i = 0; i < tm->nitems; i++)
286         {
287           kv.key = tm->keys[i];
288           kv.value = i + 1;
289
290           BV (clib_bihash_add_del) (h, &kv, 1 /* is_add */ );
291
292           if (tm->verbose > 1)
293             {
294               fformat (stdout, "--------------------\n");
295               fformat (stdout, "After adding key %llu value %lld...\n",
296                        tm->keys[i], (u64) (i + 1));
297               fformat (stdout, "%U", BV (format_bihash), h,
298                        2 /* very verbose */ );
299             }
300         }
301
302       if ((acycle % tm->report_every_n) == 0)
303         {
304           fformat (stdout, "%U", BV (format_bihash), h,
305                    0 /* very verbose */ );
306
307           fformat (stdout, "Search for items %d times...\n", tm->search_iter);
308         }
309
310       before = clib_time_now (&tm->clib_time);
311
312       for (j = 0; j < tm->search_iter; j++)
313         {
314           for (i = 0; i < tm->nitems; i++)
315             {
316               kv.key = tm->keys[i];
317               if (BV (clib_bihash_search) (h, &kv, &kv) < 0)
318                 if (BV (clib_bihash_search) (h, &kv, &kv) < 0)
319                   clib_warning
320                     ("[%d] search for key %lld failed unexpectedly\n", i,
321                      tm->keys[i]);
322               if (kv.value != (u64) (i + 1))
323                 clib_warning
324                   ("[%d] search for key %lld returned %lld, not %lld\n", i,
325                    tm->keys, kv.value, (u64) (i + 1));
326             }
327         }
328
329       if ((acycle % tm->report_every_n) == 0)
330         {
331           delta = clib_time_now (&tm->clib_time) - before;
332           total_searches = (uword) tm->search_iter * (uword) tm->nitems;
333
334           if (delta > 0)
335             fformat (stdout, "%.f searches per second\n",
336                      ((f64) total_searches) / delta);
337
338           fformat (stdout, "%lld searches in %.6f seconds\n", total_searches,
339                    delta);
340
341           fformat (stdout, "Standard E-hash search for items %d times...\n",
342                    tm->search_iter);
343         }
344
345       before = clib_time_now (&tm->clib_time);
346
347       for (j = 0; j < tm->search_iter; j++)
348         {
349           for (i = 0; i < tm->nitems; i++)
350             {
351               p = hash_get (tm->key_hash, tm->keys[i]);
352               if (p == 0 || p[0] != (uword) (i + 1))
353                 clib_warning ("ugh, couldn't find %lld\n", tm->keys[i]);
354             }
355         }
356
357       delta = clib_time_now (&tm->clib_time) - before;
358       total_searches = (uword) tm->search_iter * (uword) tm->nitems;
359
360       if ((acycle % tm->report_every_n) == 0)
361         {
362           fformat (stdout, "%lld searches in %.6f seconds\n",
363                    total_searches, delta);
364
365           if (delta > 0)
366             fformat (stdout, "%.f searches per second\n",
367                      ((f64) total_searches) / delta);
368           fformat (stdout, "Delete items...\n");
369         }
370
371       for (i = 0; i < tm->nitems; i++)
372         {
373           int j;
374           int rv;
375
376           kv.key = tm->keys[i];
377           kv.value = (u64) (i + 1);
378           rv = BV (clib_bihash_add_del) (h, &kv, 0 /* is_add */ );
379
380           if (rv < 0)
381             clib_warning ("delete key %lld not ok but should be",
382                           tm->keys[i]);
383
384           if (tm->careful_delete_tests)
385             {
386               for (j = 0; j < tm->nitems; j++)
387                 {
388                   kv.key = tm->keys[j];
389                   rv = BV (clib_bihash_search) (h, &kv, &kv);
390                   if (j <= i && rv >= 0)
391                     {
392                       clib_warning
393                         ("i %d j %d search ok but should not be, value %lld",
394                          i, j, kv.value);
395                     }
396                   if (j > i && rv < 0)
397                     {
398                       clib_warning ("i %d j %d search not ok but should be",
399                                     i, j);
400                     }
401                 }
402             }
403         }
404       if ((acycle % tm->report_every_n) == 0)
405         {
406           struct rusage r_usage;
407           getrusage (RUSAGE_SELF, &r_usage);
408           fformat (stdout, "Kernel RSS: %ld bytes\n", r_usage.ru_maxrss);
409           fformat (stdout, "%U\n", BV (format_bihash), h, 0 /* verbose */ );
410         }
411
412       /* Clean up side-bet hash table and random key vector */
413       hash_free (tm->key_hash);
414       vec_reset_length (tm->keys);
415       /* Recreate hash table if we're going to need it again */
416       if (acycle != (tm->ncycles - 1))
417         tm->key_hash = hash_create (tm->nitems, sizeof (uword));
418     }
419
420   fformat (stdout, "End of run, should be empty...\n");
421
422   fformat (stdout, "%U", BV (format_bihash), h, 0 /* very verbose */ );
423   return 0;
424 }
425
426 clib_error_t *
427 test_bihash_main (test_main_t * tm)
428 {
429   unformat_input_t *i = tm->input;
430   clib_error_t *error;
431   int which = 0;
432
433   tm->report_every_n = 1;
434   tm->hash_memory_size = 4095ULL << 20;
435
436   while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
437     {
438       if (unformat (i, "seed %u", &tm->seed))
439         ;
440
441       else if (unformat (i, "nbuckets %d", &tm->nbuckets))
442         ;
443       else if (unformat (i, "non-random-keys"))
444         tm->non_random_keys = 1;
445       else if (unformat (i, "nitems %d", &tm->nitems))
446         ;
447       else if (unformat (i, "ncycles %d", &tm->ncycles))
448         ;
449       else if (unformat (i, "careful %d", &tm->careful_delete_tests))
450         ;
451       else if (unformat (i, "verbose %d", &tm->verbose))
452         ;
453       else if (unformat (i, "search %d", &tm->search_iter))
454         ;
455       else if (unformat (i, "report-every %d", &tm->report_every_n))
456         ;
457       else if (unformat (i, "memory-size %U",
458                          unformat_memory_size, &tm->hash_memory_size))
459         ;
460       else if (unformat (i, "vec64"))
461         which = 1;
462       else if (unformat (i, "threads %u", &tm->nthreads))
463         which = 2;
464       else if (unformat (i, "verbose"))
465         tm->verbose = 1;
466       else if (unformat (i, "stale-overwrite"))
467         which = 3;
468       else
469         return clib_error_return (0, "unknown input '%U'",
470                                   format_unformat_error, i);
471     }
472
473   /* Preallocate hash table, key vector */
474   tm->key_hash = hash_create (tm->nitems, sizeof (uword));
475   vec_validate (tm->keys, tm->nitems - 1);
476   _vec_len (tm->keys) = 0;
477
478
479   switch (which)
480     {
481     case 0:
482       error = test_bihash (tm);
483       break;
484
485     case 1:
486       error = test_bihash_vec64 (tm);
487       break;
488
489     case 2:
490       error = test_bihash_threads (tm);
491       break;
492
493     case 3:
494       error = test_bihash_stale_overwrite (tm);
495       break;
496
497     default:
498       return clib_error_return (0, "no such test?");
499     }
500
501   return error;
502 }
503
504 #ifdef CLIB_UNIX
505 int
506 main (int argc, char *argv[])
507 {
508   unformat_input_t i;
509   clib_error_t *error;
510   test_main_t *tm = &test_main;
511
512   clib_mem_init (0, 4095ULL << 20);
513
514   tm->global_heap = clib_mem_get_per_cpu_heap ();
515
516   tm->input = &i;
517   tm->seed = 0xdeaddabe;
518
519   tm->nbuckets = 2;
520   tm->nitems = 5;
521   tm->ncycles = 1;
522   tm->verbose = 1;
523   tm->search_iter = 1;
524   tm->careful_delete_tests = 0;
525   clib_time_init (&tm->clib_time);
526
527   unformat_init_command_line (&i, argv);
528   error = test_bihash_main (tm);
529   unformat_free (&i);
530
531   if (error)
532     {
533       clib_error_report (error);
534       return 1;
535     }
536   return 0;
537 }
538 #endif /* CLIB_UNIX */
539
540 /*
541  * fd.io coding-style-patch-verification: ON
542  *
543  * Local Variables:
544  * eval: (c-set-style "gnu")
545  * End:
546  */