Skip to content

Commit 9af5090

Browse files
author
Julian LALU
committed
Update hashmap
1 parent 01d88af commit 9af5090

File tree

4 files changed

+156
-39
lines changed

4 files changed

+156
-39
lines changed

interface/core/containers/hashmap.h

Lines changed: 151 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,14 @@ namespace hud
3737
{
3838
};
3939

40+
template<typename slot_t>
41+
struct default_allocator
42+
: hud::heap_allocator
43+
{
44+
};
45+
4046
using control_type = u8;
47+
4148
static constexpr control_type control_empty = 0b10000000; // The slot is empty
4249
static constexpr control_type control_deleted = 0b11111110; // The slot is deleted
4350
static constexpr control_type control_sentinel = 0b11111111; // The slot is a sentinel, A sentinel is a special caracter that mark the end of the control for iteration
@@ -53,23 +60,56 @@ namespace hud
5360
using value_type = typename hud::pair<key_t, value_t>::second_type;
5461
};
5562

63+
struct group_portable_iterator
64+
{
65+
constexpr group_portable_iterator(u64 mask) noexcept
66+
: mask_(mask)
67+
{
68+
}
69+
70+
[[nodiscard]] constexpr explicit operator bool() const noexcept
71+
{
72+
return mask_ != 0;
73+
}
74+
75+
[[nodiscard]] constexpr u32 insertion_byte_offset() const noexcept
76+
{
77+
// Get number of trailing zero and divide it by 8 (>>3) to get the insert offset of the byte
78+
return hud::bits::trailing_zero(mask_) >> 3;
79+
}
80+
81+
u64 mask_;
82+
};
83+
5684
struct group_portable
5785
{
86+
using iterator_type = group_portable_iterator;
5887
static constexpr u64 slot_per_group_ = 8;
5988

6089
constexpr group_portable(const control_type *control) noexcept
6190
: value_(hud::memory::unaligned_load64(static_cast<const u8 *>(control)))
6291
{
6392
}
6493

65-
u64 match(u8 h2_hash)
94+
constexpr iterator_type match(u8 h2_hash) const noexcept
6695
{
67-
return hud::bits::has_zero_byte(hud::bits::has_value_byte(value_, h2_hash));
96+
return iterator_type {hud::bits::has_zero_byte(hud::bits::has_value_byte(value_, h2_hash))};
6897
}
6998

70-
u64 mask_of_empty_slot()
99+
constexpr iterator_type mask_of_empty_slot() const noexcept
71100
{
72-
return hud::bits::has_zero_byte(hud::bits::has_value_byte(value_, control_empty));
101+
return hud::bits::has_value_byte(value_, control_empty);
102+
}
103+
104+
[[nodiscard]] constexpr bool
105+
operator==(const group_portable &other) const noexcept
106+
{
107+
return value_ == other.value_;
108+
}
109+
110+
[[nodiscard]] constexpr bool operator!=(const group_portable &other) const noexcept
111+
{
112+
return value_ == other.value_;
73113
}
74114

75115
private:
@@ -86,37 +126,39 @@ namespace hud
86126
{
87127
hud::check(((mask + 1) & mask) == 0 && "not a mask");
88128
mask_ = mask;
89-
offset_ = h1_hash & mask_;
129+
byte_offset_ = h1_hash & mask_;
90130
}
91131

92-
u64 offset() const
132+
u64 byte_offset() const
93133
{
94-
return offset_;
134+
return byte_offset_;
95135
}
96136

97-
u64 offset(u64 i) const
137+
u64 byte_offset(u64 i) const
98138
{
99-
return (offset_ + i) & mask_;
139+
return (byte_offset_ + i) & mask_;
100140
}
101141

102142
void next_group()
103143
{
144+
// Next group index
104145
index_ += group::slot_per_group_;
105-
offset_ += index_;
106-
offset_ &= mask_;
146+
byte_offset_ += index_;
147+
byte_offset_ &= mask_;
107148
}
108149

109150
// 0-based probe index, a multiple of `Width`.
110-
u64 index_() const
151+
u64 index() const
111152
{
112153
return index_;
113154
}
114155

115156
private:
116157
/** Mask used to iterate over. */
117158
u64 mask_;
118-
/** */
119-
u64 offset_;
159+
/** Byte offset */
160+
u64 byte_offset_;
161+
120162
u64 index_ {0};
121163
};
122164

@@ -172,7 +214,7 @@ namespace hud
172214

173215
private:
174216
// The current control we are iterating. For the moment set to the global init control
175-
control_type *control_ {const_cast<control_type *>(&EMPTY_GROUP[32])};
217+
control_type *control_;
176218
// The current slot we are iterating. Keep uninitialized.
177219
value_type *slot_;
178220
};
@@ -207,22 +249,71 @@ namespace hud
207249
using value_type = typename slot_type::value_type;
208250
/** Type of the hash function. */
209251
using hasher_type = hasher_t;
210-
/** Type of the iterator */
211-
using iterator = hud::details::hashmap::iterator<slot_type>;
252+
/** Type of the iterator/ */
253+
using iterator_type = hud::details::hashmap::iterator<slot_type>;
254+
/** Type of the allocator. */
255+
using allocator_type = allocator_t;
256+
/** The type of allocation done by the allocator. */
257+
using memory_allocation_type = typename allocator_type::template memory_allocation_type<slot_type>;
212258

