| /* |
| * |
| * 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; } |