You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
330 lines
7.7 KiB
330 lines
7.7 KiB
/* |
|
htop - Hashtable.c |
|
(C) 2004-2011 Hisham H. Muhammad |
|
Released under the GNU GPLv2+, see the COPYING file |
|
in the source distribution for its full text. |
|
*/ |
|
|
|
#include "config.h" // IWYU pragma: keep |
|
|
|
#include "Hashtable.h" |
|
|
|
#include <assert.h> |
|
#include <stdint.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
|
|
#include "CRT.h" |
|
#include "Macros.h" |
|
#include "XUtils.h" |
|
|
|
#ifndef NDEBUG |
|
#include <stdio.h> |
|
#endif |
|
|
|
|
|
typedef struct HashtableItem_ { |
|
ht_key_t key; |
|
size_t probe; |
|
void* value; |
|
} HashtableItem; |
|
|
|
struct Hashtable_ { |
|
size_t size; |
|
HashtableItem* buckets; |
|
size_t items; |
|
bool owner; |
|
}; |
|
|
|
|
|
#ifndef NDEBUG |
|
|
|
static void Hashtable_dump(const Hashtable* this) { |
|
fprintf(stderr, "Hashtable %p: size=%zu items=%zu owner=%s\n", |
|
(const void*)this, |
|
this->size, |
|
this->items, |
|
this->owner ? "yes" : "no"); |
|
|
|
size_t items = 0; |
|
for (size_t i = 0; i < this->size; i++) { |
|
fprintf(stderr, " item %5zu: key = %5u probe = %2zu value = %p\n", |
|
i, |
|
this->buckets[i].key, |
|
this->buckets[i].probe, |
|
this->buckets[i].value); |
|
|
|
if (this->buckets[i].value) |
|
items++; |
|
} |
|
|
|
fprintf(stderr, "Hashtable %p: items=%zu counted=%zu\n", |
|
(const void*)this, |
|
this->items, |
|
items); |
|
} |
|
|
|
static bool Hashtable_isConsistent(const Hashtable* this) { |
|
size_t items = 0; |
|
for (size_t i = 0; i < this->size; i++) { |
|
if (this->buckets[i].value) |
|
items++; |
|
} |
|
bool res = items == this->items; |
|
if (!res) |
|
Hashtable_dump(this); |
|
return res; |
|
} |
|
|
|
size_t Hashtable_count(const Hashtable* this) { |
|
size_t items = 0; |
|
for (size_t i = 0; i < this->size; i++) { |
|
if (this->buckets[i].value) |
|
items++; |
|
} |
|
assert(items == this->items); |
|
return items; |
|
} |
|
|
|
#endif /* NDEBUG */ |
|
|
|
/* https://oeis.org/A014234 */ |
|
static const uint64_t OEISprimes[] = { |
|
7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, |
|
16381, 32749, 65521, 131071, 262139, 524287, 1048573, |
|
2097143, 4194301, 8388593, 16777213, 33554393, |
|
67108859, 134217689, 268435399, 536870909, 1073741789, |
|
2147483647, 4294967291, 8589934583, 17179869143, |
|
34359738337, 68719476731, 137438953447 |
|
}; |
|
|
|
static size_t nextPrime(size_t n) { |
|
/* on 32-bit make sure we do not return primes not fitting in size_t */ |
|
for (size_t i = 0; i < ARRAYSIZE(OEISprimes) && OEISprimes[i] < SIZE_MAX; i++) { |
|
if (n <= OEISprimes[i]) |
|
return OEISprimes[i]; |
|
} |
|
|
|
CRT_fatalError("Hashtable: no prime found"); |
|
} |
|
|
|
Hashtable* Hashtable_new(size_t size, bool owner) { |
|
Hashtable* this; |
|
|
|
this = xMalloc(sizeof(Hashtable)); |
|
this->items = 0; |
|
this->size = size ? nextPrime(size) : 13; |
|
this->buckets = (HashtableItem*) xCalloc(this->size, sizeof(HashtableItem)); |
|
this->owner = owner; |
|
|
|
assert(Hashtable_isConsistent(this)); |
|
return this; |
|
} |
|
|
|
void Hashtable_delete(Hashtable* this) { |
|
Hashtable_clear(this); |
|
|
|
free(this->buckets); |
|
free(this); |
|
} |
|
|
|
void Hashtable_clear(Hashtable* this) { |
|
assert(Hashtable_isConsistent(this)); |
|
|
|
if (this->owner) |
|
for (size_t i = 0; i < this->size; i++) |
|
free(this->buckets[i].value); |
|
|
|
memset(this->buckets, 0, this->size * sizeof(HashtableItem)); |
|
this->items = 0; |
|
|
|
assert(Hashtable_isConsistent(this)); |
|
} |
|
|
|
static void insert(Hashtable* this, ht_key_t key, void* value) { |
|
size_t index = key % this->size; |
|
size_t probe = 0; |
|
#ifndef NDEBUG |
|
size_t origIndex = index; |
|
#endif |
|
|
|
for (;;) { |
|
if (!this->buckets[index].value) { |
|
this->items++; |
|
this->buckets[index].key = key; |
|
this->buckets[index].probe = probe; |
|
this->buckets[index].value = value; |
|
return; |
|
} |
|
|
|
if (this->buckets[index].key == key) { |
|
if (this->owner && this->buckets[index].value != value) |
|
free(this->buckets[index].value); |
|
this->buckets[index].value = value; |
|
return; |
|
} |
|
|
|
/* Robin Hood swap */ |
|
if (probe > this->buckets[index].probe) { |
|
HashtableItem tmp = this->buckets[index]; |
|
|
|
this->buckets[index].key = key; |
|
this->buckets[index].probe = probe; |
|
this->buckets[index].value = value; |
|
|
|
key = tmp.key; |
|
probe = tmp.probe; |
|
value = tmp.value; |
|
} |
|
|
|
index = (index + 1) % this->size; |
|
probe++; |
|
|
|
assert(index != origIndex); |
|
} |
|
} |
|
|
|
void Hashtable_setSize(Hashtable* this, size_t size) { |
|
|
|
assert(Hashtable_isConsistent(this)); |
|
|
|
if (size <= this->items) |
|
return; |
|
|
|
size_t newSize = nextPrime(size); |
|
if (newSize == this->size) |
|
return; |
|
|
|
HashtableItem* oldBuckets = this->buckets; |
|
size_t oldSize = this->size; |
|
|
|
this->size = newSize; |
|
this->buckets = (HashtableItem*) xCalloc(this->size, sizeof(HashtableItem)); |
|
this->items = 0; |
|
|
|
/* rehash */ |
|
for (size_t i = 0; i < oldSize; i++) { |
|
if (!oldBuckets[i].value) |
|
continue; |
|
|
|
insert(this, oldBuckets[i].key, oldBuckets[i].value); |
|
} |
|
|
|
free(oldBuckets); |
|
|
|
assert(Hashtable_isConsistent(this)); |
|
} |
|
|
|
void Hashtable_put(Hashtable* this, ht_key_t key, void* value) { |
|
|
|
assert(Hashtable_isConsistent(this)); |
|
assert(this->size > 0); |
|
assert(value); |
|
|
|
/* grow on load-factor > 0.7 */ |
|
if (10 * this->items > 7 * this->size) { |
|
if (SIZE_MAX / 2 < this->size) |
|
CRT_fatalError("Hashtable: size overflow"); |
|
|
|
Hashtable_setSize(this, 2 * this->size); |
|
} |
|
|
|
insert(this, key, value); |
|
|
|
assert(Hashtable_isConsistent(this)); |
|
assert(Hashtable_get(this, key) != NULL); |
|
assert(this->size > this->items); |
|
} |
|
|
|
void* Hashtable_remove(Hashtable* this, ht_key_t key) { |
|
size_t index = key % this->size; |
|
size_t probe = 0; |
|
#ifndef NDEBUG |
|
size_t origIndex = index; |
|
#endif |
|
|
|
assert(Hashtable_isConsistent(this)); |
|
|
|
void* res = NULL; |
|
|
|
while (this->buckets[index].value) { |
|
if (this->buckets[index].key == key) { |
|
if (this->owner) { |
|
free(this->buckets[index].value); |
|
} else { |
|
res = this->buckets[index].value; |
|
} |
|
|
|
size_t next = (index + 1) % this->size; |
|
|
|
while (this->buckets[next].value && this->buckets[next].probe > 0) { |
|
this->buckets[index] = this->buckets[next]; |
|
this->buckets[index].probe -= 1; |
|
|
|
index = next; |
|
next = (index + 1) % this->size; |
|
} |
|
|
|
/* set empty after backward shifting */ |
|
this->buckets[index].value = NULL; |
|
this->items--; |
|
|
|
break; |
|
} |
|
|
|
if (this->buckets[index].probe < probe) |
|
break; |
|
|
|
index = (index + 1) % this->size; |
|
probe++; |
|
|
|
assert(index != origIndex); |
|
} |
|
|
|
assert(Hashtable_isConsistent(this)); |
|
assert(Hashtable_get(this, key) == NULL); |
|
|
|
/* shrink on load-factor < 0.125 */ |
|
if (8 * this->items < this->size) |
|
Hashtable_setSize(this, this->size / 3); /* account for nextPrime rounding up */ |
|
|
|
return res; |
|
} |
|
|
|
void* Hashtable_get(Hashtable* this, ht_key_t key) { |
|
size_t index = key % this->size; |
|
size_t probe = 0; |
|
void* res = NULL; |
|
#ifndef NDEBUG |
|
size_t origIndex = index; |
|
#endif |
|
|
|
assert(Hashtable_isConsistent(this)); |
|
|
|
while (this->buckets[index].value) { |
|
if (this->buckets[index].key == key) { |
|
res = this->buckets[index].value; |
|
break; |
|
} |
|
|
|
if (this->buckets[index].probe < probe) |
|
break; |
|
|
|
index = (index + 1) != this->size ? (index + 1) : 0; |
|
probe++; |
|
|
|
assert(index != origIndex); |
|
} |
|
|
|
return res; |
|
} |
|
|
|
void Hashtable_foreach(Hashtable* this, Hashtable_PairFunction f, void* userData) { |
|
assert(Hashtable_isConsistent(this)); |
|
for (size_t i = 0; i < this->size; i++) { |
|
HashtableItem* walk = &this->buckets[i]; |
|
if (walk->value) |
|
f(walk->key, walk->value, userData); |
|
} |
|
assert(Hashtable_isConsistent(this)); |
|
}
|
|
|