213259
public:
214260
constexpr hashmap_impl() noexcept
215-
: control_(const_cast<control_type *>(&EMPTY_GROUP[32])) // Point to the sentinel in the empty group, const_cast is ok, EMPTY_GROUP is used only when the table is empty
261+
: control_(const_cast<control_type *>(&EMPTY_GROUP[16])) // Point to the sentinel in the empty group, const_cast is ok, EMPTY_GROUP is used only when the table is empty
216262
{
217263
}
218264

219-
constexpr hud::optional<iterator> insert(const key_type &key, const value_type &value) noexcept
265+
constexpr hud::optional<iterator_type> insert(const key_type &key, const value_type &value) noexcept
220266
{
221267
return find_or_prepare_insert(key);
222268
}
223269

270+
constexpr void resize(usize new_max_slot_count) noexcept
271+
{
272+
max_slot_count_ = new_max_slot_count;
273+
274+
// Perform allocation of the buffer
275+
// |-------------------|----------------/
276+
// | control metadata | Aligned slots /
277+
// |-------------------|----------------/
278+
constexpr const usize num_cloned_bytes = group::slot_per_group_ - 1;
279+
const usize control_size = max_count() + 1 + num_cloned_bytes;
280+
// Compute the control_size that is a multiple of the slot_type alignement
281+
// We are shure to allocate enough memory for control_size and we ensure to be align on alignof(slot_type) after this allocation size
282+
// control_size is multiple of sizeof(slot_type)
283+
const uptr aligned_control_size = hud::memory::align_address(control_size, sizeof(slot_type));
284+
const usize aligned_allocation_size = aligned_control_size + new_max_slot_count * sizeof(slot_type);
285+
286+
// Allocate the buffer that will contains control metadata and aligned slots
287+
memory_allocation_type allocation = allocator_.template allocate<slot_type>(aligned_allocation_size);
288+
289+
// Save control metadata and slot pointers
290+
control_ = hud::bit_cast<control_type *>(allocation.data());
291+
slot_type *slot_ptr = hud::bit_cast<slot_type *>(control_ + aligned_control_size);
292+
hud::check(hud::memory::is_pointer_aligned(slot_ptr, alignof(slot_type)));
293+
294+
// Update number of slot we should put into the table before a resizing rehash
295+
growth_left_ = max_growth_left() - count();
296+
297+
// Reset control metadata
298+
hud::memory::set(control_, control_empty, control_size);
299+
control_[max_slot_count_] = control_sentinel;
300+
301+
//
302+
if (old)
303+
}
304+
305+
[[nodiscard]] constexpr usize max_count() const noexcept
306+
{
307+
return max_slot_count_;
308+
}
309+
310+
[[nodiscard]] constexpr usize count() const noexcept
311+
{
312+
return count_;
313+
}
314+
224315
private:
225-
constexpr hud::optional<iterator> find_or_prepare_insert(const key_type &key)
316+
constexpr hud::optional<iterator_type> find_or_prepare_insert(const key_type &key)
226317
{
227318
// TODO: prefetch control bloc
228319
// Hash the key
@@ -231,44 +322,68 @@ namespace hud
231322
const control_type *control = control_;
232323
while (true)
233324
{
234-
group g {control + probe.offset()};
325+
group g {control + probe.byte_offset()};
235326
// for (u32 i : g.match(H2(hash)))
236327
// {
237328
// }
238-
u64 mask_of_empty_slot = g.mask_of_empty_slot();
239-
if (mask_of_empty_slot != 0) [[likely]]
329+
group::iterator_type mask_of_empty_slot = g.mask_of_empty_slot();
330+
if (mask_of_empty_slot) [[likely]]
240331
{
241-
u64 insert_offset = hud::bits::trailing_zero(mask_of_empty_slot);
242-
u64 target = probe.offset(insert_offset);
332+
u64 insertion_byte_offset = mask_of_empty_slot.insertion_byte_offset();
333+
u64 target = probe.byte_offset(insertion_byte_offset);
243334
prepare_insert(hash, target, probe.index());
244335
}
245336
}
246337
return hud::nullopt;
247338
}
248339

249-
u64 prepare_insert(u64 hash, u64 target, )
340+
constexpr u64 prepare_insert(u64 hash, u64 target, u64 probe_lenght)
250341
{
251342
if (growth_left_ == 0)
252343
{
253-
const usize old_capacity = max_slot_count_;
344+
// const usize old_capacity = max_count();
254345
rehash_and_grow_if_necessary();
255346
}
347+
return 0;
256348
}
257349

