#define _GNU_SOURCE
#include <unistd.h>
#include <atomic_ops.h>
#include <sys/time.h>
#include <strings.h>
#include <sched.h>
#define HAVE_DEBUG
#define HAVE_STRACE
#include "debug.h"
#undef STRACE
#define STRACE 0
static void yield() { sched_yield(); }
static void read_barrier() { AO_nop_read(); }
static void write_barrier() { AO_nop_write(); }
static int cas(void *addr, const void *nval, const void *oval) {
return AO_compare_and_swap(addr, (AO_t)oval, (AO_t)nval);
}
typedef struct entry entry;
struct entry {
volatile void *_key;
volatile void *_val;
volatile unsigned int _hash;
};
typedef struct header header;
struct header {
volatile AO_t _btodo; unsigned long len; header *prev; volatile AO_t _bdone; entry kvs[0]; };
typedef int (hashmap_key_equals)(void *left, void *right);
typedef unsigned int (hashmap_key_hash)(void *key);
typedef void (hashmap_key_free)(void *key);
typedef struct HashMap HashMap;
struct HashMap {
volatile AO_t _size; volatile unsigned int changes; volatile header *_kvs; volatile header *_nkvs;
hashmap_key_equals *equals_func;
hashmap_key_hash *hash_func;
hashmap_key_free *free_func;
};
#define INITIAL_SIZE 4
#define REPROBE_LIMIT 17
#define BLOCK_SIZE (1024 * 8)
#define null 0
void *IGNORE = "__IGNORE__"; static void *SIZED = "__SIZED__"; static void *DELETED = "__DELETED__";
static header * kvs_promise = (header *)1;
static header * header_new(unsigned int len) {
header *h = malloc(sizeof(header) + sizeof(entry) * len);
assert(h);
h->len = len;
h->_btodo = 0;
h->_bdone = 0;
h->prev = 0;
return h;
}
static unsigned long current_time() { struct timeval time;
gettimeofday(&time, 0);
return time.tv_sec;
}
static void push_old_kvs(header *nkvs, header *okvs) {
nkvs->prev = okvs;
okvs->_btodo = current_time(); }
static int free_old_kvs2(header *kvs, unsigned long cutoff) {
if (!kvs) return 1;
if (free_old_kvs2(kvs->prev, cutoff)) {
kvs->prev = 0;
if (kvs->_btodo < cutoff) {
free(kvs);
return 1;
}
}
return 0;
}
static void free_old_kvs(header *nkvs) {
unsigned long cutoff = current_time() - 30; if (free_old_kvs2(nkvs->prev, cutoff)) {
nkvs->prev = 0;
}
}
inline static entry * _load(header *kvs, int idx) {
assert(idx >= 0);
assert(idx < kvs->len);
return kvs->kvs + idx;
}
inline static header * getkvs(HashMap *map) { return (header *)map->_kvs; }
inline static void * getkey(entry *e) { return (void *)e->_key; }
inline static void * getval(entry *e) { return (void *)e->_val; }
inline static unsigned int gethash(entry *e) {
unsigned int h = e->_hash;
while (!h) {
yield(); h = e->_hash; }
return h;
}
HashMap * hashmap_new(hashmap_key_equals *equals_func, hashmap_key_hash *hash_func, hashmap_key_free *free_func) {
assert(sizeof(unsigned long) <= sizeof(AO_t));
HashMap *map = malloc(sizeof(HashMap));
map->_size = 0;
map->changes = 0;
map->equals_func = equals_func;
map->hash_func = hash_func;
map->free_func = free_func;
header *kvs = header_new(INITIAL_SIZE);
bzero(kvs->kvs, sizeof(entry) * INITIAL_SIZE);
map->_kvs = kvs;
map->_nkvs = 0;
return map;
}
static void free_kvs2(header *kvs) { if (kvs == 0) return;
free_kvs2(kvs->prev);
free(kvs);
}
static void free_kvs(HashMap *map, header *kvs) {
free_kvs2(kvs->prev);
for (int i = kvs->len - 1; i >= 0; i--) {
entry *e = _load(kvs, i);
void *k = getkey(e);
assert(k != SIZED);
if (k) map->free_func(k);
}
free(kvs);
}
void hashmap_free(HashMap *map) {
strace("freeing hashmap: %p", map);
free_kvs(map, getkvs(map));
free(map);
}
long hashmap_size(HashMap *map) {
long res = map->_size;
if (res < 0) return 0;
return res;
}
static void _size_update(HashMap *map, int n) {
AO_fetch_and_add(&map->_size, n);
}
static void * _putif(HashMap *map, int resizing, header *kvs, void *key, const unsigned int hash, void *val, void *oldval);
int _zero_block(header *nkvs) {
assert(nkvs); assert(nkvs->len);
unsigned long len = nkvs->len;
unsigned int todo = 1 + (len - 1) / BLOCK_SIZE;
assert(todo > 0);
if (len <= BLOCK_SIZE) assert(todo == 1);
unsigned long block = AO_fetch_and_add(&nkvs->_btodo, 1);
if (block >= todo) { while (nkvs->_bdone < todo) yield(); return 0; }
unsigned int blen = BLOCK_SIZE;
if (block * BLOCK_SIZE + BLOCK_SIZE > len) blen = len - block * BLOCK_SIZE;
bzero(nkvs->kvs + block * BLOCK_SIZE, sizeof(entry) * blen);
unsigned long bdone = AO_fetch_and_add(&nkvs->_bdone, 1);
if (bdone >= todo) return 0; return 1; }
static int _copy_block(HashMap *map, header *okvs, header *nkvs) {
assert(map); assert(okvs); assert(nkvs); assert(nkvs != kvs_promise);
unsigned long len = okvs->len;
unsigned int todo = 1 + (len - 1) / BLOCK_SIZE;
assert(todo > 0);
if (len <= BLOCK_SIZE) assert(todo == 1);
unsigned long block = AO_fetch_and_add(&okvs->_btodo, 1);
if (block >= todo) { while (okvs->_bdone < todo) yield(); return 0; }
unsigned long blen = BLOCK_SIZE;
if (block * BLOCK_SIZE + BLOCK_SIZE > len) blen = len - block * BLOCK_SIZE;
blen = block * BLOCK_SIZE + blen;
for (int i = block * BLOCK_SIZE; i < blen; i++) {
entry *e = _load(okvs, i);
while (1) {
void *k = getkey(e);
if (k) {
void *old = getval(e);
if (cas(&e->_val, SIZED, old)) {
if (DELETED == _putif(map, 1, nkvs, k, gethash(e), old, null)) {
if (!cas(&e->_key, SIZED, k)) fatal("marking deleted key");
map->free_func(k);
}
break;
} else {
strace("we lost race for: %d; retry", i);
}
} else {
if (cas(&e->_key, SIZED, null)) {
break;
} else {
strace("we lost race for empty slot: %d; retry", i);
}
}
}
}
unsigned long bdone = AO_fetch_and_add(&okvs->_bdone, 1);
if (bdone >= todo) return 0; return 1; }
void * _resize(HashMap *map, header *okvs);
void _help_resize(HashMap *map, header *okvs) {
if (map->_kvs != okvs) return;
strace("help resize: %p, %p", map->_kvs, okvs);
header *nkvs = (header *)map->_nkvs;
while (nkvs == 0 || nkvs == kvs_promise) {
if (map->_kvs != okvs) return;
if (nkvs == 0) { _resize(map, okvs);
return;
}
yield(); nkvs = (header *)map->_nkvs;
}
while (map->_kvs == okvs && _zero_block(nkvs));
while (map->_kvs == okvs && _copy_block(map, okvs, nkvs));
while (map->_kvs == okvs) yield(); strace("done: %p, %p", map->_kvs, okvs);
}
void * _resize(HashMap *map, header *okvs) {
assert(map);
strace("maybe resize: %p, %p, %p", map->_kvs, okvs, map->_nkvs);
if (map->_nkvs != null) return SIZED; if (map->_kvs != okvs) return SIZED;
if (cas(&map->_nkvs, kvs_promise, null)) {
if (map->_kvs != okvs) {
if (!cas(&map->_nkvs, null, kvs_promise)) fatal("unpublising late promise");
return SIZED; }
int size = hashmap_size(map);
unsigned int len = okvs->len;
header *nkvs = null;
if (map->changes > (len / 4) && size / (float)len < 0.3f) {
strace("resizing to remove garbage: %d", len);
nkvs = header_new(len);
} else {
strace("resizing: %d (%d <= %d && %.2f >= 0.3)", len * 2, map->changes, (len / 4), size / (float)len);
nkvs = header_new(len * 2);
}
assert(nkvs); assert(nkvs->len);
okvs->_btodo = 0;
okvs->_bdone = 0;
write_barrier(); map->_nkvs = nkvs;
while (_zero_block(nkvs));
while (_copy_block(map, okvs, nkvs));
push_old_kvs(nkvs, okvs);
free_old_kvs(nkvs);
if (!cas(&map->_kvs, nkvs, okvs)) fatal("publishing new map");
if (!cas(&map->_nkvs, null, nkvs)) fatal("unpublising resize in progress");
map->changes = 0;
strace("done resizing: %p[%lu].size: %ld", nkvs, nkvs->len, hashmap_size(map));
return SIZED; }
return SIZED;
}
static void * _get(HashMap *map, header *kvs, void *key, const unsigned int hash) {
const unsigned int len = kvs->len;
int idx = hash & (len - 1);
int reprobe_try = 0;
while (1) {
entry *e = _load(kvs, idx);
void *k = getkey(e);
if (k == 0) return 0; if (k == SIZED) return SIZED;
unsigned int h = gethash(e); if (h == hash) {
read_barrier(); if (map->equals_func(k, key)) {
return getval(e); }
}
if (++reprobe_try >= len) return 0; idx = (idx + 1) & (len - 1); }
}
static void * _putif(HashMap *map, int resizing, header *kvs, void *key, const unsigned int hash, void *val, void *oldval) {
assert(map); assert(kvs);
const unsigned int len = kvs->len;
int idx = hash & (len - 1);
int mustfreekey = 0;
assert(key); assert(hash);
strace("%p %p :: [%s] = %s old: %s", map, kvs, (const char *)key, (const char *)val, (const char *)oldval);
int reprobe_try = 0;
entry *e;
while (1) {
e = _load(kvs, idx);
void *k = getkey(e);
if (k == null) { if (val == null && (oldval == IGNORE || oldval == null)) {
if (resizing) return DELETED; if (cas(&e->_key, null, null)) {
map->free_func(key); return null;
}
}
write_barrier(); if (cas(&e->_key, key, null)) {
e->_hash = hash; break; }
k = getkey(e);
}
assert(k);
if (k == SIZED) return SIZED; unsigned int h = gethash(e);
if (h == hash) {
read_barrier(); if (map->equals_func(k, key)) { mustfreekey = 1; break;
}
}
if (!resizing && ++reprobe_try >= REPROBE_LIMIT) return _resize(map, kvs);
idx = (idx + 1) & (len - 1); }
void *v = getval(e); if (v == SIZED) return SIZED;
if (!resizing && v != null) {
header *nkvs = (header *)map->_nkvs;
if (nkvs != 0 && nkvs != kvs) return SIZED;
if (map->_kvs != kvs) return SIZED;
}
while (1) {
if (oldval != IGNORE && v != oldval) {
if (resizing) fatal("resize: %s = %p != %p new: %p", (const char *)key, v, oldval, val);
return v; }
if (cas(&e->_val, val, v)) {
if (!resizing && v == null && val != null) _size_update(map, 1);
if (!resizing && v != null && val == null) _size_update(map, -1);
if (!resizing) map->changes++;
if (mustfreekey) map->free_func(key); return v; }
v = getval(e);
if (v == SIZED) return SIZED; }
}
void * hashmap_get(HashMap *map, void *key) {
unsigned int hash = map->hash_func(key);
if (!hash) hash = 1;
header *kvs = getkvs(map);
void *res = _get(map, kvs, key, hash);
while (res == SIZED) {
_help_resize(map, kvs);
kvs = getkvs(map);
res = _get(map, kvs, key, hash);
}
return res;
}
void * hashmap_putif(HashMap *map, void *key, const void *val, const void *oldval) {
unsigned int hash = map->hash_func(key);
if (!hash) hash = 1;
header *kvs = getkvs(map);
void *res = _putif(map, 0, kvs, key, hash, (void *)val, (void *)oldval);
while (res == SIZED) {
_help_resize(map, kvs);
kvs = getkvs(map);
res = _putif(map, 0, kvs, key, hash, (void *)val, (void *)oldval);
}
return res;
}
void hashmap_debug(HashMap *map) {
const int len = getkvs(map)->len;
const int size = hashmap_size(map);
float ratio = size / (float)len;
float mb = (sizeof(entry) * len) / (float) (1024 * 1024);
print("%f (%d / %d) = %.0fmb", ratio, size, len, mb);
}