[WIL]๐PINTOS_KAIST : Project 3. VIRTUAL MEMORY (1) : HASH TABLE ๐
๐ซ ํ์ฌ ๊ธ์ ์ ํํ์ง ์์ ์ ๋ณด๊ฐ ์์ ์ ์์ต๋๋ค. ์ธ์ ๋ ์ง ํ๋ฆฐ ๋ถ๋ถ์ ๋ํด ๋๊ธ์ ๋ฌ์์ฃผ์ธ์! ๐ซ
0. Hash Table์ด๋ ๋ญ๊ฐ์?
Hash Table์ด์ ์ Hashing์ ๋ํด ์ดํดํ๋ฉด Hash Table์ ๋ํ ์ดํด๊ฐ ๋น ๋ฅด๋ค.
(1) Hashing์ด๋?
'์ฝ๋ผ', 'ํํ', '์ฌ์ด๋ค'๋ฅผ ์์์ ์๋ฃ๊ตฌ์กฐ์ ์ ์ฅํ๋ค๊ณ ํ์. ์์๊ฐ 3๊ฐ์ผ๋๋ ์ฐพ๊ธฐ๊ฐ ๋น ๋ฅด์ง๋ง ์ธ์์ ์๋ ๋ชจ๋ ์๋ฃ์๋ฅผ ์ ์ฅํ๋ค๊ณ ํ ๋, ํด๋น ์๋ฃ๋ฅผ ์ ์ฅํ๊ณ ์ํ๋ ์๋ฃ๋ฅผ ์๋ฃ๊ตฌ์กฐ์์ ๊ฒ์ํ๋ ์๊ฐ์ ๋นํจ์จ์ ์ผ ๊ฒ์ด๋ค. ๊ทธ๋์ hash funtion์ ํตํด ์ ์ฅํ๊ณ ์ ํ๋ ๊ฐ์ ์์์ intํ(์ซ์)๋ก ๋ณํํ์ฌ ์ ์ฅํ๋ค. ์ฆ, ๋ฌธ์๋ฅผ ๊ฐ์ ธ์ ์ซ์๋ก ๋ณํํ๋ ๊ณผ์ ์ ํด์ฑ(Hashing)์ด๋ผ๊ณ ํ๋ค.๊ทธ๋ฆฌ๊ณ ๊ธ์๋ฅผ ํน์ ์ซ์๋ก ๋ณํํ๋๋ฐ ์ฌ์ฉํ๋ ์ฝ๋๋ฅผ ํด์ฌ ํจ์(hash funtion)์ด๋ค.
'์ฝ๋ผ' -> 123
'ํํ' -> 160
'์ฌ์ด๋ค' -> 170
(2) Hash Table์ด๋?
๊ทธ๋ผ Hash Table์? ์์ผ๋ก ์ด๋ฃจ์ด์ง ๊ฐ(ex: 'ํค': 168, '๋์ด': 20 )๋ค์ ๋ฆฌ์คํธ์ด๋ค. ์ฒซ๋ฒ์งธ ํญ๋ชฉ์ ํค(key)๋ผ๊ณ ๋ถ๋ฅด๊ณ , ๋ ๋ฒ์งธ ํญ๋ชฉ์ ๊ฐ์ด๋ผ๊ณ ๋ถ๋ฅธ๋ค. keys๊ฐ๋ค์ hash function์ ํตํด hashing์ด ์งํ๋๊ณ , ์งํ๋ ๊ฐ๋ค์ buckets์ด๋ผ๋ ๊ณณ์ ์ ์ฅํ๋ค. Hash Table์ด ๊ฒ์์๋๊ฐ๋น ๋ฅธ์ด์ ๋ ํ๋ํ๋ ์ฐพ์ง ์๊ณ hashing์ด ์งํ๋ keys๊ฐ๋ค๋ก ์ฐพ๊ธฐ ๋๋ฌธ์ O(1)์ด ์์๋๋ค.
(3) ์ด์ฉ ๊ทธ๋ผ Hash Table์ ์๋ฒฝํ ์๋ฃ๊ตฌ์กฐ์ธ๊ฐ์?
Hash Table์ ์ข์ ์๋ฃ๊ตฌ์กฐ์ด์ง๋ง, ์ธ ์์ธ์ ๋ฐ๋ผ์ ํจ์จ์ฑ์ด ์ ํด์ง๋ค.
- ํด์ ํ ์ด๋ธ์ ์ผ๋ง๋ ๋ง์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋๊ฐ
- ํด์ ํ ์ด๋ธ์์ ์ผ๋ง๋ ๋ง์ ์ ์ ์ธ ์ ์๋๊ฐ
- ์ด๋ค ํด์ ํจ์๋ฅผ ์ฌ์ฉํ๋๊ฐ.
์ ์ ์ ์ ๋ง์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ฉด ์ถฉ๋์ด ๋ง์ ํ ๊ณ , ํด์ ํ ์ด๋ธ์ ํจ์จ์ฑ์ ๋จ์ด์ง ๊ฒ์ด๋ค. ๋ง์ง๋ง ํด์ ํจ์๊ฐ ํจ์จ์ฑ์ ์ข์ฐ ํ๋ ์ด์ ๋ ํด์ ํจ์๋ฅผ ํตํด ํด์ฑ์ด ์งํ๋ ํน์ key๊ฐ๋ค์ด ํน์ ์ซ์์๋ง ๋ชฐ๋ ค ์ถฉ๋์ด ์ผ์ด๋ ๊ฒฝ์ฐ์ด๋ค. ์ข์ ํด์ ํจ์๋ ์ฌ์ฉ ๊ฐ๋ฅํ ๋ชจ๋ (bucket์ )์ ์ ๋ฐ์ดํฐ๋ฅผ ๋ถ์ฐ์ํค๋ ํจ์์ด๋ค. ๋ฐ์ดํฐ๋ฅผ ๋๊ฒ ํผํธ๋ฆด ์๋ก ์ถฉ๋์ด ์ ๊ธฐ ๋๋ฌธ์ด๋ค.
์ถฉ๋ ํ์๋ฅผ ์ค์ด๋ค ์๋ก ํด์ ํ ์ด๋ธ์ ํจ์จ์ฑ์ ๋๋ค. ์ด๋ก ์ ์ถฉ๋์ ํผํ๋ ์ต์ ์ ๋ฐฉ๋ฒ์ ํด์ ํ ์ด๋ธ์ ๋ง์ ์ ์ฅ๊ณต๊ฐ์ ํ๋ณดํ๋ ๊ฒ์ด๋ค. ํ์ง๋ง 10๊ฐ ์ ์ฅํ๊ณ ์ถ๋ค๊ณ ํด์ ํ ์ด๋ธ์ ์ ์ด 100000๊ฐ๋ฉด ์ถฉ๋์ด ์ผ์ด๋ ๊ฐ๋ฅ์ฑ์ ์ ์ง๋ง ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋๋ฌด ๋ง์ด ์ฌ์ฉํ๊ฒ ๋์ด ๊ณต๊ฐ๋ณต์ก๋๊ฐ ๋์ด๋๊ฒ๋๋ค. ํด์ ํ ์ด๋ธ์ ๋ฐ๋์ ์ถฉ๋ ์กฐ์ ์ ์ํํด์ผํ๋ฉฐ, ์ข์ ํด์ํ ์ด๋ธ์ ๋ง์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ญ๋นํ์ง ์์ผ๋ฉฐ ๊ท ํ์ ์ ์งํ๋ฉฐ ์ถฉ๋์ ํผํ๋ ๊ฒ์ด๋ค.
1. PINTOS์์์ Hash Table
pintos์์ ํด์ ํ ์ด๋ธ์ hash ๊ตฌ์กฐ์ฒด๋ก ํํ๋์ด ์๋ค. hash ๊ตฌ์กฐ์ฒด์ ์ค์ ์ ์ธ ๋ฉค๋ฒ๋ ์จ๊ฒจ์ ธ ์๋ค. ์ฆ ํด์ ํ ์ด๋ธ์ ์ฌ์ฉํ๋ ์ฝ๋๋ ์ง์ ์ ์ผ๋ก ์ ๊ทผํ ์ ์๋๋ก ์จ๊ฒจ์ ธ ์๋ค. ํด์ ํ ์ด๋ธ์ struct hash_elem ํ์ ์ ์์๋ก ์ฐ์ฐํ๊ฒ ๋๋ค.
(1) struct hash
pintos์์ bucket_cnt = 4 ๋ก ์ด๊ธฐํ ๋์ด ์๊ณ , ๋ฆฌ์คํธํ buckets๊ฐ ์กด์ฌํ๋ค. Hashing function์ ํตํด hashing๋ ๊ฐ๋ค์ด list buckets์ ๋ด๊ธด๋ค.
/* Hash table. */
struct hash {
size_t elem_cnt; /* Number of elements in table. */
size_t bucket_cnt; /* Number of buckets, a power of 2. */
struct list *buckets; /* Array of `bucket_cnt' lists. */
hash_hash_func *hash; /* Hash function. */
hash_less_func *less; /* Comparison function. */
void *aux; /* Auxiliary data for `hash' and `less'. */
};
(2) hash_hash_func
hash_hash_func์ ์ด๋ป๊ฒ ๋ค์ด์ฌ ์ง ๋ชจ๋ฅด๋ ๊ฐ hash_elem์ ๊ฐ์ ๋๋นํ ํจ์์ด๋ค. hash_elem์ด ๋ค์ํ ์๋ฃํ์ผ๋ก ๋ค์ด์ฌ ์ ์์ผ๋, ํด๋น ์๋ฃํ์ ๋ง์ถฐ hash_bytes, hash_string, hash_int๋ฅผ ์๋ง๊ฒ ์ฌ์ฉํ๋ฉด ๋๋ค. ์ด ๊ฐ๊ฐ์ ํจ์๊ฐ hash_funtion์ด๋ค. ๊ฐ ํจ์๋ค์ Fowler-Noll-Vo์ ๋น ์ํธ ํด์ํจ์๋ฅผ ์ฌ์ฉํ๊ณ ์์ผ๋ฉฐ, define์ผ๋ก ์ ์๋ ์์๋ค์ ๊ณฑํ๊ณ XOR ์ฐ์ฐ์ ํตํด Hash collision์ ๋ง๋๋ค.
/* include/lib/kernel/hash.h */
typedef uint64_t hash_hash_func (const struct hash_elem *e, void *aux);
/* include/lib/kernel/hash.h */
uint64_t hash_bytes (const void *, size_t);
uint64_t hash_string (const char *);
uint64_t hash_int (int);
1) hash_byte
/* Fowler-Noll-Vo hash constants, for 32-bit word sizes. */
#define FNV_64_PRIME 0x00000100000001B3UL
#define FNV_64_BASIS 0xcbf29ce484222325UL
/* Returns a hash of the SIZE bytes in BUF. */
uint64_t
hash_bytes (const void *buf_, size_t size) {
/* Fowler-Noll-Vo 32-bit hash, for bytes. */
const unsigned char *buf = buf_;
uint64_t hash;
ASSERT (buf != NULL);
hash = FNV_64_BASIS;
while (size-- > 0)
hash = (hash * FNV_64_PRIME) ^ *buf++;
return hash;
}
2) hash_string
/* Returns a hash of string S. */
uint64_t
hash_string (const char *s_) {
const unsigned char *s = (const unsigned char *) s_;
uint64_t hash;
ASSERT (s != NULL);
hash = FNV_64_BASIS;
while (*s != '\0')
hash = (hash * FNV_64_PRIME) ^ *s++;
return hash;
}
3) hash_int
uint64_t
hash_int (int i) {
return hash_bytes (&i, sizeof i);
}
(3) hash_init
/* Initializes hash table H to compute hash values using HASH and
compare hash elements using LESS, given auxiliary data AUX. */
/* hash funtion๋ค์ ์ฌ์ฉํ์ฌ hash table์ ์ด๊ธฐํํ๋ค. */
bool
hash_init (struct hash *h,
hash_hash_func *hash, hash_less_func *less, void *aux) {
h->elem_cnt = 0;
h->bucket_cnt = 4;
h->buckets = malloc (sizeof *h->buckets * h->bucket_cnt);
h->hash = hash;
h->less = less;
h->aux = aux;
if (h->buckets != NULL) {
hash_clear (h, NULL);
return true;
} else
return false;
}
(4) hasy_entry(elem, type, member)
hash_elem ๊ตฌ์กฐ์ฒด์ ํฌ์ธํฐ์ธ elem ๊ตฌ์กฐ์ฒด๋ฅผ ํฌํจํ ๊ตฌ์กฐ์ฒด์ ํฌ์ธํฐ๋ฅผ ๋ฐํํ๋ ํจ์์ด๋ค.
#define hash_entry (elem, type, member) { /* Omit details */ }
(5) rehash
๋ฒํท๋น ์์๊ฐ ์ ๋ฆฌ์ง ์๋๋ก ๊ด๋ฆฌ
/* Element per bucket ratios. */
#define MIN_ELEMS_PER_BUCKET 1 /* Elems/bucket < 1: reduce # of buckets. */
#define BEST_ELEMS_PER_BUCKET 2 /* Ideal elems/bucket. */
#define MAX_ELEMS_PER_BUCKET 4 /* Elems/bucket > 4: increase # of buckets. */
/* Changes the number of buckets in hash table H to match the
ideal. This function can fail because of an out-of-memory
condition, but that'll just make hash accesses less efficient;
we can still continue. */
static void
rehash (struct hash *h) {
size_t old_bucket_cnt, new_bucket_cnt;
struct list *new_buckets, *old_buckets;
size_t i;
ASSERT (h != NULL);
/* Save old bucket info for later use. */
old_buckets = h->buckets;
old_bucket_cnt = h->bucket_cnt;
/* Calculate the number of buckets to use now.
We want one bucket for about every BEST_ELEMS_PER_BUCKET.
We must have at least four buckets, and the number of
buckets must be a power of 2. */
new_bucket_cnt = h->elem_cnt / BEST_ELEMS_PER_BUCKET;
if (new_bucket_cnt < 4)
new_bucket_cnt = 4;
while (!is_power_of_2 (new_bucket_cnt))
new_bucket_cnt = turn_off_least_1bit (new_bucket_cnt);
/* Don't do anything if the bucket count wouldn't change. */
if (new_bucket_cnt == old_bucket_cnt)
return;
/* Allocate new buckets and initialize them as empty. */
new_buckets = malloc (sizeof *new_buckets * new_bucket_cnt);
if (new_buckets == NULL) {
/* Allocation failed. This means that use of the hash table will
be less efficient. However, it is still usable, so
there's no reason for it to be an error. */
return;
}
for (i = 0; i < new_bucket_cnt; i++)
list_init (&new_buckets[i]);
/* Install new bucket info. */
h->buckets = new_buckets;
h->bucket_cnt = new_bucket_cnt;
/* Move each old element into the appropriate new bucket. */
for (i = 0; i < old_bucket_cnt; i++) {
struct list *old_bucket;
struct list_elem *elem, *next;
old_bucket = &old_buckets[i];
for (elem = list_begin (old_bucket);
elem != list_end (old_bucket); elem = next) {
struct list *new_bucket
= find_bucket (h, list_elem_to_hash_elem (elem));
next = list_next (elem);
list_remove (elem);
list_push_front (new_bucket, elem);
}
}
free (old_buckets);
}