Files
bind9/tests/bench/load-names.c
Evan Hunt ac9bd03a0d clean up dns_rbt
- create_node() in rbt.c cannot fail
- the dns_rbt_*name() functions, which are wrappers around
  dns_rbt_[add|find|delete]node(), were never used except in tests.

this change isn't really necessary since RBT is likely to go away
eventually anyway. but keeping the API as simple as possible while it
persists is a good thing, and may reduce confusion while QPDB is being
developed from RBTDB code.
2024-02-14 01:36:44 -08:00

622 lines
14 KiB
C

/*
* 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 <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 <dns/fixedname.h>
#include <dns/qp.h>
#include <dns/rbt.h>
#include <dns/types.h>
#include "qp_p.h"
#include <tests/dns.h>
#include <tests/qp.h>
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);
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 dns_qpmethods_t qpmethods = {
item_check,
item_check,
item_makekey,
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(EXIT_FAILURE); \
} \
} 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
*/
static void *
new_hashmap(isc_mem_t *mem) {
isc_hashmap_t *hashmap = NULL;
isc_hashmap_create(mem, 1, &hashmap);
return (hashmap);
}
static bool
name_match(void *node, const void *key) {
const struct item_s *i = node;
return (dns_name_equal(&i->fixed.name, key));
}
static isc_result_t
add_hashmap(void *hashmap, size_t count) {
isc_result_t result = isc_hashmap_add(
hashmap, dns_name_hash(&item[count].fixed.name), name_match,
&item[count].fixed.name, &item[count], NULL);
return (result);
}
static isc_result_t
get_hashmap(void *hashmap, size_t count, void **pval) {
isc_result_t result = isc_hashmap_find(
hashmap, dns_name_hash(&item[count].fixed.name), name_match,
&item[count].fixed.name, 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);
}
/*
* ht
*/
static void *
new_ht(isc_mem_t *mem) {
isc_ht_t *ht = NULL;
isc_ht_init(&ht, mem, 1, 0);
return (ht);
}
static isc_result_t
add_ht(void *ht, size_t count) {
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) {
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);
}
/*
* rbt
*/
static void *
new_rbt(isc_mem_t *mem) {
dns_rbt_t *rbt = NULL;
(void)dns_rbt_create(mem, NULL, NULL, &rbt);
return (rbt);
}
static isc_result_t
add_rbt(void *rbt, size_t count) {
isc_result_t result;
dns_rbtnode_t *node = NULL;
result = dns_rbt_addnode(rbt, &item[count].fixed.name, &node);
if (result == ISC_R_SUCCESS ||
(result == ISC_R_EXISTS && node->data == NULL))
{
node->data = &item[count];
result = ISC_R_SUCCESS;
}
return (result);
}
static isc_result_t
get_rbt(void *rbt, size_t count, void **pval) {
isc_result_t result;
dns_rbtnode_t *node = NULL;
result = dns_rbt_findnode(rbt, &item[count].fixed.name, NULL, &node,
NULL, 0, NULL, NULL);
if (result == ISC_R_SUCCESS) {
*pval = node->data;
}
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);
}
/*
* qp
*/
static void *
new_qp(isc_mem_t *mem) {
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) {
isc_result_t result = dns_qp_insert(qp, &item[count], count);
return (result);
}
static void
sqz_qp(void *qp) {
dns_qp_compact(qp, DNS_QPGC_MAYBE);
}
static isc_result_t
get_qp(void *qp, size_t count, void **pval) {
return (dns_qp_getname(qp, &item[count].fixed.name, pval, NULL));
}
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 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 FILE_CHECK(check, msg) \
do { \
if (!(check)) { \
fprintf(stderr, "%s:%zu: %s\n", filename, lines, msg); \
exit(EXIT_FAILURE); \
} \
} while (0)
int
main(int argc, char *argv[]) {
isc_result_t result;
const char *filename = NULL;
char *filetext = NULL;
off_t fileoff;
FILE *fp = NULL;
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> <nthreads>\n");
exit(EXIT_FAILURE);
}
filename = argv[1];
result = isc_file_getsize(filename, &fileoff);
if (result != ISC_R_SUCCESS) {
fprintf(stderr, "stat(%s): %s\n", filename,
isc_result_totext(result));
exit(EXIT_FAILURE);
}
filesize = (size_t)fileoff;
filetext = isc_mem_get(mctx, filesize + 1);
fp = fopen(filename, "r");
if (fp == NULL || fread(filetext, 1, filesize, fp) < filesize) {
fprintf(stderr, "read(%s): %s\n", filename, strerror(errno));
exit(EXIT_FAILURE);
}
fclose(fp);
filetext[filesize] = '\0';
pos = filetext;
file_end = pos + filesize;
while (pos < file_end) {
char *domain = NULL, *newline = NULL;
size_t len;
FILE_CHECK(lines < ARRAY_SIZE(item), "too many lines");
pos += strspn(pos, "0123456789");
FILE_CHECK(*pos++ == ',', "missing comma");
domain = pos;
pos += strcspn(pos, "\r\n");
FILE_CHECK(*pos != '\0', "missing newline");
newline = pos;
pos += strspn(pos, "\r\n");
len = newline - domain;
item[lines].text = domain;
domain[len] = '\0';
dns_name_t *name = dns_fixedname_initname(&item[lines].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;
lines++;
}
printf("names %g MB labels %g MB\n\n", (double)wirebytes / 1048576.0,
(double)labels / 1048576.0);
printf("%10s | %10s | %10s | %10s | %10s | %10s | %10s |\n",
"algorithm", "threads", "load", "query", "dirty MB", "total",
"final MB");
for (size_t nthreads = 128; nthreads > 0; nthreads /= 2) {
printf("---------- | ---------- | ---------- | ---------- | "
"---------- | ---------- | ---------- |\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 = lines / (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);
}
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");
}