2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-22 10:10:06 +00:00
bind/lib/isc/symtab.c
alessio e1e10adc3a Switch symtab to use fxhash hashing
This merge request resolves some performance regressions introduced
with the change from isc_symtab_t to isc_hashmap_t.

The key improvements are:

1. Using a faster hash function than both isc_hashmap_t and
   isc_symtab_t. The previous implementation used SipHash, but the
   hashflood resistance properties of SipHash are unneeded for config
   parsing.
2. Shrinking the initial size of the isc_hashmap_t used inside
   isc_symtab_t. Symtab is mainly used for config parsing, and the
   when used that way it will have between 1 and ~50 keys, but the
   previous implementation initialized a map with 128 slots.
   By initializing a smaller map, we speed up mallocs and optimize for
   the typical case of few config keys.
3. Slight optimization of the string matching in the hashmap, so that
   the tail is handled in a single load + comparison, instead of byte
   by byte.
   Of the three improvements, this is the least important.
2025-03-20 11:26:09 +01:00

293 lines
7.1 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.
*/
/*! \file */
#include <stdbool.h>
#include <isc/ascii.h>
#include <isc/fxhash.h>
#include <isc/hash.h>
#include <isc/hashmap.h>
#include <isc/magic.h>
#include <isc/mem.h>
#include <isc/string.h>
#include <isc/symtab.h>
#include <isc/util.h>
typedef struct elt {
void *key;
size_t size;
unsigned int type;
isc_symvalue_t value;
} elt_t;
/* 4 bits means 16 entries at creation, which matches the common use of
* symtab */
#define ISC_SYMTAB_INIT_HASH_BITS 4
#define SYMTAB_MAGIC ISC_MAGIC('S', 'y', 'm', 'T')
#define VALID_SYMTAB(st) ISC_MAGIC_VALID(st, SYMTAB_MAGIC)
struct isc_symtab {
/* Unlocked. */
unsigned int magic;
isc_mem_t *mctx;
isc_symtabaction_t undefine_action;
void *undefine_arg;
isc_hashmap_t *hashmap;
bool case_sensitive;
};
static void
elt_destroy(isc_symtab_t *symtab, elt_t *elt) {
if (symtab->undefine_action != NULL) {
(symtab->undefine_action)(elt->key, elt->type, elt->value,
symtab->undefine_arg);
}
isc_mem_put(symtab->mctx, elt, sizeof(*elt));
}
void
isc_symtab_create(isc_mem_t *mctx, isc_symtabaction_t undefine_action,
void *undefine_arg, bool case_sensitive,
isc_symtab_t **symtabp) {
REQUIRE(mctx != NULL);
REQUIRE(symtabp != NULL && *symtabp == NULL);
isc_symtab_t *symtab = isc_mem_get(mctx, sizeof(*symtab));
*symtab = (isc_symtab_t){
.undefine_action = undefine_action,
.undefine_arg = undefine_arg,
.case_sensitive = case_sensitive,
.magic = SYMTAB_MAGIC,
};
isc_mem_attach(mctx, &symtab->mctx);
isc_hashmap_create(symtab->mctx, ISC_SYMTAB_INIT_HASH_BITS,
&symtab->hashmap);
*symtabp = symtab;
}
void
isc_symtab_destroy(isc_symtab_t **symtabp) {
REQUIRE(symtabp != NULL && VALID_SYMTAB(*symtabp));
isc_result_t result;
isc_hashmap_iter_t *it = NULL;
isc_symtab_t *symtab = *symtabp;
*symtabp = NULL;
symtab->magic = 0;
isc_hashmap_iter_create(symtab->hashmap, &it);
for (result = isc_hashmap_iter_first(it); result == ISC_R_SUCCESS;
result = isc_hashmap_iter_delcurrent_next(it))
{
elt_t *elt = NULL;
isc_hashmap_iter_current(it, (void **)&elt);
elt_destroy(symtab, elt);
}
INSIST(result == ISC_R_NOMORE);
isc_hashmap_iter_destroy(&it);
isc_hashmap_destroy(&symtab->hashmap);
isc_mem_putanddetach(&symtab->mctx, symtab, sizeof(*symtab));
}
static bool
elt__match(void *node, const void *key0, bool case_sensitive) {
const elt_t *elt = node;
const elt_t *key = key0;
if (elt->size != key->size) {
return false;
}
if (elt->type != key->type) {
return false;
}
if (case_sensitive) {
return memcmp(elt->key, key->key, key->size) == 0;
} else {
return isc_ascii_lowerequal(elt->key, key->key, key->size);
}
}
static bool
elt_match_case(void *node, const void *key) {
return elt__match(node, key, true);
}
static bool
elt_match_nocase(void *node, const void *key) {
return elt__match(node, key, false);
}
static inline uint32_t
elt_hash(elt_t *restrict elt, bool case_sensitive) {
const uint8_t *ptr = elt->key;
size_t len = elt->size;
return fx_hash_bytes(0, ptr, len, case_sensitive);
}
isc_result_t
isc_symtab_lookup(isc_symtab_t *symtab, const char *key, unsigned int type,
isc_symvalue_t *valuep) {
REQUIRE(VALID_SYMTAB(symtab));
REQUIRE(key != NULL);
REQUIRE(type != 0);
elt_t *found = NULL;
elt_t elt = {
.key = UNCONST(key),
.size = strlen(key),
.type = type,
};
uint32_t elt_hashval = elt_hash(&elt, symtab->case_sensitive);
isc_hashmap_match_fn elt_match = symtab->case_sensitive
? elt_match_case
: elt_match_nocase;
isc_result_t result = isc_hashmap_find(
symtab->hashmap, elt_hashval, elt_match, &elt, (void **)&found);
if (result == ISC_R_SUCCESS) {
SET_IF_NOT_NULL(valuep, found->value);
}
return result;
}
isc_result_t
isc_symtab_define(isc_symtab_t *symtab, const char *key, unsigned int type,
isc_symvalue_t value, isc_symexists_t exists_policy) {
return isc_symtab_define_and_return(symtab, key, type, value,
exists_policy, NULL);
}
isc_result_t
isc_symtab_define_and_return(isc_symtab_t *symtab, const char *key,
unsigned int type, isc_symvalue_t value,
isc_symexists_t exists_policy,
isc_symvalue_t *valuep) {
REQUIRE(VALID_SYMTAB(symtab));
REQUIRE(key != NULL);
REQUIRE(type != 0);
isc_result_t result;
elt_t *found = NULL;
elt_t *elt = isc_mem_get(symtab->mctx, sizeof(*elt));
*elt = (elt_t){
.key = UNCONST(key),
.size = strlen(key),
.type = type,
.value = value,
};
uint32_t elt_hashval = elt_hash(elt, symtab->case_sensitive);
isc_hashmap_match_fn elt_match = symtab->case_sensitive
? elt_match_case
: elt_match_nocase;
again:
result = isc_hashmap_add(symtab->hashmap, elt_hashval, elt_match, elt,
(void *)elt, (void *)&found);
if (result == ISC_R_SUCCESS) {
SET_IF_NOT_NULL(valuep, elt->value);
return ISC_R_SUCCESS;
}
switch (exists_policy) {
case isc_symexists_reject:
SET_IF_NOT_NULL(valuep, found->value);
isc_mem_put(symtab->mctx, elt, sizeof(*elt));
return ISC_R_EXISTS;
case isc_symexists_replace:
result = isc_hashmap_delete(symtab->hashmap, elt_hashval,
elt_match, elt);
INSIST(result == ISC_R_SUCCESS);
elt_destroy(symtab, found);
goto again;
default:
UNREACHABLE();
}
return ISC_R_SUCCESS;
}
isc_result_t
isc_symtab_undefine(isc_symtab_t *symtab, const char *key, unsigned int type) {
REQUIRE(VALID_SYMTAB(symtab));
REQUIRE(key != NULL);
REQUIRE(type != 0);
elt_t *found = NULL;
elt_t elt = {
.key = UNCONST(key),
.size = strlen(key),
.type = type,
};
uint32_t elt_hashval = elt_hash(&elt, symtab->case_sensitive);
isc_hashmap_match_fn elt_match = symtab->case_sensitive
? elt_match_case
: elt_match_nocase;
isc_result_t result = isc_hashmap_find(
symtab->hashmap, elt_hashval, elt_match, &elt, (void **)&found);
if (result == ISC_R_NOTFOUND) {
return ISC_R_NOTFOUND;
}
result = isc_hashmap_delete(symtab->hashmap, elt_hashval, elt_match,
&elt);
INSIST(result == ISC_R_SUCCESS);
elt_destroy(symtab, found);
return ISC_R_SUCCESS;
}
unsigned int
isc_symtab_count(isc_symtab_t *symtab) {
REQUIRE(VALID_SYMTAB(symtab));
return isc_hashmap_count(symtab->hashmap);
}
void
isc_symtab_foreach(isc_symtab_t *symtab, isc_symtabforeachaction_t action,
void *arg) {
REQUIRE(VALID_SYMTAB(symtab));
REQUIRE(action != NULL);
isc_result_t result;
isc_hashmap_iter_t *it = NULL;
isc_hashmap_iter_create(symtab->hashmap, &it);
for (result = isc_hashmap_iter_first(it); result == ISC_R_SUCCESS;) {
elt_t *elt = NULL;
isc_hashmap_iter_current(it, (void **)&elt);
if ((action)(elt->key, elt->type, elt->value, arg)) {
elt_destroy(symtab, elt);
result = isc_hashmap_iter_delcurrent_next(it);
} else {
result = isc_hashmap_iter_next(it);
}
}
INSIST(result == ISC_R_NOMORE);
isc_hashmap_iter_destroy(&it);
}