]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Make the load-names benchmark multithreaded
authorOndřej Surý <ondrej@isc.org>
Wed, 21 Jun 2023 12:10:28 +0000 (14:10 +0200)
committerOndřej Surý <ondrej@isc.org>
Mon, 31 Jul 2023 13:51:15 +0000 (15:51 +0200)
The load-names benchmark was originally only measuring single thread
performance of the data structures.  As this is not how those are used
in the real life, it was refactored to be multi-threaded with proper
protections in place (rwlock for ht, hashmap and rbt; transactions for
qp).

The qp test has been extended to see effect of the dns_qp_compact() and
rcu_barrier() on the overall speed and memory consumption.

tests/bench/load-names.c

index 94e105ad16812b0def958da7225580c011e5fea1..e56bea87e45f3acef7508058be27a5c3dc9ee33a 100644 (file)
 #include <assert.h>
 #include <stdlib.h>
 
+#include <isc/barrier.h>
 #include <isc/file.h>
 #include <isc/hashmap.h>
 #include <isc/ht.h>
 #include <isc/list.h>
 #include <isc/rwlock.h>
+#include <isc/thread.h>
 #include <isc/urcu.h>
 #include <isc/util.h>
 
 #include <tests/dns.h>
 #include <tests/qp.h>
 
-struct {
+struct item_s {
        const char *text;
        dns_fixedname_t fixed;
+       struct cds_lfht_node ht_node;
 } item[1024 * 1024];
 
+isc_barrier_t barrier;
+isc_rwlock_t rwl;
+
+struct thread_s {
+       isc_thread_t thread;
+       struct fun *fun;
+       void *map;
+       size_t start;
+       size_t end;
+       uint64_t d0;
+       uint64_t d1;
+} threads[1024];
+
 static void
 item_check(void *ctx, void *pval, uint32_t ival) {
        UNUSED(ctx);
@@ -63,6 +79,103 @@ const dns_qpmethods_t qpmethods = {
        testname,
 };
 
+#define CHECK(count, result)                                        \
+       do {                                                        \
+               if (result != ISC_R_SUCCESS) {                      \
+                       dns_name_t *name = &item[count].fixed.name; \
+                       char buf[DNS_NAME_MAXTEXT] = { 0 };         \
+                       dns_name_format(name, buf, sizeof(buf));    \
+                       fprintf(stderr, "%s: %s\n", buf,            \
+                               isc_result_totext(result));         \
+                       exit(1);                                    \
+               }                                                   \
+       } while (0)
+
+struct fun {
+       const char *name;
+       void *(*new)(isc_mem_t *mem);
+       isc_threadfunc_t thread;
+};
+
+/*
+ * cds_lfht
+ */
+
+static void *
+new_lfht(isc_mem_t *mem ISC_ATTR_UNUSED) {
+       struct cds_lfht *lfht = cds_lfht_new(
+               1, 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL);
+       return (lfht);
+}
+
+static int
+lfht_match(struct cds_lfht_node *ht_node, const void *_key) {
+       const struct item_s *i = caa_container_of(ht_node, struct item_s,
+                                                 ht_node);
+       const dns_name_t *key = _key;
+
+       return (dns_name_equal(key, &i->fixed.name));
+}
+
+static isc_result_t
+add_lfht(void *lfht, size_t count) {
+       unsigned long hash = dns_name_hash(&item[count].fixed.name);
+
+       struct cds_lfht_node *ht_node = cds_lfht_add_unique(
+               lfht, hash, lfht_match, &item[count].fixed.name,
+               &item[count].ht_node);
+
+       if (ht_node != &item[count].ht_node) {
+               return (ISC_R_EXISTS);
+       }
+
+       return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+get_lfht(void *lfht, size_t count, void **pval) {
+       unsigned long hash = dns_name_hash(&item[count].fixed.name);
+
+       struct cds_lfht_iter iter;
+       cds_lfht_lookup(lfht, hash, lfht_match, &item[count].fixed.name, &iter);
+
+       struct cds_lfht_node *ht_node = cds_lfht_iter_get_node(&iter);
+       if (ht_node == NULL) {
+               return (ISC_R_NOTFOUND);
+       }
+
+       *pval = caa_container_of(ht_node, struct item_s, ht_node);
+       return (ISC_R_SUCCESS);
+}
+
+static void *
+thread_lfht(void *arg0) {
+       struct thread_s *arg = arg0;
+
+       isc_barrier_wait(&barrier);
+
+       isc_time_t t0 = isc_time_now_hires();
+       for (size_t n = arg->start; n < arg->end; n++) {
+               isc_result_t result = add_lfht(arg->map, n);
+               CHECK(n, result);
+       }
+
+       isc_time_t t1 = isc_time_now_hires();
+       for (size_t n = arg->start; n < arg->end; n++) {
+               void *pval = NULL;
+               isc_result_t result = get_lfht(arg->map, n, &pval);
+               CHECK(n, result);
+               assert(pval == &item[n]);
+       }
+
+       isc_time_t t2 = isc_time_now_hires();
+
+       arg->d0 = isc_time_microdiff(&t1, &t0);
+       arg->d1 = isc_time_microdiff(&t2, &t1);
+
+       return (NULL);
+}
+
 /*
  * hashmap
  */
@@ -70,25 +183,56 @@ const dns_qpmethods_t qpmethods = {
 static void *
 new_hashmap(isc_mem_t *mem) {
        isc_hashmap_t *hashmap = NULL;
-       isc_hashmap_create(mem, 16, 0, &hashmap);
+       isc_hashmap_create(mem, 1, 0, &hashmap);
+
        return (hashmap);
 }
 
 static isc_result_t
 add_hashmap(void *hashmap, size_t count) {
-       return (isc_hashmap_add(hashmap, NULL, item[count].fixed.name.ndata,
-                               item[count].fixed.name.length, &item[count]));
-}
-
-static void
-sqz_hashmap(void *hashmap) {
-       UNUSED(hashmap);
+       isc_result_t result =
+               isc_hashmap_add(hashmap, NULL, item[count].fixed.name.ndata,
+                               item[count].fixed.name.length, &item[count]);
+       return (result);
 }
 
 static isc_result_t
 get_hashmap(void *hashmap, size_t count, void **pval) {
-       return (isc_hashmap_find(hashmap, NULL, item[count].fixed.name.ndata,
-                                item[count].fixed.name.length, pval));
+       isc_result_t result =
+               isc_hashmap_find(hashmap, NULL, item[count].fixed.name.ndata,
+                                item[count].fixed.name.length, pval);
+       return (result);
+}
+
+static void *
+thread_hashmap(void *arg0) {
+       struct thread_s *arg = arg0;
+
+       isc_barrier_wait(&barrier);
+
+       isc_time_t t0 = isc_time_now_hires();
+       WRLOCK(&rwl);
+       for (size_t n = arg->start; n < arg->end; n++) {
+               isc_result_t result = add_hashmap(arg->map, n);
+               CHECK(n, result);
+       }
+       WRUNLOCK(&rwl);
+
+       isc_time_t t1 = isc_time_now_hires();
+       RDLOCK(&rwl);
+       for (size_t n = arg->start; n < arg->end; n++) {
+               void *pval = NULL;
+               isc_result_t result = get_hashmap(arg->map, n, &pval);
+               CHECK(n, result);
+               assert(pval == &item[n]);
+       }
+       RDUNLOCK(&rwl);
+       isc_time_t t2 = isc_time_now_hires();
+
+       arg->d0 = isc_time_microdiff(&t1, &t0);
+       arg->d1 = isc_time_microdiff(&t2, &t1);
+
+       return (NULL);
 }
 
 /*
@@ -98,25 +242,54 @@ get_hashmap(void *hashmap, size_t count, void **pval) {
 static void *
 new_ht(isc_mem_t *mem) {
        isc_ht_t *ht = NULL;
-       isc_ht_init(&ht, mem, 16, 0);
+       isc_ht_init(&ht, mem, 1, 0);
        return (ht);
 }
 
 static isc_result_t
 add_ht(void *ht, size_t count) {
-       return (isc_ht_add(ht, item[count].fixed.name.ndata,
-                          item[count].fixed.name.length, &item[count]));
-}
-
-static void
-sqz_ht(void *ht) {
-       UNUSED(ht);
+       isc_result_t result = isc_ht_add(ht, item[count].fixed.name.ndata,
+                                        item[count].fixed.name.length,
+                                        &item[count]);
+       return (result);
 }
 
 static isc_result_t
 get_ht(void *ht, size_t count, void **pval) {
-       return (isc_ht_find(ht, item[count].fixed.name.ndata,
-                           item[count].fixed.name.length, pval));
+       isc_result_t result = isc_ht_find(ht, item[count].fixed.name.ndata,
+                                         item[count].fixed.name.length, pval);
+       return (result);
+}
+
+static void *
+thread_ht(void *arg0) {
+       struct thread_s *arg = arg0;
+
+       isc_barrier_wait(&barrier);
+
+       isc_time_t t0 = isc_time_now_hires();
+       WRLOCK(&rwl);
+       for (size_t n = arg->start; n < arg->end; n++) {
+               isc_result_t result = add_ht(arg->map, n);
+               CHECK(n, result);
+       }
+       WRUNLOCK(&rwl);
+
+       isc_time_t t1 = isc_time_now_hires();
+       RDLOCK(&rwl);
+       for (size_t n = arg->start; n < arg->end; n++) {
+               void *pval = NULL;
+               isc_result_t result = get_ht(arg->map, n, &pval);
+               CHECK(n, result);
+               assert(pval == &item[n]);
+       }
+       RDUNLOCK(&rwl);
+       isc_time_t t2 = isc_time_now_hires();
+
+       arg->d0 = isc_time_microdiff(&t1, &t0);
+       arg->d1 = isc_time_microdiff(&t2, &t1);
+
+       return (NULL);
 }
 
 /*
@@ -132,17 +305,48 @@ new_rbt(isc_mem_t *mem) {
 
 static isc_result_t
 add_rbt(void *rbt, size_t count) {
-       return (dns_rbt_addname(rbt, &item[count].fixed.name, &item[count]));
-}
-
-static void
-sqz_rbt(void *rbt) {
-       UNUSED(rbt);
+       isc_result_t result = dns_rbt_addname(rbt, &item[count].fixed.name,
+                                             &item[count]);
+       return (result);
 }
 
 static isc_result_t
 get_rbt(void *rbt, size_t count, void **pval) {
-       return (dns_rbt_findname(rbt, &item[count].fixed.name, 0, NULL, pval));
+       isc_result_t result = dns_rbt_findname(rbt, &item[count].fixed.name, 0,
+                                              NULL, pval);
+       return (result);
+}
+
+static void *
+thread_rbt(void *arg0) {
+       struct thread_s *arg = arg0;
+
+       isc_barrier_wait(&barrier);
+
+       isc_time_t t0 = isc_time_now_hires();
+       WRLOCK(&rwl);
+       for (size_t n = arg->start; n < arg->end; n++) {
+               isc_result_t result = add_rbt(arg->map, n);
+               CHECK(n, result);
+       }
+       WRUNLOCK(&rwl);
+
+       isc_time_t t1 = isc_time_now_hires();
+       RDLOCK(&rwl);
+       for (size_t n = arg->start; n < arg->end; n++) {
+               void *pval = NULL;
+               isc_result_t result = get_rbt(arg->map, n, &pval);
+               CHECK(n, result);
+               assert(pval == &item[n]);
+       }
+       RDUNLOCK(&rwl);
+
+       isc_time_t t2 = isc_time_now_hires();
+
+       arg->d0 = isc_time_microdiff(&t1, &t0);
+       arg->d1 = isc_time_microdiff(&t2, &t1);
+
+       return (NULL);
 }
 
 /*
@@ -151,14 +355,15 @@ get_rbt(void *rbt, size_t count, void **pval) {
 
 static void *
 new_qp(isc_mem_t *mem) {
-       dns_qp_t *qp = NULL;
-       dns_qp_create(mem, &qpmethods, NULL, &qp);
-       return (qp);
+       dns_qpmulti_t *qpmulti = NULL;
+       dns_qpmulti_create(mem, &qpmethods, NULL, &qpmulti);
+       return (qpmulti);
 }
 
 static isc_result_t
 add_qp(void *qp, size_t count) {
-       return (dns_qp_insert(qp, &item[count], count));
+       isc_result_t result = dns_qp_insert(qp, &item[count], count);
+       return (result);
 }
 
 static void
@@ -169,34 +374,84 @@ sqz_qp(void *qp) {
 static isc_result_t
 get_qp(void *qp, size_t count, void **pval) {
        uint32_t ival = 0;
-       return (dns_qp_getname(qp, &item[count].fixed.name, pval, &ival));
+       isc_result_t result = dns_qp_getname(qp, &item[count].fixed.name, pval,
+                                            &ival);
+       return (result);
+}
+
+static void *
+_thread_qp(void *arg0, bool sqz, bool brr) {
+       struct thread_s *arg = arg0;
+
+       isc_barrier_wait(&barrier);
+
+       dns_qp_t *qp = NULL;
+       dns_qpmulti_write(arg->map, &qp);
+
+       isc_time_t t0 = isc_time_now_hires();
+       for (size_t n = arg->start; n < arg->end; n++) {
+               isc_result_t result = add_qp(qp, n);
+               CHECK(n, result);
+       }
+       if (sqz) {
+               sqz_qp(qp);
+       }
+       dns_qpmulti_commit(arg->map, &qp);
+       if (brr) {
+               rcu_barrier();
+       }
+
+       isc_time_t t1 = isc_time_now_hires();
+
+       dns_qpread_t qpr;
+       dns_qpmulti_query(arg->map, &qpr);
+
+       for (size_t n = arg->start; n < arg->end; n++) {
+               void *pval = NULL;
+               isc_result_t result = get_qp(&qpr, n, &pval);
+               CHECK(n, result);
+               assert(pval == &item[n]);
+       }
+
+       dns_qpread_destroy(arg->map, &qpr);
+
+       isc_time_t t2 = isc_time_now_hires();
+
+       arg->d0 = isc_time_microdiff(&t1, &t0);
+       arg->d1 = isc_time_microdiff(&t2, &t1);
+
+       return (NULL);
+}
+
+static void *
+thread_qp(void *arg0) {
+       return (_thread_qp(arg0, true, false));
+}
+
+static void *
+thread_qp_nosqz(void *arg0) {
+       return (_thread_qp(arg0, false, false));
+}
+
+static void *
+thread_qp_brr(void *arg0) {
+       return (_thread_qp(arg0, true, true));
 }
 
 /*
  * fun table
  */
-static struct fun {
-       const char *name;
-       void *(*new)(isc_mem_t *mem);
-       isc_result_t (*add)(void *map, size_t count);
-       void (*sqz)(void *map);
-       isc_result_t (*get)(void *map, size_t count, void **pval);
-} fun_list[] = {
-       { "ht", new_ht, add_ht, sqz_ht, get_ht },
-       { "hashmap", new_hashmap, add_hashmap, sqz_hashmap, get_hashmap },
-       { "rbt", new_rbt, add_rbt, sqz_rbt, get_rbt },
-       { "qp", new_qp, add_qp, sqz_qp, get_qp },
-       { NULL, NULL, NULL, NULL, NULL },
+static struct fun fun_list[] = {
+       { "lfht", new_lfht, thread_lfht },
+       { "ht", new_ht, thread_ht },
+       { "hashmap", new_hashmap, thread_hashmap },
+       { "rbt", new_rbt, thread_rbt },
+       { "qp", new_qp, thread_qp },
+       { "qp+nosqz", new_qp, thread_qp_nosqz },
+       { "qp+barrier", new_qp, thread_qp_brr },
+       { NULL, NULL, NULL },
 };
 
-#define CHECK(result)                                                       \
-       do {                                                                \
-               if (result != ISC_R_SUCCESS) {                              \
-                       fprintf(stderr, "%s\n", isc_result_totext(result)); \
-                       exit(1);                                            \
-               }                                                           \
-       } while (0)
-
 #define FILE_CHECK(check, msg)                                                 \
        do {                                                                   \
                if (!(check)) {                                                \
@@ -215,10 +470,13 @@ main(int argc, char *argv[]) {
        size_t filesize, lines = 0, wirebytes = 0, labels = 0;
        char *pos = NULL, *file_end = NULL;
 
+       isc_rwlock_init(&rwl);
+
        isc_mem_create(&mctx);
 
        if (argc != 2) {
-               fprintf(stderr, "usage: load-names <filename.csv>\n");
+               fprintf(stderr,
+                       "usage: load-names <filename.csv> <nthreads>\n");
                exit(1);
        }
 
@@ -274,40 +532,72 @@ main(int argc, char *argv[]) {
                lines++;
        }
 
-       printf("names %g MB labels %g MB\n", (double)wirebytes / 1048576.0,
+       printf("names %g MB labels %g MB\n\n", (double)wirebytes / 1048576.0,
               (double)labels / 1048576.0);
 
-       for (struct fun *fun = fun_list; fun->name != NULL; fun++) {
-               isc_mem_t *mem = NULL;
-               void *map = NULL;
+       printf("%10s | %10s | %10s | %10s | %10s | %10s | %10s |\n",
+              "algorithm", "threads", "load", "query", "dirty MB", "total",
+              "final MB");
 
-               isc_mem_create(&mem);
-               map = fun->new (mem);
+       for (size_t nthreads = 128; nthreads > 0; nthreads /= 2) {
+               printf("---------- | ---------- | ---------- | ---------- | "
+                      "---------- | ---------- | ---------- |\n");
 
-               isc_time_t t0 = isc_time_now_hires();
-               for (size_t n = 0; n < lines; n++) {
-                       result = fun->add(map, n);
-                       CHECK(result);
-               }
-               fun->sqz(map);
-
-               isc_time_t t1 = isc_time_now_hires();
-               for (size_t n = 0; n < lines; n++) {
-                       void *pval = NULL;
-                       result = fun->get(map, n, &pval);
-                       CHECK(result);
-                       assert(pval == &item[n]);
-               }
+               for (struct fun *fun = fun_list; fun->name != NULL; fun++) {
+                       isc_mem_t *mem = NULL;
+                       void *map = NULL;
+
+                       isc_mem_create(&mem);
+                       map = fun->new (mem);
+
+                       size_t nitems = ARRAY_SIZE(item) / (nthreads + 1);
+
+                       isc_barrier_init(&barrier, nthreads);
+
+                       isc_time_t t0 = isc_time_now_hires();
+                       size_t m0 = isc_mem_inuse(mem);
+
+                       for (size_t i = 0; i < nthreads; i++) {
+                               threads[i] = (struct thread_s){
+                                       .fun = fun,
+                                       .map = map,
+                                       .start = nitems * i,
+                                       .end = nitems * i + nitems,
+                               };
+                               isc_thread_create(fun->thread, &threads[i],
+                                                 &threads[i].thread);
+                       }
 
-               isc_time_t t2 = isc_time_now_hires();
-               printf("%f sec to load %s\n",
-                      (double)isc_time_microdiff(&t1, &t0) / (1000.0 * 1000.0),
-                      fun->name);
-               printf("%f sec to query %s\n",
-                      (double)isc_time_microdiff(&t2, &t1) / (1000.0 * 1000.0),
-                      fun->name);
-               printf("%g MB used by %s\n",
-                      (double)isc_mem_inuse(mem) / (1024.0 * 1024.0),
-                      fun->name);
+                       uint64_t d0 = 0;
+                       uint64_t d1 = 0;
+
+                       for (size_t i = 0; i < nthreads; i++) {
+                               isc_thread_join(threads[i].thread, NULL);
+                               d0 += threads[i].d0;
+                               d1 += threads[i].d1;
+                       }
+
+                       size_t m1 = isc_mem_inuse(mem);
+
+                       rcu_barrier();
+
+                       isc_time_t t1 = isc_time_now_hires();
+                       uint64_t d3 = isc_time_microdiff(&t1, &t0);
+                       size_t m2 = isc_mem_inuse(mem);
+
+                       printf("%10s | %10zu | %10.4f | %10.4f | %10.4f | "
+                              "%10.4f | %10.4f |\n",
+                              fun->name, nthreads,
+                              (double)(d0 / nthreads) / (1000.0 * 1000.0),
+                              (double)(d1 / nthreads) / (1000.0 * 1000.0),
+                              (double)(m1 - m0) / (1024.0 * 1024.0),
+                              (double)d3 / (1000.0 * 1000.0),
+                              (double)(m2 - m0) / (1024.0 * 1024.0)
+
+                       );
+               }
        }
+
+       printf("---------- | ---------- | ---------- | ---------- | "
+              "---------- | ---------- | ---------- |\n");
 }