Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 38 additions & 35 deletions Hashtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ in the source distribution for its full text.
#include "Hashtable.h"

#include <assert.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -73,6 +74,11 @@ static bool Hashtable_isConsistent(const Hashtable* this) {
bool res = items == this->items;
if (!res)
Hashtable_dump(this);

assert(this->size > 0);
assert(this->size <= SIZE_MAX / sizeof(HashtableItem));
assert(this->size >= this->items);

return res;
}

Expand All @@ -88,26 +94,26 @@ size_t Hashtable_count(const Hashtable* this) {

#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,
static size_t nextPrime(size_t n) {
// Table of differences so that (2^m - primeDiffs[m]) is a prime.
// This is OEIS sequence https://oeis.org/A013603 except for
// entry 0 (2^0 = 1 as a non-prime special case).
static const uint8_t primeDiffs[] = {
0, 0, 1, 1, 3, 1, 3, 1, 5, 3, 3, 9, 3, 1, 3, 19,
#if SIZE_MAX > UINT16_MAX
131071, 262139, 524287, 1048573,
2097143, 4194301, 8388593, 16777213, 33554393,
67108859, 134217689, 268435399, 536870909, 1073741789,
2147483647, 4294967291,
15, 1, 5, 1, 3, 9, 3, 15, 3, 39, 5, 39, 57, 3, 35, 1,
# if SIZE_MAX > UINT32_MAX
5, 9, 41, 31, 5, 25, 45, 7, 87, 21, 11, 57, 17, 55, 21, 115,
59, 81, 27, 129, 47, 111, 33, 55, 5, 13, 27, 55, 93, 1, 57, 25,
# endif
#endif
#if SIZE_MAX > UINT32_MAX
8589934583, 17179869143, 34359738337, 68719476731, 137438953447,
#endif
};
};

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); i++) {
if (n <= OEISprimes[i]) {
return OEISprimes[i];
assert(sizeof(n) * CHAR_BIT <= ARRAYSIZE(primeDiffs));
for (uint8_t shift = 3; shift < sizeof(n) * CHAR_BIT; shift++) {
size_t prime = ((size_t)1 << shift) - primeDiffs[shift];
if (n <= prime) {
return prime;
}
}

Expand Down Expand Up @@ -192,21 +198,18 @@ static void insert(Hashtable* this, ht_key_t key, void* value) {
}
}

void Hashtable_setSize(Hashtable* this, size_t size) {

static void Hashtable_setSize(Hashtable* this, size_t size) {
assert(Hashtable_isConsistent(this));
assert(size >= this->items);

if (size <= this->items)
return;

size_t newSize = nextPrime(size);
if (newSize == this->size)
size = nextPrime(size);
if (size == this->size)
return;

HashtableItem* oldBuckets = this->buckets;
size_t oldSize = this->size;

this->size = newSize;
this->size = size;
this->buckets = (HashtableItem*) xCalloc(this->size, sizeof(HashtableItem));
this->items = 0;

Expand All @@ -224,24 +227,20 @@ void Hashtable_setSize(Hashtable* this, size_t size) {
}

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");
if (sizeof(HashtableItem) < 7 && SIZE_MAX / 7 < this->size)
CRT_fatalError("Hashtable: size overflow");

Hashtable_setSize(this, 2 * this->size);
}
if (this->items >= this->size * 7 / 10)
Hashtable_setSize(this, this->size + 1);
Comment on lines +237 to +238
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICS the Hashtable_setSize should be called in either path to allocate new entries as needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean? This part of code addresses expanding the buffer. The buckets buffer is allocated stating at Hashtable_new.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but once the items get near the size because it can't allocate any more buffer space, you could at some point reach items == size, and thus the next insert will fail due to no more space allocated.

Instead when nearing the maximum capacity we should fall of to a more linear allocation regime …

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BenBE When it "can't allocate any more buffer space" htop will exit, because of the xCalloc call.

The case where items == size can only happen on small sizes such as 2 or 3 (the minimum size is 7 now, so the sizes of 2 and 3 are theoretical situations), but even when that happens, the next Hashtable_put call will always grow the buffer. Thus there's no problem here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was more thinking for very large allocations. Will have to take a closer look after New Year's …

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any harm in making the call to Hashtable_setSize unconditional? The above bounds check should be part of that function already.

Copy link
Contributor Author

@Explorer09 Explorer09 Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking how that would affect the shrinking of the buffer, with respect to this code:

https://github.com/Explorer09/htop-1/blob/3dc65f62da56befae7ed7dcb6e66ca5bea856710/Hashtable.c#L292

I personally like the idea, by centralizing the conditionals that readjust the buffer size, we can save some sanity checks in the Hashtable_setSize function.

Copy link
Contributor Author

@Explorer09 Explorer09 Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: It seems that there's a side effect if I try to merge the conditionals of expanding and shrinking the buffer in Hashtable_setSize, thus I have to give up on the idea.

When creating a Hashtable through Hashtable_new, it is allowed to specify a larger size for initial allocation. During the initial population of the items, this avoids unnecessary expansion or relocation of the buffer. If I move the shrinking condition to Hashtable_setSize, then the buffer will shrink automatically when adding an element to it. This would remove the benefits of initialing a Hashtable with larger size.

Copy link
Member

@BenBE BenBE Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we arrived at this issue before … IIRC.

Maybe inhibit shrinking while we try to insert items …

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe inhibit shrinking while we try to insert items …

Inhibit shrinking means a flag argument in Hashtable_setSize. It seems like we cannot have less than 2 arguments for the setSize function. If we cannot reduce the number of arguments for it, then I'd like to keep the current function prototype, and use the size argument to determine whether the buffer should grow or shrink.


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) {
Expand Down Expand Up @@ -293,8 +292,12 @@ void* Hashtable_remove(Hashtable* this, ht_key_t key) {
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 */
if (sizeof(HashtableItem) < 3 && SIZE_MAX / 3 < this->size)
CRT_fatalError("Hashtable: size overflow");

if (this->items < this->size / 8) {
Hashtable_setSize(this, this->size * 3 / 8); /* account for nextPrime rounding up */
}

return res;
}
Expand Down
2 changes: 0 additions & 2 deletions Hashtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ void Hashtable_delete(Hashtable* this);

void Hashtable_clear(Hashtable* this);

void Hashtable_setSize(Hashtable* this, size_t size);

void Hashtable_put(Hashtable* this, ht_key_t key, void* value);

void* Hashtable_remove(Hashtable* this, ht_key_t key);
Expand Down
Loading