implement hash table object; expand test to include that; change all "key"s to uintptr_t

This was SVN commit r2177.
This commit is contained in:
janwas 2005-04-26 20:51:43 +00:00
parent 7327e942d6
commit d8f05aee4b
2 changed files with 172 additions and 38 deletions

View File

@ -128,7 +128,7 @@ static intptr_t active_data_structures;
struct Node
{
Node* next;
void* key;
uintptr_t key;
// <additional_bytes> are allocated here at the caller's discretion.
};
@ -526,7 +526,7 @@ void lfl_free(LFList* list)
// "Find"
// look for a given key in the list; return true iff found.
// pos points to the last inspected node and its successor and predecessor.
static bool list_lookup(LFList* list, void* key, ListPos* pos)
static bool list_lookup(LFList* list, uintptr_t key, ListPos* pos)
{
TLS* tls = tls_get();
assert2(tls != (void*)-1);
@ -575,7 +575,7 @@ try_again:
// the nodes are sorted in ascending key order, so we've either
// found <key>, or it's not in the list.
const void* cur_key = pos->cur->key;
const uintptr_t cur_key = pos->cur->key;
if(cur_key >= key)
return (cur_key == key);
@ -597,7 +597,7 @@ try_again:
// return pointer to "user data" attached to <key>,
// or 0 if not found in the list.
void* lfl_find(LFList* list, void* key)
void* lfl_find(LFList* list, uintptr_t key)
{
ListPos* pos = (ListPos*)alloca(sizeof(ListPos));
if(!list_lookup(list, key, pos))
@ -610,7 +610,7 @@ void* lfl_find(LFList* list, void* key)
// by first checking if already in the list. returns 0 if out of memory,
// otherwise a pointer to "user data" attached to <key>. the optional
// <was_inserted> return variable indicates whether <key> was added.
void* lfl_insert(LFList* list, void* key, size_t additional_bytes, int* was_inserted)
void* lfl_insert(LFList* list, uintptr_t key, size_t additional_bytes, int* was_inserted)
{
TLS* tls = tls_get();
assert2(tls != (void*)-1);
@ -663,7 +663,7 @@ have_node:
// remove from list; return -1 if not found, or 0 on success.
int lfl_erase(LFList* list, void* key)
int lfl_erase(LFList* list, uintptr_t key)
{
TLS* tls = tls_get();
assert2(tls != (void*)-1);
@ -701,30 +701,94 @@ try_again:
//
//////////////////////////////////////////////////////////////////////////////
static const size_t SZ = 32;
static LFList T[SZ];
static size_t h(void* key)
// note: implemented via lfl, so we don't need to track
// active_data_structures or call smr_try_shutdown here.
static void validate(LFHash* hash)
{
return ((uintptr_t)key) % SZ;
assert2(hash->tbl);
assert2(is_pow2(hash->mask+1));
}
// return hash "chain" (i.e. linked list) that is assigned to <key>.
static LFList* chain(LFHash* hash, uintptr_t key)
{
validate(hash);
return &hash->tbl[key & hash->mask];
}
void* lfh_find(void* key)
// make ready a previously unused(!) hash object. table size will be
// <num_entries>; this cannot currently be expanded. if a negative error
// code (currently only ERR_NO_MEM) is returned, the hash can't be used.
int lfh_init(LFHash* hash, size_t num_entries)
{
LFList* list = &T[h(key)];
return lfl_find(list, key);
hash->tbl = 0;
hash->mask = ~0;
if(!is_pow2((long)num_entries))
{
debug_warn("lfh_init: size must be power of 2");
return ERR_INVALID_PARAM;
}
void* lfh_insert(void* key, size_t additional_bytes, int* was_inserted)
hash->tbl = (LFList*)malloc(sizeof(LFList) * num_entries);
if(!hash->tbl)
return ERR_NO_MEM;
hash->mask = (uint)num_entries-1;
for(int i = 0; i < (int)num_entries; i++)
{
LFList* list = &T[h(key)];
return lfl_insert(list, key, additional_bytes, was_inserted);
int err = lfl_init(&hash->tbl[i]);
if(err < 0)
{
// failed - free all and bail
for(int j = 0; j < i; j++)
lfl_free(&hash->tbl[j]);
return ERR_NO_MEM;
}
}
int lfh_erase(void* key)
return 0;
}
// call when hash is no longer needed; should no longer hold any references.
void lfh_free(LFHash* hash)
{
LFList* list = &T[h(key)];
return lfl_erase(list, key);
validate(hash);
// free all chains
for(size_t i = 0; i < hash->mask+1; i++)
lfl_free(&hash->tbl[i]);
free(hash->tbl);
hash->tbl = 0;
hash->mask = 0;
}
// return pointer to "user data" attached to <key>,
// or 0 if not found in the hash.
void* lfh_find(LFHash* hash, uintptr_t key)
{
return lfl_find(chain(hash,key), key);
}
// insert into hash if not already present. returns 0 if out of memory,
// otherwise a pointer to "user data" attached to <key>. the optional
// <was_inserted> return variable indicates whether <key> was added.
void* lfh_insert(LFHash* hash, uintptr_t key, size_t additional_bytes, int* was_inserted)
{
return lfl_insert(chain(hash,key), key, additional_bytes, was_inserted);
}
// remove from hash; return -1 if not found, or 0 on success.
int lfh_erase(LFHash* hash, uintptr_t key)
{
return lfl_erase(chain(hash,key), key);
}
@ -738,37 +802,54 @@ namespace test {
#if PERFORM_SELF_TEST
// make sure lock-free list works at all; doesn't test thread-safety.
// make sure the data structures work at all; doesn't test thread-safety.
static void basic_single_threaded_test()
{
LFList list;
lfl_init(&list);
int err;
void* user_data;
const uint ENTRIES = 20;
const uint ENTRIES = 50;
// should be more than max # retired nodes to test release..() code
void* key = (void*)0x1000;
uintptr_t key = 0x1000;
int sig = 10;
LFList list;
err = lfl_init(&list);
assert2(err == 0);
LFHash hash;
err = lfh_init(&hash, 8);
assert2(err == 0);
// add some entries; store "signatures" (ascending int values)
for(uint i = 0; i < ENTRIES; i++)
{
int was_inserted;
void* user_data = lfl_insert(&list, (u8*)key+i, sizeof(int), &was_inserted);
assert2(user_data != 0 && was_inserted);
user_data = lfl_insert(&list, key+i, sizeof(int), &was_inserted);
assert2(user_data != 0 && was_inserted);
*(int*)user_data = sig+i;
user_data = lfh_insert(&hash, key+i, sizeof(int), &was_inserted);
assert2(user_data != 0 && was_inserted);
*(int*)user_data = sig+i;
}
// make sure all "signatures" are present in list
for(uint i = 0; i < ENTRIES; i++)
{
void* user_data = lfl_find(&list, (u8*)key+i);
user_data = lfl_find(&list, key+i);
assert2(user_data != 0);
assert2(*(int*)user_data == sig+i);
user_data = lfh_find(&hash, key+i);
assert2(user_data != 0);
assert2(*(int*)user_data == sig+i);
}
lfl_free(&list);
lfh_free(&hash);
}
@ -781,6 +862,7 @@ static bool is_complete;
static intptr_t num_active_threads;
static LFList list;
static LFHash hash;
typedef std::set<uintptr_t> KeySet;
typedef KeySet::const_iterator KeySetIt;
@ -809,10 +891,11 @@ static void* thread_func(void* arg)
while(!is_complete)
{
const uintptr_t key = rand_up_to(100);
const int action = rand_up_to(4);
const int sleep_duration_ms = rand_up_to(100);
void* user_data;
const int action = rand_up_to(4);
const uintptr_t key = rand_up_to(100);
const int sleep_duration_ms = rand_up_to(100);
debug_out("thread %d: %s\n", thread_number, action_strings[action]);
//
@ -828,7 +911,12 @@ static void* thread_func(void* arg)
{
case TA_FIND:
{
void* user_data = lfl_find(&list, (void*)key);
user_data = lfl_find(&list, key);
assert2(was_in_set == (user_data != 0));
if(user_data)
assert2(*(uintptr_t*)user_data == ~key);
user_data = lfh_find(&hash, key);
assert2(was_in_set == (user_data != 0));
if(user_data)
assert2(*(uintptr_t*)user_data == ~key);
@ -838,7 +926,13 @@ static void* thread_func(void* arg)
case TA_INSERT:
{
int was_inserted;
void* user_data = lfl_insert(&list, (void*)key, sizeof(uintptr_t), &was_inserted);
user_data = lfl_insert(&list, key, sizeof(uintptr_t), &was_inserted);
assert2(user_data != 0); // only triggers if out of memory
*(uintptr_t*)user_data = ~key; // checked above
assert2(was_in_set == !was_inserted);
user_data = lfh_insert(&hash, key, sizeof(uintptr_t), &was_inserted);
assert2(user_data != 0); // only triggers if out of memory
*(uintptr_t*)user_data = ~key; // checked above
assert2(was_in_set == !was_inserted);
@ -847,7 +941,12 @@ static void* thread_func(void* arg)
case TA_ERASE:
{
int err = lfl_erase(&list, (void*)key);
int err;
err = lfl_erase(&list, key);
assert2(was_in_set == (err == 0));
err = lfh_erase(&hash, key);
assert2(was_in_set == (err == 0));
}
break;
@ -873,14 +972,17 @@ static void multithreaded_torture_test()
{
int err;
// rand() determines TestActions; we need deterministic results.
// this test is randomized; we need deterministic results.
srand(1);
static const double TEST_LENGTH = 30.; // [seconds]
const double end_time = get_time() + TEST_LENGTH;
is_complete = false;
lfl_init(&list);
err = lfl_init(&list);
assert2(err == 0);
err = lfh_init(&hash, 128);
assert2(err == 0);
err = pthread_mutex_init(&mutex, 0);
assert2(err == 0);
@ -900,6 +1002,7 @@ static void multithreaded_torture_test()
usleep(5*1000);
lfl_free(&list);
lfh_free(&hash);
err = pthread_mutex_destroy(&mutex);
assert2(err == 0);
}

View File

@ -36,3 +36,34 @@ extern void* lfl_insert(LFList* list, void* key, size_t additional_bytes, int* w
// remove from list; return -1 if not found, or 0 on success.
extern int lfl_erase(LFList* list, void* key);
//
// lock-free hash table (chained, fixed size)
//
struct LFHash
{
LFList* tbl;
uint mask;
};
// make ready a previously unused(!) hash object. table size will be
// <num_entries>; this cannot currently be expanded. if a negative error
// code (currently only ERR_NO_MEM) is returned, the hash can't be used.
extern int lfh_init(LFHash* hash, size_t num_entries);
// call when hash is no longer needed; should no longer hold any references.
extern void lfh_free(LFHash* hash);
// return pointer to "user data" attached to <key>,
// or 0 if not found in the hash.
extern void* lfh_find(LFHash* hash, uintptr_t key);
// insert into hash if not already present. returns 0 if out of memory,
// otherwise a pointer to "user data" attached to <key>. the optional
// <was_inserted> return variable indicates whether <key> was added.
extern void* lfh_insert(LFHash* hash, uintptr_t key, size_t additional_bytes, int* was_inserted);
// remove from hash; return -1 if not found, or 0 on success.
extern int lfh_erase(LFHash* hash, uintptr_t key);