@@ -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 >;
0 commit comments