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