|
|
Chain型ハッシュ: 並行処理編 Part 2ソースはGitHubに移行しました。 FineGrained Hash Table (細粒度ハッシュテーブル)
細粒度ハッシュテーブルを実装する。実装自体は素直で非常に簡単だが、ベンチマーク結果は思ったほどではない。
使いどころを選ぶのだろうが、並行プログラミングはいろいろと難しい。 ソースはGitHubに移行しました。 なお、通常のアルゴリズム解説ならば必須の、ハッシュ関数に関する議論は一切行わない。 ここではハッシュ関数hashCode()を"keyをテーブルサイズで割った余り"で定義する。 static unsigned int hashCode(lkey_t key, const hashtable_t * ht) { return (key % ht->table_size); } データ構造データ構造node_t、list_t、hashtable_tを定義する。 hashtable_tはハッシュテーブルの本体、 list_tはハッシュテーブルの各要素に付属するList、node_tはListに連結するnodeのデータ構造である。 以降、共通のデータ型として"common.h"に"bool_t"、"lkey_t"、"val_t"を定義する。 "bool_t"は自明、 "lkey_t"はnodeのkey、"val_t"はnodeのvalを保持する。 common.h: typedef bool bool_t; typedef uint64_t lkey_t; typedef int64_t val_t; 次に、nodeとlist、およびhashtableのデータ構造を示す。 RefinableHash.h typedef struct _node_t { lkey_t key; /* key */ val_t value; /* 値 */ struct _node_t *next; /* 次のnodeへのポインタ*/ } node_t; typedef struct _list_t { node_t *head; pthread_mutex_t *mtx; } list_t; typedef struct _hashtable_t { unsigned long int setSize; /* 全node数 */ list_t *bucket; /* ハッシュテーブル本体 */ unsigned int table_size; /* ハッシュテーブルの大きさ(長さ) */ list_t *old_bucket; /* resizeする際に、一時的に利用するハッシュテーブル */ unsigned int old_table_size; /* old_bucketの大きさ = resize前のテーブルの大きさ */ } hashtable_t; node_tは(key,val)のペアと、次のnodeへのポインタを保持する。
list_tにはリストの先頭headが確保される。
hashtable_tには全ノード数を記録するsetSize、
ハッシュテーブル本体であるbucketとその大きさを記録するtable_size、
およびresize()でテーブルサイズを変更する際に、一時的にこれまでのテーブル(へのポインタ)を保持する
old_bucketとそのサイズを記録するold_table_sizeがある。 基本関数ハッシュテーブルの生成/* * bool_t *init_list(list_t *l) * * list_t *lにノードheadを生成する。 * * 成功すればtrue、失敗ならfalseを返す。 * */ static bool_t list_init(list_t * l) { if ((l->head = (node_t *) calloc(1, sizeof(node_t))) == NULL) { elog("calloc error"); return false; } l->head->next = NULL; /* mtxの設定はinit_bucket()で行う */ if ((l->mtx = (pthread_mutex_t *) calloc(1, sizeof(pthread_mutex_t))) == NULL) { elog("calloc error"); free (l->head); return false; } return true; } /* * bool_t init_bucket(hashtable_t * ht, const unsigned int init_table_size, const unsigned int new_table_size) * * ハッシュテーブルhtの各要素を初期化(リストの初期化)する。 * * 成功すればtrue、失敗すればfalseを返す。 */ static bool_t init_bucket(hashtable_t * ht, const unsigned int init_table_size, const unsigned int new_table_size) { unsigned int i; #ifdef PTHREAD_MUTEX_FAST_NP pthread_mutexattr_t mtx_attr; #endif if ((ht->bucket = (list_t *) calloc(new_table_size, sizeof(list_t))) == NULL) { elog("calloc error"); return false; } for (i = 0; i < new_table_size; i++) { if (list_init(&ht->bucket[i]) != true) return false; if (i < init_table_size) { ht->bucket[i].mtx = ht->old_bucket[i].mtx; } else { #ifdef PTHREAD_MUTEX_FAST_NP pthread_mutexattr_init(&mtx_attr); pthread_mutexattr_settype(&mtx_attr, PTHREAD_MUTEX_FAST_NP); pthread_mutex_init(ht->bucket[i].mtx, mtx_attr); pthread_mutexattr_destroy(&mtx_attr); #else pthread_mutex_init(ht->bucket[i].mtx, NULL); #endif } } return true; } static void free_bucket(list_t * bucket, const unsigned int table_size) { int i; for (i = 0; i < table_size; i++) { pthread_mutex_destroy(bucket[i].mtx); free(&(*bucket[i].head)); } free(bucket); } /* * hashtable_t *init_hashtable(const unsigned int table_size) * * 大きさtable_sizeのハッシュテーブルを生成する。 * * 成功すれば、そのポインタを返す。失敗すればNULLを返す。 * */ hashtable_t *init_hashtable(const unsigned int table_size) { hashtable_t *ht; if ((ht = (hashtable_t *) calloc(1, sizeof(hashtable_t))) == NULL) { elog("calloc error"); return NULL; } ht->table_size = table_size; ht->setSize = 0; if (init_bucket(ht, 0, table_size) != true) { free(ht); return NULL; } return ht; } void free_hashtable(hashtable_t * ht) { free_bucket(ht->bucket, ht->table_size); free(ht); } ノードの追加/* * bool_t add_node_op(list_t * l, node_t * newNode) * * ノードnewNodeをリストlに追加する。 * * 成功すればtrue、失敗ならfalseを返す。 * */ static bool_t add_node_op(list_t * l, node_t * newNode) { node_t *pred, *curr; bool_t ret = true; assert(l->head != NULL); pred = l->head; curr = pred->next; if (curr == NULL) { l->head->next = newNode; newNode->next = NULL; } else { while (curr != NULL && curr->key < newNode->key) { pred = curr; curr = curr->next; } if (curr == NULL || newNode->key != curr->key) { newNode->next = curr; pred->next = newNode; } else ret = false; } return ret; } /* * bool_t add_node(list_t * l, const lkey_t key, const val_t val) * * リストlにノード(key,val)を生成して追加する。 * * 成功するとtrue、失敗するとfalseを返す。 * */ static bool_t add_node(list_t * l, const lkey_t key, const val_t val) { node_t *newNode; if ((newNode = create_node(key, val)) == 0) return false; if (add_node_op(l, newNode) != true) { free_node(newNode); return false; } return true; } /* * bool_t add(hashtable_t * ht, const lkey_t key, const val_t val) * * ハッシュテーブルhtにnode(key,val)を追加する。 * * 成功すればtrue、失敗すればfalseを返す。 */ bool_t add(hashtable_t * ht, const lkey_t key, const val_t val) { unsigned int myBucket, table_size; bool_t ret = false; int retry = 2; do { table_size = ht->table_size; myBucket = hashCode(key, ht); /* T1: */ lock(ht->bucket[myBucket].mtx); /* T2: */ if (table_size == ht->table_size) /* T1とT2の間でresize()が実行された場合の対処 */ { if (add_node(&ht->bucket[myBucket], key, val) == true) { ht->setSize++; ret = true; unlock(ht->bucket[myBucket].mtx); break; } } unlock(ht->bucket[myBucket].mtx); } while (retry-- > 0); if (policy(ht)) { resize(ht); fprintf (stderr, "Resized\n"); } return ret; }関数add_node()内部で呼ぶnode生成関数create_node()を示す。 /* * node_t *create_node(const lkey_t key, const val_t val) * * (key, val)を持つnodeを生成する。 * * 成功すればnodeへのポインタを返す。失敗すればNULLを返す。 */ static node_t *create_node(const lkey_t key, const val_t val) { node_t *newNode; if ((newNode = (node_t *) calloc(1, sizeof(node_t))) == NULL) { elog("calloc error"); return NULL; } newNode->key = key; newNode->value = val; return newNode; } nodeの削除/* * bool_t delete_node(list_t * l, const lkey_t key, val_t * getval) * * リストlからノード(key,val)を削除する。 * * 成功するとgetvalにvalを代入してtrueを返す。失敗するとfalseを返す。 * */ static bool_t delete_node(list_t * l, const lkey_t key, val_t * getval) { node_t *pred, *curr; pred = l->head; curr = pred->next; if (curr == NULL) return false; while (curr != NULL && curr->key < key) { pred = curr; curr = curr->next; } if (key == curr->key && curr != NULL) { *getval = curr->value; pred->next = curr->next; free_node(curr); } else return false; return true; } /* * bool_t delete(hashtable_t * ht, const lkey_t key, val_t * getval) * * ハッシュテーブルhtからkeyを持つnodeを削除し、そのnodeのvalを*valに書き込む。 * keyが存在しない場合は失敗。 * * 成功すればtrue、失敗すればfalseを返す。 */ bool_t delete(hashtable_t * ht, const lkey_t key, val_t * getval) { unsigned int myBucket, table_size; bool_t ret = false; int retry = 2; do { table_size = ht->table_size; myBucket = hashCode(key, ht); /* T1: */ lock(ht->bucket[myBucket].mtx); /* T2: */ if (table_size == ht->table_size) /* T1とT2の間でresize()が実行された場合の対処 */ { if (delete_node(&ht->bucket[myBucket], key, getval) == true) { ht->setSize--; ret = true; unlock(ht->bucket[myBucket].mtx); break; } } unlock(ht->bucket[myBucket].mtx); } while (retry-- > 0); return ret; } ノードの検索/* * bool_t find_node(list_t * l, lkey_t key) * * リストlにkeyを持つノードがあるか否か調べる。 * * 成功すればtrue、失敗すればfalseを返す。 */ static bool_t find_node(list_t * l, lkey_t key) { node_t *pred, *curr; pred = l->head; curr = pred->next; if (curr == NULL) { return false; } else { while (curr != NULL && curr->key < key) { pred = curr; curr = curr->next; } if (curr == NULL || key != curr->key) return false; } return true; } /* * bool_t find(hashtable_t * ht, const lkey_t key) * * ハッシュテーブルhtからkeyを持つnodeを検索し、そのnodeのvalを*valに書き込む。 * keyが存在しない場合は失敗。 * * 成功すればtrue、失敗すればfalseを返す。 */ bool_t find(hashtable_t * ht, const lkey_t key) { unsigned int myBucket = hashCode(key, ht); bool_t ret; lock(ht->bucket[myBucket].mtx); ret = find_node(&ht->bucket[myBucket], key); unlock(ht->bucket[myBucket].mtx); return ret; } ハッシュテーブルの再構築(resize())static bool_t policy(hashtable_t * ht) { return ((int) (ht->setSize / ht->table_size) > 4 ? true : false); } static void resize(hashtable_t * ht) { list_t *l; node_t *pred, *curr; unsigned int i, myBucket, table_size; table_size = ht->table_size; for (i = 0; i < table_size; i++) lock(ht->bucket[i].mtx); if (table_size != ht->table_size) { for (i = 0; i < table_size; i++) unlock(ht->bucket[i].mtx); return; } ht->old_table_size = ht->table_size; ht->old_bucket = ht->bucket; if (init_bucket(ht, ht->table_size, ht->table_size * 2) == false) return; ht->table_size *= 2; for (i = 0; i < ht->old_table_size; i++) { l = &ht->old_bucket[i]; pred = l->head; curr = pred->next; while (curr != NULL) { pred->next = curr->next; /* next = curr->next; pred->next = next; */ myBucket = hashCode(curr->key, ht); add_node_op(&ht->bucket[myBucket], curr); curr = pred->next; } } for (i = 0; i < ht->old_table_size; i++) unlock(ht->bucket[i].mtx); free_bucket(ht->old_bucket, ht->old_table_size); } 実行ソースはGitHubに移行しました。
Last-modified: 2014-7-6
|