blob: 0cadcd4740218f18abcb2f0811e5eb6929e04bd7 [file] [log] [blame]
/*
*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "src/core/statistics/hash_table.h"
#include <stdio.h>
#include <stddef.h>
#include <grpc/support/log.h>
#include <grpc/support/alloc.h>
#include <grpc/support/port_platform.h>
#define CENSUS_HT_NUM_BUCKETS 1999
/* A single hash table data entry */
typedef struct ht_entry {
census_ht_key key;
void *data;
struct ht_entry *next;
} ht_entry;
/* hash table bucket */
typedef struct bucket {
/* NULL if bucket is empty */
ht_entry *next;
/* -1 if all buckets are empty. */
int32_t prev_non_empty_bucket;
/* -1 if all buckets are empty. */
int32_t next_non_empty_bucket;
} bucket;
struct unresizable_hash_table {
/* Number of entries in the table */
size_t size;
/* Number of buckets */
uint32_t num_buckets;
/* Array of buckets initialized at creation time. Memory consumption is
16 bytes per bucket on a 64-bit platform. */
bucket *buckets;
/* Index of the first non-empty bucket. -1 iff size == 0. */
int32_t first_non_empty_bucket;
/* Index of the last non_empty bucket. -1 iff size == 0. */
int32_t last_non_empty_bucket;
/* Immutable options of this hash table, initialized at creation time. */
census_ht_option options;
};
typedef struct entry_locator {
int32_t bucket_idx;
int is_first_in_chain;
int found;
ht_entry *prev_entry;
} entry_locator;
/* Asserts if option is not valid. */
void check_options(const census_ht_option *option) {
GPR_ASSERT(option != NULL);
GPR_ASSERT(option->num_buckets > 0);
GPR_ASSERT(option->key_type == CENSUS_HT_UINT64 ||
option->key_type == CENSUS_HT_POINTER);
if (option->key_type == CENSUS_HT_UINT64) {
GPR_ASSERT(option->hash == NULL);
} else if (option->key_type == CENSUS_HT_POINTER) {
GPR_ASSERT(option->hash != NULL);
GPR_ASSERT(option->compare_keys != NULL);
}
}
#define REMOVE_NEXT(options, ptr) \
do { \
ht_entry *tmp = (ptr)->next; \
(ptr)->next = tmp->next; \
delete_entry(options, tmp); \
} while (0)
static void delete_entry(const census_ht_option *opt, ht_entry *p) {
if (opt->delete_data != NULL) {
opt->delete_data(p->data);
}
if (opt->delete_key != NULL) {
opt->delete_key(p->key.ptr);
}
gpr_free(p);
}
static uint64_t hash(const census_ht_option *opt, census_ht_key key) {
return opt->key_type == CENSUS_HT_UINT64 ? key.val : opt->hash(key.ptr);
}
census_ht *census_ht_create(const census_ht_option *option) {
int i;
census_ht *ret = NULL;
check_options(option);
ret = (census_ht *)gpr_malloc(sizeof(census_ht));
ret->size = 0;
ret->num_buckets = option->num_buckets;
ret->buckets = (bucket *)gpr_malloc(sizeof(bucket) * ret->num_buckets);
ret->options = *option;
/* initialize each bucket */
for (i = 0; i < ret->options.num_buckets; i++) {
ret->buckets[i].prev_non_empty_bucket = -1;
ret->buckets[i].next_non_empty_bucket = -1;
ret->buckets[i].next = NULL;
}
return ret;
}
static int32_t find_bucket_idx(const census_ht *ht, census_ht_key key) {
return hash(&ht->options, key) % ht->num_buckets;
}
static int keys_match(const census_ht_option *opt, const ht_entry *p,
const census_ht_key key) {
GPR_ASSERT(opt->key_type == CENSUS_HT_UINT64 ||
opt->key_type == CENSUS_HT_POINTER);
if (opt->key_type == CENSUS_HT_UINT64) return p->key.val == key.val;
return !opt->compare_keys((p->key).ptr, key.ptr);
}
static entry_locator ht_find(const census_ht *ht, census_ht_key key) {
entry_locator loc = {0, 0, 0, NULL};
int32_t idx = 0;
ht_entry *ptr = NULL;
GPR_ASSERT(ht != NULL);
idx = find_bucket_idx(ht, key);
ptr = ht->buckets[idx].next;
if (ptr == NULL) {
/* bucket is empty */
return loc;
}
if (keys_match(&ht->options, ptr, key)) {
loc.bucket_idx = idx;
loc.is_first_in_chain = 1;
loc.found = 1;
return loc;
} else {
for (; ptr->next != NULL; ptr = ptr->next) {
if (keys_match(&ht->options, ptr->next, key)) {
loc.bucket_idx = idx;
loc.is_first_in_chain = 0;
loc.found = 1;
loc.prev_entry = ptr;
return loc;
}
}
}
/* Could not find the key */
return loc;
}
void *census_ht_find(const census_ht *ht, census_ht_key key) {
entry_locator loc = ht_find(ht, key);
if (loc.found == 0) {
return NULL;
}
return loc.is_first_in_chain ? ht->buckets[loc.bucket_idx].next->data
: loc.prev_entry->next->data;
}
void census_ht_insert(census_ht *ht, census_ht_key key, void *data) {
int32_t idx = find_bucket_idx(ht, key);
ht_entry *ptr = NULL;
entry_locator loc = ht_find(ht, key);
if (loc.found) {
/* Replace old value with new value. */
ptr = loc.is_first_in_chain ? ht->buckets[loc.bucket_idx].next
: loc.prev_entry->next;
if (ht->options.delete_data != NULL) {
ht->options.delete_data(ptr->data);
}
ptr->data = data;
return;
}
/* first entry in the table. */
if (ht->size == 0) {
ht->buckets[idx].next_non_empty_bucket = -1;
ht->buckets[idx].prev_non_empty_bucket = -1;
ht->first_non_empty_bucket = idx;
ht->last_non_empty_bucket = idx;
} else if (ht->buckets[idx].next == NULL) {
/* first entry in the bucket. */
ht->buckets[ht->last_non_empty_bucket].next_non_empty_bucket = idx;
ht->buckets[idx].prev_non_empty_bucket = ht->last_non_empty_bucket;
ht->buckets[idx].next_non_empty_bucket = -1;
ht->last_non_empty_bucket = idx;
}
ptr = (ht_entry *)gpr_malloc(sizeof(ht_entry));
ptr->key = key;
ptr->data = data;
ptr->next = ht->buckets[idx].next;
ht->buckets[idx].next = ptr;
ht->size++;
}
void census_ht_erase(census_ht *ht, census_ht_key key) {
entry_locator loc = ht_find(ht, key);
if (loc.found == 0) {
/* noop if not found */
return;
}
ht->size--;
if (loc.is_first_in_chain) {
bucket *b = &ht->buckets[loc.bucket_idx];
GPR_ASSERT(b->next != NULL);
/* The only entry in the bucket */
if (b->next->next == NULL) {
int prev = b->prev_non_empty_bucket;
int next = b->next_non_empty_bucket;
if (prev != -1) {
ht->buckets[prev].next_non_empty_bucket = next;
} else {
ht->first_non_empty_bucket = next;
}
if (next != -1) {
ht->buckets[next].prev_non_empty_bucket = prev;
} else {
ht->last_non_empty_bucket = prev;
}
}
REMOVE_NEXT(&ht->options, b);
} else {
GPR_ASSERT(loc.prev_entry->next != NULL);
REMOVE_NEXT(&ht->options, loc.prev_entry);
}
}
/* Returns NULL if input table is empty. */
census_ht_kv *census_ht_get_all_elements(const census_ht *ht, size_t *num) {
census_ht_kv *ret = NULL;
int i = 0;
int32_t idx = -1;
GPR_ASSERT(ht != NULL && num != NULL);
*num = ht->size;
if (*num == 0) {
return NULL;
}
ret = (census_ht_kv *)gpr_malloc(sizeof(census_ht_kv) * ht->size);
idx = ht->first_non_empty_bucket;
while (idx >= 0) {
ht_entry *ptr = ht->buckets[idx].next;
for (; ptr != NULL; ptr = ptr->next) {
ret[i].k = ptr->key;
ret[i].v = ptr->data;
i++;
}
idx = ht->buckets[idx].next_non_empty_bucket;
}
return ret;
}
static void ht_delete_entry_chain(const census_ht_option *options,
ht_entry *first) {
if (first == NULL) {
return;
}
if (first->next != NULL) {
ht_delete_entry_chain(options, first->next);
}
delete_entry(options, first);
}
void census_ht_destroy(census_ht *ht) {
unsigned i;
for (i = 0; i < ht->num_buckets; ++i) {
ht_delete_entry_chain(&ht->options, ht->buckets[i].next);
}
gpr_free(ht->buckets);
gpr_free(ht);
}
size_t census_ht_get_size(const census_ht *ht) { return ht->size; }