| /* |
| * Copyright 2012-2015 Samy Al Bahra. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. |
| */ |
| |
| #define CK_HT_IM |
| #include <ck_ht.h> |
| |
| /* |
| * This implementation borrows several techniques from Josh Dybnis's |
| * nbds library which can be found at http://code.google.com/p/nbds |
| * |
| * This release currently only includes support for 64-bit platforms. |
| * We can address 32-bit platforms in a future release. |
| */ |
| #include <ck_cc.h> |
| #include <ck_md.h> |
| #include <ck_pr.h> |
| #include <ck_stdint.h> |
| #include <ck_stdbool.h> |
| #include <ck_string.h> |
| |
| #include "ck_ht_hash.h" |
| #include "ck_internal.h" |
| |
| #ifndef CK_HT_BUCKET_LENGTH |
| |
| #ifdef CK_HT_PP |
| #define CK_HT_BUCKET_SHIFT 2ULL |
| #else |
| #define CK_HT_BUCKET_SHIFT 1ULL |
| #endif |
| |
| #define CK_HT_BUCKET_LENGTH (1U << CK_HT_BUCKET_SHIFT) |
| #define CK_HT_BUCKET_MASK (CK_HT_BUCKET_LENGTH - 1) |
| #endif |
| |
| #ifndef CK_HT_PROBE_DEFAULT |
| #define CK_HT_PROBE_DEFAULT 64ULL |
| #endif |
| |
| #if defined(CK_F_PR_LOAD_8) && defined(CK_F_PR_STORE_8) |
| #define CK_HT_WORD uint8_t |
| #define CK_HT_WORD_MAX UINT8_MAX |
| #define CK_HT_STORE(x, y) ck_pr_store_8(x, y) |
| #define CK_HT_LOAD(x) ck_pr_load_8(x) |
| #elif defined(CK_F_PR_LOAD_16) && defined(CK_F_PR_STORE_16) |
| #define CK_HT_WORD uint16_t |
| #define CK_HT_WORD_MAX UINT16_MAX |
| #define CK_HT_STORE(x, y) ck_pr_store_16(x, y) |
| #define CK_HT_LOAD(x) ck_pr_load_16(x) |
| #elif defined(CK_F_PR_LOAD_32) && defined(CK_F_PR_STORE_32) |
| #define CK_HT_WORD uint32_t |
| #define CK_HT_WORD_MAX UINT32_MAX |
| #define CK_HT_STORE(x, y) ck_pr_store_32(x, y) |
| #define CK_HT_LOAD(x) ck_pr_load_32(x) |
| #else |
| #error "ck_ht is not supported on your platform." |
| #endif |
| |
| struct ck_ht_map { |
| unsigned int mode; |
| CK_HT_TYPE deletions; |
| CK_HT_TYPE probe_maximum; |
| CK_HT_TYPE probe_length; |
| CK_HT_TYPE probe_limit; |
| CK_HT_TYPE size; |
| CK_HT_TYPE n_entries; |
| CK_HT_TYPE mask; |
| CK_HT_TYPE capacity; |
| CK_HT_TYPE step; |
| CK_HT_WORD *probe_bound; |
| struct ck_ht_entry *entries; |
| }; |
| |
| void |
| ck_ht_stat(struct ck_ht *table, |
| struct ck_ht_stat *st) |
| { |
| struct ck_ht_map *map = table->map; |
| |
| st->n_entries = map->n_entries; |
| st->probe_maximum = map->probe_maximum; |
| return; |
| } |
| |
| void |
| ck_ht_hash(struct ck_ht_hash *h, |
| struct ck_ht *table, |
| const void *key, |
| uint16_t key_length) |
| { |
| |
| table->h(h, key, key_length, table->seed); |
| return; |
| } |
| |
| void |
| ck_ht_hash_direct(struct ck_ht_hash *h, |
| struct ck_ht *table, |
| uintptr_t key) |
| { |
| |
| ck_ht_hash(h, table, &key, sizeof(key)); |
| return; |
| } |
| |
| static void |
| ck_ht_hash_wrapper(struct ck_ht_hash *h, |
| const void *key, |
| size_t length, |
| uint64_t seed) |
| { |
| |
| h->value = (unsigned long)MurmurHash64A(key, length, seed); |
| return; |
| } |
| |
| static struct ck_ht_map * |
| ck_ht_map_create(struct ck_ht *table, CK_HT_TYPE entries) |
| { |
| struct ck_ht_map *map; |
| CK_HT_TYPE size; |
| uintptr_t prefix; |
| uint32_t n_entries; |
| |
| n_entries = ck_internal_power_2(entries); |
| if (n_entries < CK_HT_BUCKET_LENGTH) |
| n_entries = CK_HT_BUCKET_LENGTH; |
| |
| size = sizeof(struct ck_ht_map) + |
| (sizeof(struct ck_ht_entry) * n_entries + CK_MD_CACHELINE - 1); |
| |
| if (table->mode & CK_HT_WORKLOAD_DELETE) { |
| prefix = sizeof(CK_HT_WORD) * n_entries; |
| size += prefix; |
| } else { |
| prefix = 0; |
| } |
| |
| map = table->m->malloc(size); |
| if (map == NULL) |
| return NULL; |
| |
| map->mode = table->mode; |
| map->size = size; |
| map->probe_limit = ck_internal_max_64(n_entries >> |
| (CK_HT_BUCKET_SHIFT + 2), CK_HT_PROBE_DEFAULT); |
| |
| map->deletions = 0; |
| map->probe_maximum = 0; |
| map->capacity = n_entries; |
| map->step = ck_internal_bsf_64(map->capacity); |
| map->mask = map->capacity - 1; |
| map->n_entries = 0; |
| map->entries = (struct ck_ht_entry *)(((uintptr_t)&map[1] + prefix + |
| CK_MD_CACHELINE - 1) & ~(CK_MD_CACHELINE - 1)); |
| |
| if (table->mode & CK_HT_WORKLOAD_DELETE) { |
| map->probe_bound = (CK_HT_WORD *)&map[1]; |
| memset(map->probe_bound, 0, prefix); |
| } else { |
| map->probe_bound = NULL; |
| } |
| |
| memset(map->entries, 0, sizeof(struct ck_ht_entry) * n_entries); |
| ck_pr_fence_store(); |
| return map; |
| } |
| |
| static inline void |
| ck_ht_map_bound_set(struct ck_ht_map *m, |
| struct ck_ht_hash h, |
| CK_HT_TYPE n_probes) |
| { |
| CK_HT_TYPE offset = h.value & m->mask; |
| |
| if (n_probes > m->probe_maximum) |
| CK_HT_TYPE_STORE(&m->probe_maximum, n_probes); |
| |
| if (m->probe_bound != NULL && m->probe_bound[offset] < n_probes) { |
| if (n_probes >= CK_HT_WORD_MAX) |
| n_probes = CK_HT_WORD_MAX; |
| |
| CK_HT_STORE(&m->probe_bound[offset], n_probes); |
| ck_pr_fence_store(); |
| } |
| |
| return; |
| } |
| |
| static inline CK_HT_TYPE |
| ck_ht_map_bound_get(struct ck_ht_map *m, struct ck_ht_hash h) |
| { |
| CK_HT_TYPE offset = h.value & m->mask; |
| CK_HT_TYPE r = CK_HT_WORD_MAX; |
| |
| if (m->probe_bound != NULL) { |
| r = CK_HT_LOAD(&m->probe_bound[offset]); |
| if (r == CK_HT_WORD_MAX) |
| r = CK_HT_TYPE_LOAD(&m->probe_maximum); |
| } else { |
| r = CK_HT_TYPE_LOAD(&m->probe_maximum); |
| } |
| |
| return r; |
| } |
| |
| static void |
| ck_ht_map_destroy(struct ck_malloc *m, struct ck_ht_map *map, bool defer) |
| { |
| |
| m->free(map, map->size, defer); |
| return; |
| } |
| |
| static inline size_t |
| ck_ht_map_probe_next(struct ck_ht_map *map, size_t offset, ck_ht_hash_t h, size_t probes) |
| { |
| ck_ht_hash_t r; |
| size_t stride; |
| unsigned long level = (unsigned long)probes >> CK_HT_BUCKET_SHIFT; |
| |
| r.value = (h.value >> map->step) >> level; |
| stride = (r.value & ~CK_HT_BUCKET_MASK) << 1 |
| | (r.value & CK_HT_BUCKET_MASK); |
| |
| return (offset + level + |
| (stride | CK_HT_BUCKET_LENGTH)) & map->mask; |
| } |
| |
| bool |
| ck_ht_init(struct ck_ht *table, |
| unsigned int mode, |
| ck_ht_hash_cb_t *h, |
| struct ck_malloc *m, |
| CK_HT_TYPE entries, |
| uint64_t seed) |
| { |
| |
| if (m == NULL || m->malloc == NULL || m->free == NULL) |
| return false; |
| |
| table->m = m; |
| table->mode = mode; |
| table->seed = seed; |
| |
| if (h == NULL) { |
| table->h = ck_ht_hash_wrapper; |
| } else { |
| table->h = h; |
| } |
| |
| table->map = ck_ht_map_create(table, entries); |
| return table->map != NULL; |
| } |
| |
| static struct ck_ht_entry * |
| ck_ht_map_probe_wr(struct ck_ht_map *map, |
| ck_ht_hash_t h, |
| ck_ht_entry_t *snapshot, |
| ck_ht_entry_t **available, |
| const void *key, |
| uint16_t key_length, |
| CK_HT_TYPE *probe_limit, |
| CK_HT_TYPE *probe_wr) |
| { |
| struct ck_ht_entry *bucket, *cursor; |
| struct ck_ht_entry *first = NULL; |
| size_t offset, i, j; |
| CK_HT_TYPE probes = 0; |
| CK_HT_TYPE limit; |
| |
| if (probe_limit == NULL) { |
| limit = ck_ht_map_bound_get(map, h); |
| } else { |
| limit = CK_HT_TYPE_MAX; |
| } |
| |
| offset = h.value & map->mask; |
| for (i = 0; i < map->probe_limit; i++) { |
| /* |
| * Probe on a complete cache line first. Scan forward and wrap around to |
| * the beginning of the cache line. Only when the complete cache line has |
| * been scanned do we move on to the next row. |
| */ |
| bucket = (void *)((uintptr_t)(map->entries + offset) & |
| ~(CK_MD_CACHELINE - 1)); |
| |
| for (j = 0; j < CK_HT_BUCKET_LENGTH; j++) { |
| uint16_t k; |
| |
| if (probes++ > limit) |
| break; |
| |
| cursor = bucket + ((j + offset) & (CK_HT_BUCKET_LENGTH - 1)); |
| |
| /* |
| * It is probably worth it to encapsulate probe state |
| * in order to prevent a complete reprobe sequence in |
| * the case of intermittent writers. |
| */ |
| if (cursor->key == CK_HT_KEY_TOMBSTONE) { |
| if (first == NULL) { |
| first = cursor; |
| *probe_wr = probes; |
| } |
| |
| continue; |
| } |
| |
| if (cursor->key == CK_HT_KEY_EMPTY) |
| goto leave; |
| |
| if (cursor->key == (uintptr_t)key) |
| goto leave; |
| |
| if (map->mode & CK_HT_MODE_BYTESTRING) { |
| void *pointer; |
| |
| /* |
| * Check memoized portion of hash value before |
| * expensive full-length comparison. |
| */ |
| k = ck_ht_entry_key_length(cursor); |
| if (k != key_length) |
| continue; |
| |
| #ifdef CK_HT_PP |
| if ((cursor->value >> CK_MD_VMA_BITS) != ((h.value >> 32) & CK_HT_KEY_MASK)) |
| continue; |
| #else |
| if (cursor->hash != h.value) |
| continue; |
| #endif |
| |
| pointer = ck_ht_entry_key(cursor); |
| if (memcmp(pointer, key, key_length) == 0) |
| goto leave; |
| } |
| } |
| |
| offset = ck_ht_map_probe_next(map, offset, h, probes); |
| } |
| |
| cursor = NULL; |
| |
| leave: |
| if (probe_limit != NULL) { |
| *probe_limit = probes; |
| } else if (first == NULL) { |
| *probe_wr = probes; |
| } |
| |
| *available = first; |
| |
| if (cursor != NULL) { |
| *snapshot = *cursor; |
| } |
| |
| return cursor; |
| } |
| |
| bool |
| ck_ht_gc(struct ck_ht *ht, unsigned long cycles, unsigned long seed) |
| { |
| CK_HT_WORD *bounds = NULL; |
| struct ck_ht_map *map = ht->map; |
| CK_HT_TYPE maximum, i; |
| CK_HT_TYPE size = 0; |
| |
| if (map->n_entries == 0) { |
| CK_HT_TYPE_STORE(&map->probe_maximum, 0); |
| if (map->probe_bound != NULL) |
| memset(map->probe_bound, 0, sizeof(CK_HT_WORD) * map->capacity); |
| |
| return true; |
| } |
| |
| if (cycles == 0) { |
| maximum = 0; |
| |
| if (map->probe_bound != NULL) { |
| size = sizeof(CK_HT_WORD) * map->capacity; |
| bounds = ht->m->malloc(size); |
| if (bounds == NULL) |
| return false; |
| |
| memset(bounds, 0, size); |
| } |
| } else { |
| maximum = map->probe_maximum; |
| } |
| |
| for (i = 0; i < map->capacity; i++) { |
| struct ck_ht_entry *entry, *priority, snapshot; |
| struct ck_ht_hash h; |
| CK_HT_TYPE probes_wr; |
| CK_HT_TYPE offset; |
| |
| entry = &map->entries[(i + seed) & map->mask]; |
| if (entry->key == CK_HT_KEY_EMPTY || |
| entry->key == CK_HT_KEY_TOMBSTONE) { |
| continue; |
| } |
| |
| if (ht->mode & CK_HT_MODE_BYTESTRING) { |
| #ifndef CK_HT_PP |
| h.value = entry->hash; |
| #else |
| ht->h(&h, ck_ht_entry_key(entry), ck_ht_entry_key_length(entry), |
| ht->seed); |
| #endif |
| entry = ck_ht_map_probe_wr(map, h, &snapshot, &priority, |
| ck_ht_entry_key(entry), |
| ck_ht_entry_key_length(entry), |
| NULL, &probes_wr); |
| } else { |
| #ifndef CK_HT_PP |
| h.value = entry->hash; |
| #else |
| ht->h(&h, &entry->key, sizeof(entry->key), ht->seed); |
| #endif |
| entry = ck_ht_map_probe_wr(map, h, &snapshot, &priority, |
| (void *)entry->key, |
| sizeof(entry->key), |
| NULL, &probes_wr); |
| } |
| |
| offset = h.value & map->mask; |
| |
| if (priority != NULL) { |
| CK_HT_TYPE_STORE(&map->deletions, map->deletions + 1); |
| ck_pr_fence_store(); |
| #ifndef CK_HT_PP |
| CK_HT_TYPE_STORE(&priority->key_length, entry->key_length); |
| CK_HT_TYPE_STORE(&priority->hash, entry->hash); |
| #endif |
| ck_pr_store_ptr_unsafe(&priority->value, (void *)entry->value); |
| ck_pr_fence_store(); |
| ck_pr_store_ptr_unsafe(&priority->key, (void *)entry->key); |
| ck_pr_fence_store(); |
| CK_HT_TYPE_STORE(&map->deletions, map->deletions + 1); |
| ck_pr_fence_store(); |
| ck_pr_store_ptr_unsafe(&entry->key, (void *)CK_HT_KEY_TOMBSTONE); |
| ck_pr_fence_store(); |
| } |
| |
| if (cycles == 0) { |
| if (probes_wr > maximum) |
| maximum = probes_wr; |
| |
| if (probes_wr >= CK_HT_WORD_MAX) |
| probes_wr = CK_HT_WORD_MAX; |
| |
| if (bounds != NULL && probes_wr > bounds[offset]) |
| bounds[offset] = probes_wr; |
| } else if (--cycles == 0) |
| break; |
| } |
| |
| if (maximum != map->probe_maximum) |
| CK_HT_TYPE_STORE(&map->probe_maximum, maximum); |
| |
| if (bounds != NULL) { |
| for (i = 0; i < map->capacity; i++) |
| CK_HT_STORE(&map->probe_bound[i], bounds[i]); |
| |
| ht->m->free(bounds, size, false); |
| } |
| |
| return true; |
| } |
| |
| static struct ck_ht_entry * |
| ck_ht_map_probe_rd(struct ck_ht_map *map, |
| ck_ht_hash_t h, |
| ck_ht_entry_t *snapshot, |
| const void *key, |
| uint16_t key_length) |
| { |
| struct ck_ht_entry *bucket, *cursor; |
| size_t offset, i, j; |
| CK_HT_TYPE probes = 0; |
| CK_HT_TYPE probe_maximum; |
| |
| #ifndef CK_HT_PP |
| CK_HT_TYPE d = 0; |
| CK_HT_TYPE d_prime = 0; |
| retry: |
| #endif |
| |
| probe_maximum = ck_ht_map_bound_get(map, h); |
| offset = h.value & map->mask; |
| |
| for (i = 0; i < map->probe_limit; i++) { |
| /* |
| * Probe on a complete cache line first. Scan forward and wrap around to |
| * the beginning of the cache line. Only when the complete cache line has |
| * been scanned do we move on to the next row. |
| */ |
| bucket = (void *)((uintptr_t)(map->entries + offset) & |
| ~(CK_MD_CACHELINE - 1)); |
| |
| for (j = 0; j < CK_HT_BUCKET_LENGTH; j++) { |
| uint16_t k; |
| |
| if (probes++ > probe_maximum) |
| return NULL; |
| |
| cursor = bucket + ((j + offset) & (CK_HT_BUCKET_LENGTH - 1)); |
| |
| #ifdef CK_HT_PP |
| snapshot->key = (uintptr_t)ck_pr_load_ptr(&cursor->key); |
| ck_pr_fence_load(); |
| snapshot->value = (uintptr_t)ck_pr_load_ptr(&cursor->value); |
| #else |
| d = CK_HT_TYPE_LOAD(&map->deletions); |
| snapshot->key = (uintptr_t)ck_pr_load_ptr(&cursor->key); |
| ck_pr_fence_load(); |
| snapshot->key_length = CK_HT_TYPE_LOAD(&cursor->key_length); |
| snapshot->hash = CK_HT_TYPE_LOAD(&cursor->hash); |
| snapshot->value = (uintptr_t)ck_pr_load_ptr(&cursor->value); |
| #endif |
| |
| /* |
| * It is probably worth it to encapsulate probe state |
| * in order to prevent a complete reprobe sequence in |
| * the case of intermittent writers. |
| */ |
| if (snapshot->key == CK_HT_KEY_TOMBSTONE) |
| continue; |
| |
| if (snapshot->key == CK_HT_KEY_EMPTY) |
| goto leave; |
| |
| if (snapshot->key == (uintptr_t)key) |
| goto leave; |
| |
| if (map->mode & CK_HT_MODE_BYTESTRING) { |
| void *pointer; |
| |
| /* |
| * Check memoized portion of hash value before |
| * expensive full-length comparison. |
| */ |
| k = ck_ht_entry_key_length(snapshot); |
| if (k != key_length) |
| continue; |
| #ifdef CK_HT_PP |
| if ((snapshot->value >> CK_MD_VMA_BITS) != ((h.value >> 32) & CK_HT_KEY_MASK)) |
| continue; |
| #else |
| if (snapshot->hash != h.value) |
| continue; |
| |
| d_prime = CK_HT_TYPE_LOAD(&map->deletions); |
| |
| /* |
| * It is possible that the slot was |
| * replaced, initiate a re-probe. |
| */ |
| if (d != d_prime) |
| goto retry; |
| #endif |
| |
| pointer = ck_ht_entry_key(snapshot); |
| if (memcmp(pointer, key, key_length) == 0) |
| goto leave; |
| } |
| } |
| |
| offset = ck_ht_map_probe_next(map, offset, h, probes); |
| } |
| |
| return NULL; |
| |
| leave: |
| return cursor; |
| } |
| |
| CK_HT_TYPE |
| ck_ht_count(struct ck_ht *table) |
| { |
| struct ck_ht_map *map = ck_pr_load_ptr(&table->map); |
| |
| return CK_HT_TYPE_LOAD(&map->n_entries); |
| } |
| |
| bool |
| ck_ht_next(struct ck_ht *table, |
| struct ck_ht_iterator *i, |
| struct ck_ht_entry **entry) |
| { |
| struct ck_ht_map *map = table->map; |
| uintptr_t key; |
| |
| if (i->offset >= map->capacity) |
| return false; |
| |
| do { |
| key = map->entries[i->offset].key; |
| if (key != CK_HT_KEY_EMPTY && key != CK_HT_KEY_TOMBSTONE) |
| break; |
| } while (++i->offset < map->capacity); |
| |
| if (i->offset >= map->capacity) |
| return false; |
| |
| *entry = map->entries + i->offset++; |
| return true; |
| } |
| |
| bool |
| ck_ht_reset_size_spmc(struct ck_ht *table, CK_HT_TYPE size) |
| { |
| struct ck_ht_map *map, *update; |
| |
| map = table->map; |
| update = ck_ht_map_create(table, size); |
| if (update == NULL) |
| return false; |
| |
| ck_pr_store_ptr_unsafe(&table->map, update); |
| ck_ht_map_destroy(table->m, map, true); |
| return true; |
| } |
| |
| bool |
| ck_ht_reset_spmc(struct ck_ht *table) |
| { |
| struct ck_ht_map *map = table->map; |
| |
| return ck_ht_reset_size_spmc(table, map->capacity); |
| } |
| |
| bool |
| ck_ht_grow_spmc(struct ck_ht *table, CK_HT_TYPE capacity) |
| { |
| struct ck_ht_map *map, *update; |
| struct ck_ht_entry *bucket, *previous; |
| struct ck_ht_hash h; |
| size_t k, i, j, offset; |
| CK_HT_TYPE probes; |
| |
| restart: |
| map = table->map; |
| |
| if (map->capacity >= capacity) |
| return false; |
| |
| update = ck_ht_map_create(table, capacity); |
| if (update == NULL) |
| return false; |
| |
| for (k = 0; k < map->capacity; k++) { |
| previous = &map->entries[k]; |
| |
| if (previous->key == CK_HT_KEY_EMPTY || previous->key == CK_HT_KEY_TOMBSTONE) |
| continue; |
| |
| if (table->mode & CK_HT_MODE_BYTESTRING) { |
| #ifdef CK_HT_PP |
| void *key; |
| uint16_t key_length; |
| |
| key = ck_ht_entry_key(previous); |
| key_length = ck_ht_entry_key_length(previous); |
| #endif |
| |
| #ifndef CK_HT_PP |
| h.value = previous->hash; |
| #else |
| table->h(&h, key, key_length, table->seed); |
| #endif |
| } else { |
| #ifndef CK_HT_PP |
| h.value = previous->hash; |
| #else |
| table->h(&h, &previous->key, sizeof(previous->key), table->seed); |
| #endif |
| } |
| |
| offset = h.value & update->mask; |
| probes = 0; |
| |
| for (i = 0; i < update->probe_limit; i++) { |
| bucket = (void *)((uintptr_t)(update->entries + offset) & ~(CK_MD_CACHELINE - 1)); |
| |
| for (j = 0; j < CK_HT_BUCKET_LENGTH; j++) { |
| struct ck_ht_entry *cursor = bucket + ((j + offset) & (CK_HT_BUCKET_LENGTH - 1)); |
| |
| probes++; |
| if (CK_CC_LIKELY(cursor->key == CK_HT_KEY_EMPTY)) { |
| *cursor = *previous; |
| update->n_entries++; |
| ck_ht_map_bound_set(update, h, probes); |
| break; |
| } |
| } |
| |
| if (j < CK_HT_BUCKET_LENGTH) |
| break; |
| |
| offset = ck_ht_map_probe_next(update, offset, h, probes); |
| } |
| |
| if (i == update->probe_limit) { |
| /* |
| * We have hit the probe limit, the map needs to be even |
| * larger. |
| */ |
| ck_ht_map_destroy(table->m, update, false); |
| capacity <<= 1; |
| goto restart; |
| } |
| } |
| |
| ck_pr_fence_store(); |
| ck_pr_store_ptr_unsafe(&table->map, update); |
| ck_ht_map_destroy(table->m, map, true); |
| return true; |
| } |
| |
| bool |
| ck_ht_remove_spmc(struct ck_ht *table, |
| ck_ht_hash_t h, |
| ck_ht_entry_t *entry) |
| { |
| struct ck_ht_map *map; |
| struct ck_ht_entry *candidate, snapshot; |
| |
| map = table->map; |
| |
| if (table->mode & CK_HT_MODE_BYTESTRING) { |
| candidate = ck_ht_map_probe_rd(map, h, &snapshot, |
| ck_ht_entry_key(entry), |
| ck_ht_entry_key_length(entry)); |
| } else { |
| candidate = ck_ht_map_probe_rd(map, h, &snapshot, |
| (void *)entry->key, |
| sizeof(entry->key)); |
| } |
| |
| /* No matching entry was found. */ |
| if (candidate == NULL || snapshot.key == CK_HT_KEY_EMPTY) |
| return false; |
| |
| *entry = snapshot; |
| |
| ck_pr_store_ptr_unsafe(&candidate->key, (void *)CK_HT_KEY_TOMBSTONE); |
| ck_pr_fence_store(); |
| CK_HT_TYPE_STORE(&map->n_entries, map->n_entries - 1); |
| return true; |
| } |
| |
| bool |
| ck_ht_get_spmc(struct ck_ht *table, |
| ck_ht_hash_t h, |
| ck_ht_entry_t *entry) |
| { |
| struct ck_ht_entry *candidate, snapshot; |
| struct ck_ht_map *map; |
| CK_HT_TYPE d, d_prime; |
| |
| restart: |
| map = ck_pr_load_ptr(&table->map); |
| |
| /* |
| * Platforms that cannot read key and key_length atomically must reprobe |
| * on the scan of any single entry. |
| */ |
| d = CK_HT_TYPE_LOAD(&map->deletions); |
| |
| if (table->mode & CK_HT_MODE_BYTESTRING) { |
| candidate = ck_ht_map_probe_rd(map, h, &snapshot, |
| ck_ht_entry_key(entry), ck_ht_entry_key_length(entry)); |
| } else { |
| candidate = ck_ht_map_probe_rd(map, h, &snapshot, |
| (void *)entry->key, sizeof(entry->key)); |
| } |
| |
| d_prime = CK_HT_TYPE_LOAD(&map->deletions); |
| if (d != d_prime) { |
| /* |
| * It is possible we have read (K, V'). Only valid states are |
| * (K, V), (K', V') and (T, V). Restart load operation in face |
| * of concurrent deletions or replacements. |
| */ |
| goto restart; |
| } |
| |
| if (candidate == NULL || snapshot.key == CK_HT_KEY_EMPTY) |
| return false; |
| |
| *entry = snapshot; |
| return true; |
| } |
| |
| bool |
| ck_ht_set_spmc(struct ck_ht *table, |
| ck_ht_hash_t h, |
| ck_ht_entry_t *entry) |
| { |
| struct ck_ht_entry snapshot, *candidate, *priority; |
| struct ck_ht_map *map; |
| CK_HT_TYPE probes, probes_wr; |
| bool empty = false; |
| |
| for (;;) { |
| map = table->map; |
| |
| if (table->mode & CK_HT_MODE_BYTESTRING) { |
| candidate = ck_ht_map_probe_wr(map, h, &snapshot, &priority, |
| ck_ht_entry_key(entry), |
| ck_ht_entry_key_length(entry), |
| &probes, &probes_wr); |
| } else { |
| candidate = ck_ht_map_probe_wr(map, h, &snapshot, &priority, |
| (void *)entry->key, |
| sizeof(entry->key), |
| &probes, &probes_wr); |
| } |
| |
| if (priority != NULL) { |
| probes = probes_wr; |
| break; |
| } |
| |
| if (candidate != NULL) |
| break; |
| |
| if (ck_ht_grow_spmc(table, map->capacity << 1) == false) |
| return false; |
| } |
| |
| if (candidate == NULL) { |
| candidate = priority; |
| empty = true; |
| } |
| |
| if (candidate->key != CK_HT_KEY_EMPTY && |
| priority != NULL && candidate != priority) { |
| /* |
| * Entry is moved into another position in probe sequence. |
| * We avoid a state of (K, B) (where [K, B] -> [K', B]) by |
| * guaranteeing a forced reprobe before transitioning from K to |
| * T. (K, B) implies (K, B, D') so we will reprobe successfully |
| * from this transient state. |
| */ |
| probes = probes_wr; |
| |
| #ifndef CK_HT_PP |
| CK_HT_TYPE_STORE(&priority->key_length, entry->key_length); |
| CK_HT_TYPE_STORE(&priority->hash, entry->hash); |
| #endif |
| |
| /* |
| * Readers must observe version counter change before they |
| * observe re-use. If they observe re-use, it is at most |
| * a tombstone. |
| */ |
| if (priority->value == CK_HT_KEY_TOMBSTONE) { |
| CK_HT_TYPE_STORE(&map->deletions, map->deletions + 1); |
| ck_pr_fence_store(); |
| } |
| |
| ck_pr_store_ptr_unsafe(&priority->value, (void *)entry->value); |
| ck_pr_fence_store(); |
| ck_pr_store_ptr_unsafe(&priority->key, (void *)entry->key); |
| ck_pr_fence_store(); |
| |
| /* |
| * Make sure that readers who observe the tombstone would |
| * also observe counter change. |
| */ |
| CK_HT_TYPE_STORE(&map->deletions, map->deletions + 1); |
| ck_pr_fence_store(); |
| |
| ck_pr_store_ptr_unsafe(&candidate->key, (void *)CK_HT_KEY_TOMBSTONE); |
| ck_pr_fence_store(); |
| } else { |
| /* |
| * In this case we are inserting a new entry or replacing |
| * an existing entry. Yes, this can be combined into above branch, |
| * but isn't because you are actually looking at dying code |
| * (ck_ht is effectively deprecated and is being replaced soon). |
| */ |
| bool replace = candidate->key != CK_HT_KEY_EMPTY && |
| candidate->key != CK_HT_KEY_TOMBSTONE; |
| |
| if (priority != NULL) { |
| if (priority->key == CK_HT_KEY_TOMBSTONE) { |
| CK_HT_TYPE_STORE(&map->deletions, map->deletions + 1); |
| ck_pr_fence_store(); |
| } |
| |
| candidate = priority; |
| probes = probes_wr; |
| } |
| |
| #ifdef CK_HT_PP |
| ck_pr_store_ptr_unsafe(&candidate->value, (void *)entry->value); |
| ck_pr_fence_store(); |
| ck_pr_store_ptr_unsafe(&candidate->key, (void *)entry->key); |
| #else |
| CK_HT_TYPE_STORE(&candidate->key_length, entry->key_length); |
| CK_HT_TYPE_STORE(&candidate->hash, entry->hash); |
| ck_pr_store_ptr_unsafe(&candidate->value, (void *)entry->value); |
| ck_pr_fence_store(); |
| ck_pr_store_ptr_unsafe(&candidate->key, (void *)entry->key); |
| #endif |
| |
| /* |
| * If we are insert a new entry then increment number |
| * of entries associated with map. |
| */ |
| if (replace == false) |
| CK_HT_TYPE_STORE(&map->n_entries, map->n_entries + 1); |
| } |
| |
| ck_ht_map_bound_set(map, h, probes); |
| |
| /* Enforce a load factor of 0.5. */ |
| if (map->n_entries * 2 > map->capacity) |
| ck_ht_grow_spmc(table, map->capacity << 1); |
| |
| if (empty == true) { |
| entry->key = CK_HT_KEY_EMPTY; |
| } else { |
| *entry = snapshot; |
| } |
| |
| return true; |
| } |
| |
| bool |
| ck_ht_put_spmc(struct ck_ht *table, |
| ck_ht_hash_t h, |
| ck_ht_entry_t *entry) |
| { |
| struct ck_ht_entry snapshot, *candidate, *priority; |
| struct ck_ht_map *map; |
| CK_HT_TYPE probes, probes_wr; |
| |
| for (;;) { |
| map = table->map; |
| |
| if (table->mode & CK_HT_MODE_BYTESTRING) { |
| candidate = ck_ht_map_probe_wr(map, h, &snapshot, &priority, |
| ck_ht_entry_key(entry), |
| ck_ht_entry_key_length(entry), |
| &probes, &probes_wr); |
| } else { |
| candidate = ck_ht_map_probe_wr(map, h, &snapshot, &priority, |
| (void *)entry->key, |
| sizeof(entry->key), |
| &probes, &probes_wr); |
| } |
| |
| if (candidate != NULL || priority != NULL) |
| break; |
| |
| if (ck_ht_grow_spmc(table, map->capacity << 1) == false) |
| return false; |
| } |
| |
| if (priority != NULL) { |
| /* Version counter is updated before re-use. */ |
| CK_HT_TYPE_STORE(&map->deletions, map->deletions + 1); |
| ck_pr_fence_store(); |
| |
| /* Re-use tombstone if one was found. */ |
| candidate = priority; |
| probes = probes_wr; |
| } else if (candidate->key != CK_HT_KEY_EMPTY && |
| candidate->key != CK_HT_KEY_TOMBSTONE) { |
| /* |
| * If the snapshot key is non-empty and the value field is not |
| * a tombstone then an identical key was found. As store does |
| * not implement replacement, we will fail. |
| */ |
| return false; |
| } |
| |
| ck_ht_map_bound_set(map, h, probes); |
| |
| #ifdef CK_HT_PP |
| ck_pr_store_ptr_unsafe(&candidate->value, (void *)entry->value); |
| ck_pr_fence_store(); |
| ck_pr_store_ptr_unsafe(&candidate->key, (void *)entry->key); |
| #else |
| CK_HT_TYPE_STORE(&candidate->key_length, entry->key_length); |
| CK_HT_TYPE_STORE(&candidate->hash, entry->hash); |
| ck_pr_store_ptr_unsafe(&candidate->value, (void *)entry->value); |
| ck_pr_fence_store(); |
| ck_pr_store_ptr_unsafe(&candidate->key, (void *)entry->key); |
| #endif |
| |
| CK_HT_TYPE_STORE(&map->n_entries, map->n_entries + 1); |
| |
| /* Enforce a load factor of 0.5. */ |
| if (map->n_entries * 2 > map->capacity) |
| ck_ht_grow_spmc(table, map->capacity << 1); |
| |
| return true; |
| } |
| |
| void |
| ck_ht_destroy(struct ck_ht *table) |
| { |
| |
| ck_ht_map_destroy(table->m, table->map, false); |
| return; |
| } |