From: Tony Finch Date: Wed, 28 Sep 2022 15:56:46 +0000 (+0100) Subject: Benchmarks for the qp-trie X-Git-Tag: v9.19.11~17^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a9d57b91db34eede6aba503a7eb022087e709713;p=thirdparty%2Fbind9.git Benchmarks for the qp-trie The main benchmark is `qpmulti`, which exercizes the qp-trie transactional API with differing numbers of threads and differing data sizes, to get some idea of how its performance scales. The `load-names` benchmark compares the times to populate and query and the memory used by various BIND data structures: qp-trie, hash table (chained), hash map (closed), and red-black tree. The `qp-dump` program is a test utility rather than a benchmark. It populates a qp-trie and prints it out, either in an ad-hoc text format, or as input to the graphviz `dot` program. --- diff --git a/tests/bench/.gitignore b/tests/bench/.gitignore index afd821b4254..9f93506f128 100644 --- a/tests/bench/.gitignore +++ b/tests/bench/.gitignore @@ -2,4 +2,7 @@ /compress /iterated_hash /dns_name_fromwire +/load-names +/qp-dump +/qpmulti /siphash diff --git a/tests/bench/Makefile.am b/tests/bench/Makefile.am index f99d47ed571..5204031f325 100644 --- a/tests/bench/Makefile.am +++ b/tests/bench/Makefile.am @@ -1,19 +1,27 @@ include $(top_srcdir)/Makefile.top -AM_CPPFLAGS += \ - $(LIBISC_CFLAGS) \ - $(LIBDNS_CFLAGS) \ - -I$(top_srcdir)/fuzz +AM_CPPFLAGS += \ + $(LIBUV_CFLAGS) \ + $(LIBISC_CFLAGS) \ + $(LIBDNS_CFLAGS) \ + -I$(top_srcdir)/fuzz \ + -I$(top_srcdir)/lib/dns \ + -I$(top_srcdir)/tests/include -LDADD += \ - $(LIBISC_LIBS) \ - $(LIBDNS_LIBS) +LDADD += \ + $(LIBUV_LIBS) \ + $(LIBISC_LIBS) \ + $(LIBDNS_LIBS) \ + $(top_builddir)/tests/libtest/libtest.la -noinst_PROGRAMS = \ - ascii \ - compress \ - iterated_hash \ - dns_name_fromwire \ +noinst_PROGRAMS = \ + ascii \ + compress \ + dns_name_fromwire \ + iterated_hash \ + load-names \ + qp-dump \ + qpmulti \ siphash dns_name_fromwire_SOURCES = \ diff --git a/tests/bench/load-names.c b/tests/bench/load-names.c new file mode 100644 index 00000000000..1e4b69e1c64 --- /dev/null +++ b/tests/bench/load-names.c @@ -0,0 +1,313 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "qp_p.h" + +#include +#include + +struct { + const char *text; + dns_fixedname_t fixed; +} item[1024 * 1024]; + +static void +item_check(void *ctx, void *pval, uint32_t ival) { + UNUSED(ctx); + assert(pval == &item[ival]); +} + +static size_t +item_makekey(dns_qpkey_t key, void *ctx, void *pval, uint32_t ival) { + UNUSED(ctx); + assert(pval == &item[ival]); + return (dns_qpkey_fromname(key, &item[ival].fixed.name)); +} + +static void +testname(void *ctx, char *buf, size_t size) { + REQUIRE(ctx == NULL); + strlcpy(buf, "test", size); +} + +const struct dns_qpmethods qpmethods = { + item_check, + item_check, + item_makekey, + testname, +}; + +/* + * hashmap + */ + +static void * +new_hashmap(isc_mem_t *mem) { + isc_hashmap_t *hashmap = NULL; + isc_hashmap_create(mem, 16, 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); +} + +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)); +} + +/* + * ht + */ + +static void * +new_ht(isc_mem_t *mem) { + isc_ht_t *ht = NULL; + isc_ht_init(&ht, mem, 16, 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); +} + +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)); +} + +/* + * rbt + */ + +static void * +new_rbt(isc_mem_t *mem) { + dns_rbt_t *rbt = NULL; + dns_rbt_create(mem, NULL, NULL, &rbt); + return (rbt); +} + +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); +} + +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)); +} + +/* + * qp + */ + +static void * +new_qp(isc_mem_t *mem) { + dns_qp_t *qp = NULL; + dns_qp_create(mem, &qpmethods, NULL, &qp); + return (qp); +} + +static isc_result_t +add_qp(void *qp, size_t count) { + return (dns_qp_insert(qp, &item[count], count)); +} + +static void +sqz_qp(void *qp) { + dns_qp_compact(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)); +} + +/* + * 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 }, +}; + +#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)) { \ + fprintf(stderr, "%s:%zu: %s\n", filename, count, msg); \ + exit(1); \ + } \ + } while (0) + +int +main(int argc, char *argv[]) { + isc_result_t result; + + isc_mem_create(&mctx); + + if (argc != 2) { + fprintf(stderr, "usage: load-names \n"); + exit(1); + } + + const char *filename = argv[1]; + off_t fileoff; + result = isc_file_getsize(filename, &fileoff); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "stat(%s): %s\n", filename, + isc_result_totext(result)); + exit(1); + } + size_t filesize = (size_t)fileoff; + + char *filetext = isc_mem_get(mctx, filesize + 1); + FILE *fp = fopen(filename, "r"); + if (fp == NULL || fread(filetext, 1, filesize, fp) < filesize) { + fprintf(stderr, "read(%s): %s\n", filename, strerror(errno)); + exit(1); + } + fclose(fp); + filetext[filesize] = '\0'; + + size_t count = 0; + size_t wirebytes = 0; + size_t labels = 0; + + char *pos = filetext; + char *file_end = pos + filesize; + while (pos < file_end) { + FILE_CHECK(count < ARRAY_SIZE(item), "too many lines"); + pos += strspn(pos, "0123456789"); + + FILE_CHECK(*pos++ == ',', "missing comma"); + + char *domain = pos; + pos += strcspn(pos, "\r\n"); + FILE_CHECK(*pos != '\0', "missing newline"); + char *newline = pos; + pos += strspn(pos, "\r\n"); + size_t len = newline - domain; + + item[count].text = domain; + domain[len] = '\0'; + + dns_name_t *name = dns_fixedname_initname(&item[count].fixed); + isc_buffer_t buffer; + isc_buffer_init(&buffer, domain, len); + isc_buffer_add(&buffer, len); + result = dns_name_fromtext(name, &buffer, dns_rootname, 0, + NULL); + FILE_CHECK(result == ISC_R_SUCCESS, isc_result_totext(result)); + + wirebytes += name->length; + labels += name->labels; + count++; + } + + printf("names %g MB labels %g MB\n", (double)wirebytes / 1048576.0, + (double)labels / 1048576.0); + + size_t lines = count; + + for (struct fun *fun = fun_list; fun->name != NULL; fun++) { + isc_time_t t0; + isc_time_now_hires(&t0); + + isc_mem_t *mem = NULL; + isc_mem_create(&mem); + void *map = fun->new (mem); + + for (count = 0; count < lines; count++) { + result = fun->add(map, count); + CHECK(result); + } + fun->sqz(map); + + isc_time_t t1; + isc_time_now_hires(&t1); + + for (count = 0; count < lines; count++) { + void *pval = NULL; + result = fun->get(map, count, &pval); + CHECK(result); + assert(pval == &item[count]); + } + + isc_time_t t2; + isc_time_now_hires(&t2); + + 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); + } +} diff --git a/tests/bench/qp-dump.c b/tests/bench/qp-dump.c new file mode 100644 index 00000000000..6aced4349f0 --- /dev/null +++ b/tests/bench/qp-dump.c @@ -0,0 +1,271 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +static inline size_t +smallname_length(void *pval, uint32_t ival) { + UNUSED(pval); + return (ival & 0xff); +} + +static inline size_t +smallname_labels(void *pval, uint32_t ival) { + UNUSED(pval); + return (ival >> 8); +} + +static inline isc_refcount_t * +smallname_refcount(void *pval, uint32_t ival) { + UNUSED(ival); + return (pval); +} + +static inline uint8_t * +smallname_ndata(void *pval, uint32_t ival) { + return ((uint8_t *)(smallname_refcount(pval, ival) + 1)); +} + +static inline uint8_t * +smallname_offsets(void *pval, uint32_t ival) { + return (smallname_ndata(pval, ival) + smallname_length(pval, ival)); +} + +static void +smallname_from_name(/* isc_mem_t *mctx, */ const dns_name_t *name, void **valp, + uint32_t *ctxp) { + size_t size = sizeof(isc_refcount_t) + name->length + name->labels; + *valp = isc_mem_get(mctx, size); + *ctxp = name->labels << 8 | name->length; + isc_refcount_init(smallname_refcount(*valp, *ctxp), 0); + memmove(smallname_ndata(*valp, *ctxp), name->ndata, name->length); + memmove(smallname_offsets(*valp, *ctxp), name->offsets, name->labels); +} + +static void +smallname_free(/* isc_mem_t *mctx, */ void *pval, uint32_t ival) { + size_t size = sizeof(isc_refcount_t); + size += smallname_length(pval, ival) + smallname_labels(pval, ival); + isc_mem_put(mctx, pval, size); +} + +static void +name_from_smallname(dns_name_t *name, void *pval, uint32_t ival) { + dns_name_reset(name); + name->ndata = smallname_ndata(pval, ival); + name->length = smallname_length(pval, ival); + name->labels = smallname_labels(pval, ival); + name->offsets = smallname_offsets(pval, ival); + name->attributes.readonly = true; + if (name->ndata[name->offsets[name->labels - 1]] == '\0') { + name->attributes.absolute = true; + } +} + +static size_t +qpkey_from_smallname(dns_qpkey_t key, void *ctx, void *pval, uint32_t ival) { + UNUSED(ctx); + dns_name_t name = DNS_NAME_INITEMPTY; + name_from_smallname(&name, pval, ival); + return (dns_qpkey_fromname(key, &name)); +} + +static void +smallname_attach(void *ctx, void *pval, uint32_t ival) { + UNUSED(ctx); + isc_refcount_increment0(smallname_refcount(pval, ival)); +} + +static void +smallname_detach(void *ctx, void *pval, uint32_t ival) { + if (isc_refcount_decrement(smallname_refcount(pval, ival)) == 1) { + isc_mem_free(ctx, pval); + } +} + +static void +testname(void *ctx, char *buf, size_t size) { + REQUIRE(ctx == NULL); + strlcpy(buf, "test", size); +} + +const struct dns_qpmethods methods = { + smallname_attach, + smallname_detach, + qpkey_from_smallname, + testname, +}; + +static void +usage(void) { + fprintf(stderr, + "usage: qp_dump [-drt] \n" + " -d output in graphviz dot format\n" + " -t output in ad-hoc indented text format\n"); +} + +int +main(int argc, char *argv[]) { + bool dumpdot = false; + bool dumptxt = false; + int opt; + + while ((opt = isc_commandline_parse(argc, argv, "dt")) != -1) { + switch (opt) { + case 'd': + dumpdot = true; + continue; + case 't': + dumptxt = true; + continue; + default: + usage(); + exit(1); + continue; + } + } + argc -= isc_commandline_index; + argv += isc_commandline_index; + + if (argc != 1) { + /* must exit 0 to appease test runner */ + usage(); + exit(0); + } + + isc_mem_create(&mctx); + + const char *filename = argv[0]; + off_t fileoff; + isc_result_t result = isc_file_getsize(filename, &fileoff); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "stat(%s): %s\n", filename, + isc_result_totext(result)); + exit(1); + } + size_t filesize = (size_t)fileoff; + + char *filetext = isc_mem_get(mctx, filesize + 1); + FILE *fp = fopen(filename, "r"); + if (fp == NULL || fread(filetext, 1, filesize, fp) < filesize) { + fprintf(stderr, "read(%s): %s\n", filename, strerror(errno)); + exit(1); + } + fclose(fp); + filetext[filesize] = '\0'; + + dns_qp_t *qp = NULL; + dns_qp_create(mctx, &methods, NULL, &qp); + + size_t wirebytes = 0; + size_t labels = 0; + size_t names = 0; + char *pos = filetext; + char *file_end = pos + filesize; + while (pos < file_end) { + char *domain = pos; + pos += strcspn(pos, "\r\n"); + char *newline = pos; + pos += strspn(pos, "\r\n"); + size_t len = newline - domain; + domain[len] = '\0'; + + dns_fixedname_t fixed; + dns_name_t *name = dns_fixedname_initname(&fixed); + isc_buffer_t buffer; + isc_buffer_init(&buffer, domain, len); + isc_buffer_add(&buffer, len); + result = dns_name_fromtext(name, &buffer, dns_rootname, 0, + NULL); + void *pval = NULL; + uint32_t ival = 0; + if (result == ISC_R_SUCCESS) { + smallname_from_name(name, &pval, &ival); + result = dns_qp_insert(qp, pval, ival); + } + if (result == ISC_R_EXISTS && pval != NULL) { + smallname_free(pval, ival); + continue; + } + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "%s:%zu: %s %s\n", filename, names, + domain, isc_result_totext(result)); + exit(1); + } + + wirebytes += name->length; + labels += name->labels; + names += 1; + } + dns_qp_compact(qp); + + size_t smallbytes = wirebytes + labels + names * sizeof(isc_refcount_t); + dns_qp_memusage_t memusage = dns_qp_memusage(qp); + uint64_t compaction_us, recovery_us, rollback_us; + dns_qp_gctime(&compaction_us, &recovery_us, &rollback_us); + +#define print_megabytes(label, value) \ + printf("%6.2f MiB - " label "\n", (double)(value) / 1048576.0) + + if (!dumptxt && !dumpdot) { + printf("leaves %zu\n" + " nodes %zu\n" + " used %zu\n" + " free %zu\n" + " cow %zu\n" + "chunks %zu\n" + " bytes %zu\n", + memusage.leaves, memusage.live, memusage.used, + memusage.free, memusage.hold, memusage.chunk_count, + memusage.bytes); + + printf("%f compaction\n", (double)compaction_us / 1000000); + printf("%f recovery\n", (double)recovery_us / 1000000); + printf("%f rollback\n", (double)rollback_us / 1000000); + + size_t bytes = memusage.bytes; + print_megabytes("file size", filesize); + print_megabytes("names", wirebytes); + print_megabytes("labels", labels); + print_megabytes("names + labels", wirebytes + labels); + print_megabytes("smallnames", smallbytes); + print_megabytes("qp-trie", bytes); + print_megabytes("qp-trie + smallnames", bytes + smallbytes); + print_megabytes("calculated", bytes + smallbytes + filesize); + print_megabytes("allocated", isc_mem_inuse(mctx)); + printf("%6zu - height\n", qp_test_getheight(qp)); + printf("%6zu - max key len\n", qp_test_maxkeylen(qp)); + } + + if (dumptxt) + qp_test_dumptrie(qp); + if (dumpdot) + qp_test_dumpdot(qp); + + return (0); +} diff --git a/tests/bench/qpmulti.c b/tests/bench/qpmulti.c new file mode 100644 index 00000000000..e86797a0614 --- /dev/null +++ b/tests/bench/qpmulti.c @@ -0,0 +1,374 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "qp_p.h" + +#include + +#define ITEM_COUNT ((size_t)1000000) + +#define MS_PER_SEC 1000 +#define US_PER_SEC 1000000 +#define NS_PER_SEC 1000000000 + +static double +doubletime(isc_time_t t0, isc_time_t t1) { + return ((double)isc_time_microdiff(&t1, &t0) / (double)US_PER_SEC); +} + +static struct { + bool present; + uint8_t len; + dns_qpkey_t key; +} *item; + +static void +item_refcount(void *ctx, void *pval, uint32_t ival) { + UNUSED(ctx); + UNUSED(pval); + UNUSED(ival); +} + +static size_t +item_makekey(dns_qpkey_t key, void *ctx, void *pval, uint32_t ival) { + UNUSED(ctx); + UNUSED(pval); + memmove(key, item[ival].key, item[ival].len); + return (item[ival].len); +} + +static void +benchname(void *ctx, char *buf, size_t size) { + UNUSED(ctx); + strlcpy(buf, "bench", size); +} + +const struct dns_qpmethods item_methods = { + item_refcount, + item_refcount, + item_makekey, + benchname, +}; + +static uint8_t +random_byte(void) { + return (isc_random_uniform(SHIFT_OFFSET - SHIFT_NOBYTE) + SHIFT_NOBYTE); +} + +static void +init_items(isc_mem_t *mctx) { + isc_time_t t0, t1; + void *pval = NULL; + uint32_t ival = ~0U; + dns_qp_t *qp = NULL; + + size_t bytes = ITEM_COUNT * sizeof(*item); + + item = isc_mem_allocatex(mctx, bytes, ISC_MEM_ZERO); + + isc_time_now_hires(&t0); + + /* ensure there are no duplicate names */ + dns_qp_create(mctx, &item_methods, NULL, &qp); + for (size_t i = 0; i < ITEM_COUNT; i++) { + do { + size_t len = isc_random_uniform(16) + 4; + item[i].len = len; + for (size_t off = 0; off < len; off++) { + item[i].key[off] = random_byte(); + } + item[i].key[len] = SHIFT_NOBYTE; + } while (dns_qp_getkey(qp, item[i].key, item[i].len, &pval, + &ival) == ISC_R_SUCCESS); + INSIST(dns_qp_insert(qp, &item[i], i) == ISC_R_SUCCESS); + } + dns_qp_destroy(&qp); + + isc_time_now_hires(&t1); + double time = doubletime(t0, t1); + printf("%f sec to create %zu items, %f/sec\n", time, ITEM_COUNT, + ITEM_COUNT / time); +} + +static void +init_multi(isc_mem_t *mctx, dns_qpmulti_t **qpmp, uint32_t max) { + isc_time_t t0, t1; + dns_qpmulti_t *multi = NULL; + dns_qp_t *qp = NULL; + size_t count = 0; + + isc_time_now_hires(&t0); + + dns_qpmulti_create(mctx, &item_methods, NULL, qpmp); + multi = *qpmp; + + /* initial contents of the trie */ + dns_qpmulti_update(multi, &qp); + for (size_t i = 0; i < max; i++) { + if (isc_random_uniform(2) == 0) { + continue; + } + INSIST(dns_qp_insert(qp, &item[i], i) == ISC_R_SUCCESS); + item[i].present = true; + count++; + } + dns_qpmulti_commit(multi, &qp); + + isc_time_now_hires(&t1); + double time = doubletime(t0, t1); + printf("%f sec to load %zu items, %f/sec\n", time, count, count / time); +} + +static void +init_logging(isc_mem_t *mctx) { + isc_result_t result; + isc_logdestination_t destination; + isc_logconfig_t *logconfig = NULL; + isc_log_t *lctx = NULL; + + isc_log_create(mctx, &lctx, &logconfig); + isc_log_setcontext(lctx); + dns_log_init(lctx); + dns_log_setcontext(lctx); + + destination.file.stream = stderr; + destination.file.name = NULL; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.maximum_size = 0; + isc_log_createchannel(logconfig, "stderr", ISC_LOG_TOFILEDESC, + ISC_LOG_DYNAMIC, &destination, + ISC_LOG_PRINTPREFIX | ISC_LOG_PRINTTIME | + ISC_LOG_ISO8601); + + // isc_log_setdebuglevel(lctx, 1); + + result = isc_log_usechannel(logconfig, "stderr", + ISC_LOGCATEGORY_DEFAULT, NULL); + INSIST(result == ISC_R_SUCCESS); +} + +typedef void +transaction_fun(dns_qpmulti_t *multi, uint32_t max, uint32_t ops, + uint64_t *absent_r, uint64_t *present_r); + +static transaction_fun read_transaction, update_transaction; + +struct thread_args { + transaction_fun *txfun; /* (in) */ + dns_qpmulti_t *multi; /* (in) */ + isc_thread_t tid; /* (in) */ + uint32_t max; /* item index (in) */ + uint32_t ops; /* per transaction (in) */ + uint64_t absent; /* items not found or inserted (out) */ + uint64_t present; /* items found or deleted (out) */ + uint64_t transactions; /* (out) */ + isc_time_t t0; /* (out) */ + isc_time_t t1; /* (out) */ +}; + +static void +read_transaction(dns_qpmulti_t *multi, uint32_t max, uint32_t ops, + uint64_t *absent_r, uint64_t *present_r) { + dns_qpread_t *qp = NULL; + uint64_t absent = 0; + uint64_t present = 0; + void *pval; + uint32_t ival; + isc_result_t result; + + dns_qpmulti_query(multi, &qp); + for (uint32_t n = 0; n < ops; n++) { + uint32_t i = isc_random_uniform(max); + result = dns_qp_getkey(qp, item[i].key, item[i].len, &pval, + &ival); + if (result == ISC_R_SUCCESS) { + ++present; + } else { + ++absent; + } + } + dns_qpread_destroy(multi, &qp); + *present_r = present; + *absent_r = absent; +} + +static void +update_transaction(dns_qpmulti_t *multi, uint32_t max, uint32_t ops, + uint64_t *absent_r, uint64_t *present_r) { + dns_qp_t *qp = NULL; + uint64_t absent = 0; + uint64_t present = 0; + isc_result_t result; + + if (multi->read->generation & 255) { + dns_qpmulti_write(multi, &qp); + } else { + dns_qpmulti_update(multi, &qp); + } + for (uint32_t n = 0; n < ops; n++) { + uint32_t i = isc_random_uniform(max); + if (item[i].present) { + result = dns_qp_deletekey(qp, item[i].key, item[i].len); + INSIST(result == ISC_R_SUCCESS); + item[i].present = false; + ++present; + } else { + result = dns_qp_insert(qp, &item[i], i); + INSIST(result == ISC_R_SUCCESS); + item[i].present = true; + ++absent; + } + } + dns_qpmulti_commit(multi, &qp); + *present_r += present; + *absent_r += absent; +} + +static isc_refcount_t stop; + +static void * +thread_loop(void *args_v) { + struct thread_args *args = args_v; + transaction_fun *txfun = args->txfun; + dns_qpmulti_t *multi = args->multi; + uint32_t max = args->max; + uint32_t ops = args->ops; + uint64_t absent = 0; + uint64_t present = 0; + uint64_t transactions = 0; + +#if HAVE_LIBURCU + rcu_register_thread(); +#endif + isc_time_now_hires(&args->t0); + while (isc_refcount_current(&stop) == 0) { + txfun(multi, max, ops, &absent, &present); + ++transactions; + } + isc_time_now_hires(&args->t1); + args->absent = absent; + args->present = present; + args->transactions = transactions; +#if HAVE_LIBURCU + rcu_unregister_thread(); +#endif + return (args); +} + +static void +dispatch_threads(dns_qpmulti_t *multi, useconds_t runtime, uint32_t max, + uint32_t updaters, uint32_t updateops, uint32_t readers, + uint32_t readops) { + struct thread_args thread[64]; + uint32_t threads = updaters + readers; + + REQUIRE(threads <= ARRAY_SIZE(thread)); + + for (uint32_t t = 0; t < threads; t++) { + thread[t] = (struct thread_args){ + .txfun = t < updaters ? update_transaction + : read_transaction, + .multi = multi, + .max = max, + .ops = t < updaters ? updateops : readops, + }; + } + + isc_refcount_init(&stop, 0); + + for (uint32_t t = 0; t < threads; t++) { + isc_thread_create(thread_loop, &thread[t], &thread[t].tid); + } + + usleep(runtime); + isc_refcount_increment0(&stop); + + for (uint32_t t = 0; t < threads; t++) { + isc_thread_join(thread[t].tid, NULL); + } + + struct { + double time, txns, ops; + } stats[2] = {}; + + for (uint32_t t = 0; t < threads; t++) { + struct thread_args *tp = &thread[t]; + stats[t < updaters].time += doubletime(tp->t0, tp->t1); + stats[t < updaters].txns += tp->transactions; + stats[t < updaters].ops += tp->transactions * tp->ops; + } + printf("%2u up %2u ops/tx %7.3f txn/ms %5.3f ops/us ", updaters, + updateops, + stats[1].txns / (stats[1].time * MS_PER_SEC / updaters), + stats[1].ops / (stats[1].time * US_PER_SEC / updaters)); + printf("%2u rd %2u ops/tx %8.3f txn/ms %7.3f ops/us %6.3f ops/us/thr\n", + readers, readops, + stats[0].txns / (stats[0].time * MS_PER_SEC / readers), + stats[0].ops / (stats[0].time * US_PER_SEC / readers), + stats[0].ops / (stats[0].time * US_PER_SEC)); +} + +int +main(void) { + dns_qpmulti_t *multi = NULL; + isc_mem_t *mctx = NULL; + + isc_mem_create(&mctx); + isc_mem_setdestroycheck(mctx, true); + init_logging(mctx); + init_items(mctx); + + uint32_t threads = 12; + uint32_t max = ITEM_COUNT; + useconds_t runtime = 0.2 * US_PER_SEC; + + init_multi(mctx, &multi, max); + for (uint32_t t = 2; t <= threads; t++) { + dispatch_threads(multi, runtime, max, 1, 64, t - 1, 8); + } + dns_qpmulti_destroy(&multi); + + for (max = 1000; max <= ITEM_COUNT; max *= 10) { + init_multi(mctx, &multi, max); + for (uint32_t t = 1; t <= threads; t++) { + dispatch_threads(multi, runtime, max, 0, 0, t, 64); + } + dns_qpmulti_destroy(&multi); + } + + isc_log_destroy(&dns_lctx); + isc_mem_free(mctx, item); + isc_mem_destroy(&mctx); + isc_mem_checkdestroyed(stderr); + + return (0); +}