258-
void rehash_and_grow_if_necessary()
350+
constexpr void rehash_and_grow_if_necessary()
259351
{
260-
const usize cap = max_slot_count_;
352+
const usize capacity = max_count();
261353

262354
// Rehash in place if the current size is <= 25/32 of capacity.
263-
if (cap > group::slot_per_group_ && count_ * uint64_t {32} <= cap * uint64_t {25})
355+
if (capacity > group::slot_per_group_ && count_ * uint64_t {32} <= capacity * uint64_t {25})
264356
{
265357
}
266358
else
267359
{
268-
resize(NextCapacity(cap));
360+
resize(next_capacity(capacity));
269361
}
270362
}
271363

364+
[[nodiscard]] constexpr usize next_capacity(usize capacity) const noexcept
365+
{
366+
return capacity * 2 + 1;
367+
}
368+
369+
/**
370+
* Compute the maximum number of slots we should put into the table before a resizing rehash.
371+
* Subtract the returned value with the number of slots `count()` to obtains the number of slots we can currently before a resizing rehash.
372+
*/
373+
[[nodiscard]] constexpr usize max_growth_left() noexcept
374+
{
375+
// Current load factor is 7/8, this means we can resize when 7/8 slots are occupied
376+
// A special case appear when group are 8 bytes width and `max_count()` is 7 : 7−7/8=7, in this case, we return 6
377+
//
378+
// | Capacity (capacity) | capacity - capacity / 8 | result (max_growth_left) |
379+
// | 7 (special case) | - | 6 |
380+
// | 8 | 8−8/8 | 7 |
381+
// | 16 | 16−16/8 | 14 |
382+
// | 32 | 32−32/8 | 28 |
383+
// | 64 | 64−64/8 | 56 |
384+
return group::slot_per_group_ == 8 && max_count() == 7 ? 6 : max_count() - max_count() / 8;
385+
}
386+
272387
private:
273388
/** The max slot count of the hashmap. */
274389
usize max_slot_count_ {0};
@@ -282,8 +397,11 @@ namespace hud
282397
/** Pointer to the slot segment. */
283398
value_type *slots_;
284399

285-
/** growth left elements. */
400+
/** Number of slot we should put into the table before a resizing rehash. */
286401
usize growth_left_ {0};
402+
403+
/** The allocator. */
404+
allocator_type allocator_;
287405
};
288406

289407
} // namespace details::hashmap
@@ -293,10 +411,9 @@ namespace hud
293411
typename value_t,
294412
typename hasher_t = details::hashmap::default_hasher<key_t>,
295413
typename key_equal_t = details::hashmap::default_equal<key_t>,
296-
typename allocator_t = heap_allocator>
414+
typename allocator_t = details::hashmap::default_allocator<details::hashmap::slot<key_t, value_t>>>
297415
class hashmap
298416
: details::hashmap::hashmap_impl<details::hashmap::slot<key_t, value_t>, hasher_t, key_equal_t, allocator_t>
299-
300417
{
301418
private:
302419
using super = details::hashmap::hashmap_impl<details::hashmap::slot<key_t, value_t>, hasher_t, key_equal_t, allocator_t>;

interface/core/containers/hashset.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ namespace hud
2020
typename value_t,
2121
typename hasher_t = details::hashmap::default_hasher<value_t>,
2222
typename key_equal_t = details::hashmap::default_equal<value_t>,
23-
typename allocator_t = heap_allocator>
23+
typename allocator_t = details::hashmap::default_allocator<value_t>>
2424
class hashset
2525
: details::hashmap::hashmap_impl<details::hashset::slot<value_t>, hasher_t, key_equal_t, allocator_t>
2626
{

interface/core/os_common/bits.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ namespace hud::os::common
103103

104104
[[nodiscard]] static constexpr u64 trailing_zero(u64 value) noexcept
105105
{
106-
u64 c = 64; // c sera le nombre de bits zéro à droite
107-
value &= -i64(value); // Mask les bits de poids faible avec l'opération de complément à 2
106+
u64 c = 64; // c will be the number of zero bits on the right
107+
value &= -i64(value);
108108
if (value)
109109
c--;
110110
if (value & 0x00000000FFFFFFFFu)

interface/core/os_linux/uuid.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ namespace hud::os::linux
2929

3030
// 1. Generate 16 random bytes (=128 bits)
3131
alignas(16) u8 bytes[16];
32-
i32 bytes_coun_copied = getrandom(bytes, sizeof(bytes), GRND_RANDOM);
32+
i32 bytes_count_copied = getrandom(bytes, sizeof(bytes), GRND_RANDOM);
3333

3434
// LCOV_EXCL_START ( Supposed never failed, else alternative is to read in /dev/{u}random )
35-
if (bytes_coun_copied != sizeof(bytes))
35+
if (bytes_count_copied != sizeof(bytes)) [[unlikely]]
3636
{
3737
return false;
3838
}

0 commit comments

Comments
 (0)