Concurrent trie-hash map library
Concurrent trie-hash map library – a general purpose associative array,
combining the elements of hashing and radix trie. Highlights:
The implementation is written in C11 and distributed under the 2-clause
BSD license.
NOTE: Delete operations (the key/data destruction) must be synchronised with
the readers using some reclamation mechanism. You can use the Epoch-based
Reclamation (EBR) library provided HERE.
References (some, but not all, key ideas are based on these papers):
thmap_t *thmap_create(uintptr_t baseptr, const thmap_ops_t *ops, unsigned flags)
ops
parameter canthmap_ops_t
below). In such case, the baseptr
is the base (start)ops
NULL
, then malloc(3) and free(3) will be used as thebaseptr
should beflags
are:
THMAP_NOCOPY
: the keys on insert will not be copied and the givenTHMAP_SETROOT
: indicate that the root of the map will be manuallythmap_setroot
routine; by default, the map is initialisedthmap_create
.void thmap_destroy(thmap_t *hmap)
void *thmap_get(thmap_t *hmap, const void *key, size_t len)
NULL
if the key is not found (see the caveats section).void *thmap_put(thmap_t *hmap, const void *key, size_t len, void *val)
val
to test whether the insert was successful.void *thmap_del(thmap_t *hmap, const void *key, size_t len)
NULL
. The memory associated with the entry isthmap_stage_gc
and thmap_gc
routines.void *thmap_stage_gc(thmap_t *hmap)
thmap_gc
. Not calling thevoid thmap_gc(thmap_t *hmap, void *ref)
thmap_stage_gc
.If the map is created using the THMAP_SETROOT
flag, then the following
functions are applicable:
void thmap_setroot(thmap_t *thmap, uintptr_t root_offset)
thmap_ops_t::alloc
routine. Return 0 on successuintptr_t thmap_getroot(const thmap_t *thmap)
The thmap_ops_t
structure has the following members:
uintptr_t (*alloc)(size_t len)
len
. The address must be relative to thevoid (*free)(uintptr_t addr, size_t len)
len
Internally, offsets from the base pointer are used to organise the access
to the data structure. This allows user to store the data structure in the
shared memory, using the allocation/free functions. The keys will also be
copied using the custom functions; if THMAP_NOCOPY
is set, then the keys
must belong to the same shared memory object.
The implementation was extensively tested on a 24-core x86 machine,
see the stress test for the details on the technique.
The implementation uses pointer tagging and atomic operations. This
requires the base address and the allocations to provide at least word
alignment.
While the NULL
values may be inserted, thmap_get
and thmap_del
cannot indicate whether the key was not found or a key with a NULL value
was found. If the caller needs to indicate an “empty” value, it can use a
special pointer value, such as (void *)(uintptr_t)0x1
.
The library has been benchmarked using different key profiles (8 to 256
bytes), set sizes (hundreds, thousands, millions) and ratio between readers
and writers (from 60:40 to 90:10). In all cases it demonstrated nearly
linear scalability (up to the number of cores). Here is an example result
when matched with the C++ libcuckoo library:
Disclaimer: benchmark results, however, depend on many aspects (workload,
hardware characteristics, methodology, etc). Ultimately, readers are
encouraged to perform their own benchmarks.
Simple case backed by malloc(3), which could be used in multi-threaded
environment:
thmap_t *kvmap;
struct obj *obj;
kvmap = thmap_create(0, NULL);
assert(kvmap != NULL);
...
obj = obj_create();
thmap_put(kvmap, "test", sizeof("test") - 1, obj);
...
obj = thmap_get(kvmap, "test", sizeof("test") - 1);
...
thmap_destroy(kvmap);
Just build the package, install it and link the library using the
-lthmap
flag.
cd pkg && make rpm
cd pkg && make deb