c1a446919667df4a1a8b75d455f57f0e54036c9a
[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               kv.key = tm->keys[i];
341               if (BV (clib_bihash_search) (h, &kv, &kv) < 0)
342                 if (BV (clib_bihash_search) (h, &kv, &kv) < 0)
343                   clib_warning
344                     ("[%d] search for key %lld failed unexpectedly\n", i,
345                      tm->keys[i]);
346               if (kv.value != (u64) (i + 1))
347                 clib_warning
348                   ("[%d] search for key %lld returned %lld, not %lld\n", i,
349                    tm->keys, kv.value, (u64) (i + 1));
350             }
351         }
352
353       if ((acycle % tm->report_every_n) == 0)
354         {
355           delta = clib_time_now (&tm->clib_time) - before;
356           total_searches = (uword) tm->search_iter * (uword) tm->nitems;
357
358           if (delta > 0)
359             fformat (stdout, "%.f searches per second\n",
360                      ((f64) total_searches) / delta);
361
362           fformat (stdout, "%lld searches in %.6f seconds\n", total_searches,
363                    delta);
364
365           fformat (stdout, "Standard E-hash search for items %d times...\n",
366                    tm->search_iter);
367         }
368
369       before = clib_time_now (&tm->clib_time);
370
371       for (j = 0; j < tm->search_iter; j++)
372         {
373           for (i = 0; i < tm->nitems; i++)
374             {
375               p = hash_get (tm->key_hash, tm->keys[i]);
376               if (p == 0 || p[0] != (uword) (i + 1))
377                 clib_warning ("ugh, couldn't find %lld\n", tm->keys[i]);
378             }
379         }
380
381       delta = clib_time_now (&tm->clib_time) - before;
382       total_searches = (uword) tm->search_iter * (uword) tm->nitems;
383
384       if ((acycle % tm->report_every_n) == 0)
385         {
386           fformat (stdout, "%lld searches in %.6f seconds\n",
387                    total_searches, delta);
388
389           if (delta > 0)
390             fformat (stdout, "%.f searches per second\n",
391                      ((f64) total_searches) / delta);
392           fformat (stdout, "Delete items...\n");
393         }
394
395       for (i = 0; i < tm->nitems; i++)
396         {
397           int j;
398           int rv;
399
400           kv.key = tm->keys[i];
401           kv.value = (u64) (i + 1);
402           rv = BV (clib_bihash_add_del) (h, &kv, 0 /* is_add */ );
403
404           if (rv < 0)
405             clib_warning ("delete key %lld not ok but should be",
406                           tm->keys[i]);
407
408           if (tm->careful_delete_tests)
409             {
410               for (j = 0; j < tm->nitems; j++)
411                 {
412                   kv.key = tm->keys[j];
413                   rv = BV (clib_bihash_search) (h, &kv, &kv);
414                   if (j <= i && rv >= 0)
415                     {
416                       clib_warning
417                         ("i %d j %d search ok but should not be, value %lld",
418                          i, j, kv.value);
419                     }
420                   if (j > i && rv < 0)
421                     {
422                       clib_warning ("i %d j %d search not ok but should be",
423                                     i, j);
424                     }
425                 }
426             }
427         }
428       if ((acycle % tm->report_every_n) == 0)
429         {
430           struct rusage r_usage;
431           getrusage (RUSAGE_SELF, &r_usage);
432           fformat (stdout, "Kernel RSS: %ld bytes\n", r_usage.ru_maxrss);
433           fformat (stdout, "%U\n", BV (format_bihash), h, 0 /* verbose */ );
434         }
435
436       /* Clean up side-bet hash table and random key vector */
437       hash_free (tm->key_hash);
438       vec_reset_length (tm->keys);
439       /* Recreate hash table if we're going to need it again */
440       if (acycle != (tm->ncycles - 1))
441         tm->key_hash = hash_create (tm->nitems, sizeof (uword));
442     }
443
444   fformat (stdout, "End of run, should be empty...\n");
445
446   fformat (stdout, "%U", BV (format_bihash), h, 0 /* very verbose */ );
447
448   BV (clib_bihash_free) (h);
449
450   return 0;
451 }
452
453 clib_error_t *
454 test_bihash_main (test_main_t * tm)
455 {
456   unformat_input_t *i = tm->input;
457   clib_error_t *error;
458   int which = 0;
459
460   tm->report_every_n = 1;
461   tm->hash_memory_size = 1ULL << 30;
462
463   while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
464     {
465       if (unformat (i, "seed %u", &tm->seed))
466         ;
467
468       else if (unformat (i, "nbuckets %d", &tm->nbuckets))
469         ;
470       else if (unformat (i, "non-random-keys"))
471         tm->non_random_keys = 1;
472       else if (unformat (i, "nitems %d", &tm->nitems))
473         ;
474       else if (unformat (i, "ncycles %d", &tm->ncycles))
475         ;
476       else if (unformat (i, "careful %d", &tm->careful_delete_tests))
477         ;
478       else if (unformat (i, "verbose %d", &tm->verbose))
479         ;
480       else if (unformat (i, "search %d", &tm->search_iter))
481         ;
482       else if (unformat (i, "report-every %d", &tm->report_every_n))
483         ;
484       else if (unformat (i, "memory-size %U",
485                          unformat_memory_size, &tm->hash_memory_size))
486         ;
487       else if (unformat (i, "vec64"))
488         which = 1;
489       else if (unformat (i, "threads %u", &tm->nthreads))
490         which = 2;
491       else if (unformat (i, "verbose"))
492         tm->verbose = 1;
493       else if (unformat (i, "stale-overwrite"))
494         which = 3;
495       else
496         return clib_error_return (0, "unknown input '%U'",
497                                   format_unformat_error, i);
498     }
499
500   /* Preallocate hash table, key vector */
501   tm->key_hash = hash_create (tm->nitems, sizeof (uword));
502   vec_validate (tm->keys, tm->nitems - 1);
503   _vec_len (tm->keys) = 0;
504
505
506   switch (which)
507     {
508     case 0:
509       error = test_bihash (tm);
510       break;
511
512     case 1:
513       error = test_bihash_vec64 (tm);
514       break;
515
516     case 2:
517       error = test_bihash_threads (tm);
518       break;
519
520     case 3:
521       error = test_bihash_stale_overwrite (tm);
522       break;
523
524     default:
525       return clib_error_return (0, "no such test?");
526     }
527
528   return error;
529 }
530
531 #ifdef CLIB_UNIX
532 int
533 main (int argc, char *argv[])
534 {
535   unformat_input_t i;
536   clib_error_t *error;
537   test_main_t *tm = &test_main;
538
539   clib_mem_init (0, 4095ULL << 20);
540
541   tm->global_heap = clib_mem_get_per_cpu_heap ();
542
543   tm->input = &i;
544   tm->seed = 0xdeaddabe;
545
546   tm->nbuckets = 2;
547   tm->nitems = 5;
548   tm->ncycles = 1;
549   tm->verbose = 1;
550   tm->search_iter = 1;
551   tm->careful_delete_tests = 0;
552   clib_time_init (&tm->clib_time);
553
554   unformat_init_command_line (&i, argv);
555   error = test_bihash_main (tm);
556   unformat_free (&i);
557
558   if (error)
559     {
560       clib_error_report (error);
561       return 1;
562     }
563   return 0;
564 }
565 #endif /* CLIB_UNIX */
566
567 /*
568  * fd.io coding-style-patch-verification: ON
569  *
570  * Local Variables:
571  * eval: (c-set-style "gnu")
572  * End:
573  */