vlib: improvement to automatic core pinning
[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_initiator_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_initiator_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_initiator_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 static clib_error_t *
251 test_bihash_vanilla_overwrite (test_main_t *tm)
252 {
253   int i;
254   BVT (clib_bihash) * h;
255   BVT (clib_bihash_kv) kv;
256
257   h = &tm->hash;
258
259 #if BIHASH_32_64_SVM
260   BV (clib_bihash_initiator_init_svm)
261   (h, "test", tm->nbuckets, 0x30000000 /* base_addr */, tm->hash_memory_size);
262 #else
263   BV (clib_bihash_init) (h, "test", tm->nbuckets, tm->hash_memory_size);
264 #endif
265
266   for (i = 0; i < 100; i++)
267     {
268       kv.key = 12345;
269       kv.value = i;
270
271       BV (clib_bihash_add_del) (h, &kv, 1 /* is_add */);
272     }
273
274   fformat (stdout, "End of run, should one item...\n");
275   fformat (stdout, "%U", BV (format_bihash), h, 0 /* very verbose */);
276   BV (clib_bihash_free) (h);
277   return 0;
278 }
279
280 static clib_error_t *
281 test_bihash_value_assert (test_main_t *tm)
282 {
283   BVT (clib_bihash) * h;
284   BVT (clib_bihash_kv) kv;
285
286   h = &tm->hash;
287
288 #if BIHASH_32_64_SVM
289   BV (clib_bihash_initiator_init_svm)
290   (h, "test", tm->nbuckets, 0x30000000 /* base_addr */, tm->hash_memory_size);
291 #else
292   BV (clib_bihash_init) (h, "test", tm->nbuckets, tm->hash_memory_size);
293 #endif
294
295   kv.key = 12345;
296   kv.value = 0xFEEDFACE8BADF00DULL;
297
298   fformat (stderr, "The following add should ASSERT...\n");
299   BV (clib_bihash_add_del) (h, &kv, 1 /* is_add */);
300
301   return 0;
302 }
303
304 static clib_error_t *
305 test_bihash (test_main_t * tm)
306 {
307   int i, j;
308   uword *p;
309   uword total_searches;
310   f64 before, delta;
311   BVT (clib_bihash) * h;
312   BVT (clib_bihash_kv) kv;
313   u32 acycle;
314
315   h = &tm->hash;
316
317 #if BIHASH_32_64_SVM
318   BV (clib_bihash_initiator_init_svm) (h, "test", tm->nbuckets,
319                                        0x30000000 /* base_addr */ ,
320                                        tm->hash_memory_size);
321 #else
322   BV (clib_bihash_init) (h, "test", tm->nbuckets, tm->hash_memory_size);
323 #endif
324
325   for (acycle = 0; acycle < tm->ncycles; acycle++)
326     {
327       if ((acycle % tm->report_every_n) == 0)
328         {
329           fformat (stdout, "Cycle %lld out of %lld...\n",
330                    acycle, tm->ncycles);
331
332           fformat (stdout, "Pick %lld unique %s keys...\n",
333                    tm->nitems, tm->non_random_keys ? "non-random" : "random");
334         }
335
336       for (i = 0; i < tm->nitems; i++)
337         {
338           u64 rndkey;
339
340           if (tm->non_random_keys == 0)
341             {
342
343             again:
344               rndkey = random_u64 (&tm->seed);
345
346               p = hash_get (tm->key_hash, rndkey);
347               if (p)
348                 goto again;
349             }
350           else
351             rndkey = (u64) (i + 1) << 16;
352           rndkey += acycle;
353
354           hash_set (tm->key_hash, rndkey, i + 1);
355           vec_add1 (tm->keys, rndkey);
356         }
357
358
359       if ((acycle % tm->report_every_n) == 0)
360         fformat (stdout, "Add items...\n");
361
362       for (i = 0; i < tm->nitems; i++)
363         {
364           kv.key = tm->keys[i];
365           kv.value = i + 1;
366
367           BV (clib_bihash_add_del) (h, &kv, 1 /* is_add */ );
368
369           if (tm->verbose > 1)
370             {
371               fformat (stdout, "--------------------\n");
372               fformat (stdout, "After adding key %llu value %lld...\n",
373                        tm->keys[i], (u64) (i + 1));
374               fformat (stdout, "%U", BV (format_bihash), h,
375                        2 /* very verbose */ );
376             }
377         }
378
379       if ((acycle % tm->report_every_n) == 0)
380         {
381           fformat (stdout, "%U", BV (format_bihash), h,
382                    0 /* very verbose */ );
383
384           fformat (stdout, "Search for items %d times...\n", tm->search_iter);
385         }
386
387       before = clib_time_now (&tm->clib_time);
388
389       for (j = 0; j < tm->search_iter; j++)
390         {
391           for (i = 0; i < tm->nitems; i++)
392             {
393               /* Prefetch buckets 8 iterations ahead */
394               if (1 && (i < ((i64) tm->nitems - 8)))
395                 {
396                   BVT (clib_bihash_kv) pref_kv;
397                   u64 pref_hash;
398                   pref_kv.key = tm->keys[i + 8];
399                   pref_hash = BV (clib_bihash_hash) (&pref_kv);
400                   BV (clib_bihash_prefetch_bucket) (h, pref_hash);
401                 }
402
403               kv.key = tm->keys[i];
404               if (BV (clib_bihash_search) (h, &kv, &kv) < 0)
405                 if (BV (clib_bihash_search) (h, &kv, &kv) < 0)
406                   clib_warning
407                     ("[%d] search for key %lld failed unexpectedly\n", i,
408                      tm->keys[i]);
409               if (kv.value != (u64) (i + 1))
410                 clib_warning
411                   ("[%d] search for key %lld returned %lld, not %lld\n", i,
412                    tm->keys, kv.value, (u64) (i + 1));
413             }
414         }
415
416       if ((acycle % tm->report_every_n) == 0)
417         {
418           delta = clib_time_now (&tm->clib_time) - before;
419           total_searches = (uword) tm->search_iter * (uword) tm->nitems;
420
421           if (delta > 0)
422             fformat (stdout,
423                      "%.f searches per second, %.2f nsec per search\n",
424                      ((f64) total_searches) / delta,
425                      1e9 * (delta / ((f64) total_searches)));
426
427           fformat (stdout, "%lld searches in %.6f seconds\n", total_searches,
428                    delta);
429
430           fformat (stdout, "Standard E-hash search for items %d times...\n",
431                    tm->search_iter);
432         }
433
434       before = clib_time_now (&tm->clib_time);
435
436       for (j = 0; j < tm->search_iter; j++)
437         {
438           for (i = 0; i < tm->nitems; i++)
439             {
440               p = hash_get (tm->key_hash, tm->keys[i]);
441               if (p == 0 || p[0] != (uword) (i + 1))
442                 clib_warning ("ugh, couldn't find %lld\n", tm->keys[i]);
443             }
444         }
445
446       delta = clib_time_now (&tm->clib_time) - before;
447       total_searches = (uword) tm->search_iter * (uword) tm->nitems;
448
449       if ((acycle % tm->report_every_n) == 0)
450         {
451           fformat (stdout, "%lld searches in %.6f seconds\n",
452                    total_searches, delta);
453
454           if (delta > 0)
455             fformat (stdout, "%.f searches per second\n",
456                      ((f64) total_searches) / delta);
457           fformat (stdout, "Delete items...\n");
458         }
459
460       for (i = 0; i < tm->nitems; i++)
461         {
462           int j;
463           int rv;
464
465           kv.key = tm->keys[i];
466           kv.value = (u64) (i + 1);
467           rv = BV (clib_bihash_add_del) (h, &kv, 0 /* is_add */ );
468
469           if (rv < 0)
470             clib_warning ("delete key %lld not ok but should be",
471                           tm->keys[i]);
472
473           if (tm->careful_delete_tests)
474             {
475               for (j = 0; j < tm->nitems; j++)
476                 {
477                   /* Prefetch buckets 8 iterations ahead */
478                   if (1 && (j < ((i64) tm->nitems - 8)))
479                     {
480                       BVT (clib_bihash_kv) pref_kv;
481                       u64 pref_hash;
482                       pref_kv.key = tm->keys[j + 8];
483                       pref_hash = BV (clib_bihash_hash) (&pref_kv);
484                       BV (clib_bihash_prefetch_bucket) (h, pref_hash);
485                     }
486
487                   kv.key = tm->keys[j];
488                   rv = BV (clib_bihash_search) (h, &kv, &kv);
489                   if (j <= i && rv >= 0)
490                     {
491                       clib_warning
492                         ("i %d j %d search ok but should not be, value %lld",
493                          i, j, kv.value);
494                     }
495                   if (j > i && rv < 0)
496                     {
497                       clib_warning ("i %d j %d search not ok but should be",
498                                     i, j);
499                     }
500                 }
501             }
502         }
503       if ((acycle % tm->report_every_n) == 0)
504         {
505           struct rusage r_usage;
506           getrusage (RUSAGE_SELF, &r_usage);
507           fformat (stdout, "Kernel RSS: %ld bytes\n", r_usage.ru_maxrss);
508           fformat (stdout, "%U\n", BV (format_bihash), h, 0 /* verbose */ );
509         }
510
511       /* Clean up side-bet hash table and random key vector */
512       hash_free (tm->key_hash);
513       vec_reset_length (tm->keys);
514       /* Recreate hash table if we're going to need it again */
515       if (acycle != (tm->ncycles - 1))
516         tm->key_hash = hash_create (tm->nitems, sizeof (uword));
517     }
518
519   fformat (stdout, "End of run, should be empty...\n");
520
521   fformat (stdout, "%U", BV (format_bihash), h, 0 /* very verbose */ );
522
523   BV (clib_bihash_free) (h);
524
525   return 0;
526 }
527
528 clib_error_t *
529 test_bihash_main (test_main_t * tm)
530 {
531   unformat_input_t *i = tm->input;
532   clib_error_t *error;
533   int which = 0;
534
535   tm->report_every_n = 1;
536   tm->hash_memory_size = 1ULL << 30;
537
538   while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
539     {
540       if (unformat (i, "seed %u", &tm->seed))
541         ;
542
543       else if (unformat (i, "nbuckets %d", &tm->nbuckets))
544         ;
545       else if (unformat (i, "non-random-keys"))
546         tm->non_random_keys = 1;
547       else if (unformat (i, "nitems %d", &tm->nitems))
548         ;
549       else if (unformat (i, "ncycles %d", &tm->ncycles))
550         ;
551       else if (unformat (i, "careful %d", &tm->careful_delete_tests))
552         ;
553       else if (unformat (i, "verbose %d", &tm->verbose))
554         ;
555       else if (unformat (i, "search %d", &tm->search_iter))
556         ;
557       else if (unformat (i, "report-every %d", &tm->report_every_n))
558         ;
559       else if (unformat (i, "memory-size %U",
560                          unformat_memory_size, &tm->hash_memory_size))
561         ;
562       else if (unformat (i, "vec64"))
563         which = 1;
564       else if (unformat (i, "threads %u", &tm->nthreads))
565         which = 2;
566       else if (unformat (i, "verbose"))
567         tm->verbose = 1;
568       else if (unformat (i, "stale-overwrite"))
569         which = 3;
570       else if (unformat (i, "overwrite"))
571         which = 4;
572       else if (unformat (i, "value-assert"))
573         which = 5;
574       else
575         return clib_error_return (0, "unknown input '%U'",
576                                   format_unformat_error, i);
577     }
578
579   /* Preallocate hash table, key vector */
580   tm->key_hash = hash_create (tm->nitems, sizeof (uword));
581   vec_validate (tm->keys, tm->nitems - 1);
582   vec_set_len (tm->keys, 0);
583
584   switch (which)
585     {
586     case 0:
587       error = test_bihash (tm);
588       break;
589
590     case 1:
591       error = test_bihash_vec64 (tm);
592       break;
593
594     case 2:
595       error = test_bihash_threads (tm);
596       break;
597
598     case 3:
599       error = test_bihash_stale_overwrite (tm);
600       break;
601
602     case 4:
603       error = test_bihash_vanilla_overwrite (tm);
604       break;
605
606     case 5:
607       error = test_bihash_value_assert (tm);
608       break;
609
610     default:
611       return clib_error_return (0, "no such test?");
612     }
613
614   return error;
615 }
616
617 #ifdef CLIB_UNIX
618 int
619 main (int argc, char *argv[])
620 {
621   unformat_input_t i;
622   clib_error_t *error;
623   test_main_t *tm = &test_main;
624
625   clib_mem_init (0, 4095ULL << 20);
626
627   tm->global_heap = clib_mem_get_per_cpu_heap ();
628
629   tm->input = &i;
630   tm->seed = 0xdeaddabe;
631
632   tm->nbuckets = 2;
633   tm->nitems = 5;
634   tm->ncycles = 1;
635   tm->verbose = 1;
636   tm->search_iter = 1;
637   tm->careful_delete_tests = 0;
638   clib_time_init (&tm->clib_time);
639
640   unformat_init_command_line (&i, argv);
641   error = test_bihash_main (tm);
642   unformat_free (&i);
643
644   if (error)
645     {
646       clib_error_report (error);
647       return 1;
648     }
649   return 0;
650 }
651 #endif /* CLIB_UNIX */
652
653 /*
654  * fd.io coding-style-patch-verification: ON
655  *
656  * Local Variables:
657  * eval: (c-set-style "gnu")
658  * End:
659  */