3 * A mostly concurrent hashtable
6 * Rodrigo Kumpera (kumpera@gmail.com)
11 #include "mono-conc-hashtable.h"
12 #include <mono/utils/hazard-pointer.h>
14 /* Configuration knobs. */
16 #define INITIAL_SIZE 32
17 #define LOAD_FACTOR 0.75f
18 #define TOMBSTONE ((gpointer)(ssize_t)-1)
30 struct _MonoConcurrentHashTable
{
31 volatile conc_table
*table
; /* goes to HP0 */
33 GEqualFunc equal_func
;
36 GDestroyNotify key_destroy_func
;
37 GDestroyNotify value_destroy_func
;
41 conc_table_new (int size
)
43 conc_table
*res
= g_new (conc_table
, 1);
44 res
->table_size
= size
;
45 res
->kvs
= g_new0 (key_value_pair
, size
);
50 conc_table_free (gpointer ptr
)
52 conc_table
*table
= (conc_table
*)ptr
;
58 conc_table_lf_free (conc_table
*table
)
60 mono_thread_hazardous_try_free (table
, conc_table_free
);
65 A common problem with power of two hashtables is that it leads of bad clustering when dealing
68 The solution here is to mix the bits from two primes plus the hash itself, it produces a better spread
69 than just the numbers.
72 static MONO_ALWAYS_INLINE
int
75 return ((hash
* 215497) >> 16) ^ (hash
* 1823231 + hash
);
78 static MONO_ALWAYS_INLINE
void
79 insert_one_local (conc_table
*table
, GHashFunc hash_func
, gpointer key
, gpointer value
)
81 key_value_pair
*kvs
= table
->kvs
;
82 int table_mask
= table
->table_size
- 1;
83 int hash
= mix_hash (hash_func (key
));
84 int i
= hash
& table_mask
;
86 while (table
->kvs
[i
].key
)
87 i
= (i
+ 1) & table_mask
;
90 kvs
[i
].value
= value
;
93 /* LOCKING: Must be called holding hash_table->mutex */
95 expand_table (MonoConcurrentHashTable
*hash_table
)
97 conc_table
*old_table
= (conc_table
*)hash_table
->table
;
98 conc_table
*new_table
= conc_table_new (old_table
->table_size
* 2);
99 key_value_pair
*kvs
= old_table
->kvs
;
102 for (i
= 0; i
< old_table
->table_size
; ++i
) {
103 if (kvs
[i
].key
&& kvs
[i
].key
!= TOMBSTONE
)
104 insert_one_local (new_table
, hash_table
->hash_func
, kvs
[i
].key
, kvs
[i
].value
);
106 mono_memory_barrier ();
107 hash_table
->table
= new_table
;
108 hash_table
->overflow_count
= (int)(new_table
->table_size
* LOAD_FACTOR
);
109 conc_table_lf_free (old_table
);
113 MonoConcurrentHashTable
*
114 mono_conc_hashtable_new (GHashFunc hash_func
, GEqualFunc key_equal_func
)
116 MonoConcurrentHashTable
*res
= g_new0 (MonoConcurrentHashTable
, 1);
117 res
->hash_func
= hash_func
? hash_func
: g_direct_hash
;
118 res
->equal_func
= key_equal_func
;
119 // res->equal_func = g_direct_equal;
120 res
->table
= conc_table_new (INITIAL_SIZE
);
121 res
->element_count
= 0;
122 res
->overflow_count
= (int)(INITIAL_SIZE
* LOAD_FACTOR
);
126 MonoConcurrentHashTable
*
127 mono_conc_hashtable_new_full (GHashFunc hash_func
, GEqualFunc key_equal_func
, GDestroyNotify key_destroy_func
, GDestroyNotify value_destroy_func
)
129 MonoConcurrentHashTable
*res
= mono_conc_hashtable_new (hash_func
, key_equal_func
);
130 res
->key_destroy_func
= key_destroy_func
;
131 res
->value_destroy_func
= value_destroy_func
;
137 mono_conc_hashtable_destroy (MonoConcurrentHashTable
*hash_table
)
139 if (hash_table
->key_destroy_func
|| hash_table
->value_destroy_func
) {
141 conc_table
*table
= (conc_table
*)hash_table
->table
;
142 key_value_pair
*kvs
= table
->kvs
;
144 for (i
= 0; i
< table
->table_size
; ++i
) {
145 if (kvs
[i
].key
&& kvs
[i
].key
!= TOMBSTONE
) {
146 if (hash_table
->key_destroy_func
)
147 (hash_table
->key_destroy_func
) (kvs
[i
].key
);
148 if (hash_table
->value_destroy_func
)
149 (hash_table
->value_destroy_func
) (kvs
[i
].value
);
153 conc_table_free ((gpointer
)hash_table
->table
);
158 mono_conc_hashtable_lookup (MonoConcurrentHashTable
*hash_table
, gpointer key
)
160 MonoThreadHazardPointers
* hp
;
162 int hash
, i
, table_mask
;
164 hash
= mix_hash (hash_table
->hash_func (key
));
165 hp
= mono_hazard_pointer_get ();
168 table
= (conc_table
*)mono_get_hazardous_pointer ((gpointer
volatile*)&hash_table
->table
, hp
, 0);
169 table_mask
= table
->table_size
- 1;
171 i
= hash
& table_mask
;
173 if (G_LIKELY (!hash_table
->equal_func
)) {
174 while (kvs
[i
].key
) {
175 if (key
== kvs
[i
].key
) {
177 /* The read of keys must happen before the read of values */
178 mono_memory_barrier ();
179 value
= kvs
[i
].value
;
180 /* FIXME check for NULL if we add suppport for removal */
181 mono_hazard_pointer_clear (hp
, 0);
184 i
= (i
+ 1) & table_mask
;
187 GEqualFunc equal
= hash_table
->equal_func
;
189 while (kvs
[i
].key
) {
190 if (kvs
[i
].key
!= TOMBSTONE
&& equal (key
, kvs
[i
].key
)) {
192 /* The read of keys must happen before the read of values */
193 mono_memory_barrier ();
194 value
= kvs
[i
].value
;
196 /* We just read a value been deleted, try again. */
197 if (G_UNLIKELY (!value
))
200 mono_hazard_pointer_clear (hp
, 0);
203 i
= (i
+ 1) & table_mask
;
207 /* The table might have expanded and the value is now on the newer table */
208 mono_memory_barrier ();
209 if (hash_table
->table
!= table
)
212 mono_hazard_pointer_clear (hp
, 0);
217 * mono_conc_hashtable_remove:
218 * Remove a value from the hashtable. Requires external locking
219 * \returns the old value if \p key is already present or NULL
222 mono_conc_hashtable_remove (MonoConcurrentHashTable
*hash_table
, gpointer key
)
226 int hash
, i
, table_mask
;
228 g_assert (key
!= NULL
&& key
!= TOMBSTONE
);
230 hash
= mix_hash (hash_table
->hash_func (key
));
232 table
= (conc_table
*)hash_table
->table
;
234 table_mask
= table
->table_size
- 1;
235 i
= hash
& table_mask
;
237 if (!hash_table
->equal_func
) {
240 return NULL
; /*key not found*/
243 if (key
== kvs
[i
].key
) {
244 gpointer value
= kvs
[i
].value
;
245 kvs
[i
].value
= NULL
;
246 mono_memory_barrier ();
247 kvs
[i
].key
= TOMBSTONE
;
249 if (hash_table
->key_destroy_func
!= NULL
)
250 (*hash_table
->key_destroy_func
) (key
);
251 if (hash_table
->value_destroy_func
!= NULL
)
252 (*hash_table
->value_destroy_func
) (value
);
256 i
= (i
+ 1) & table_mask
;
259 GEqualFunc equal
= hash_table
->equal_func
;
262 return NULL
; /*key not found*/
265 if (kvs
[i
].key
!= TOMBSTONE
&& equal (key
, kvs
[i
].key
)) {
266 gpointer old_key
= kvs
[i
].key
;
267 gpointer value
= kvs
[i
].value
;
268 kvs
[i
].value
= NULL
;
269 mono_memory_barrier ();
270 kvs
[i
].key
= TOMBSTONE
;
272 if (hash_table
->key_destroy_func
!= NULL
)
273 (*hash_table
->key_destroy_func
) (old_key
);
274 if (hash_table
->value_destroy_func
!= NULL
)
275 (*hash_table
->value_destroy_func
) (value
);
279 i
= (i
+ 1) & table_mask
;
284 * mono_conc_hashtable_insert:
285 * Insert a value into the hashtable. Requires external locking.
286 * \returns the old value if \p key is already present or NULL
289 mono_conc_hashtable_insert (MonoConcurrentHashTable
*hash_table
, gpointer key
, gpointer value
)
293 int hash
, i
, table_mask
;
295 g_assert (key
!= NULL
&& key
!= TOMBSTONE
);
296 g_assert (value
!= NULL
);
298 hash
= mix_hash (hash_table
->hash_func (key
));
300 if (hash_table
->element_count
>= hash_table
->overflow_count
)
301 expand_table (hash_table
);
303 table
= (conc_table
*)hash_table
->table
;
305 table_mask
= table
->table_size
- 1;
306 i
= hash
& table_mask
;
308 if (!hash_table
->equal_func
) {
310 if (!kvs
[i
].key
|| kvs
[i
].key
== TOMBSTONE
) {
311 kvs
[i
].value
= value
;
312 /* The write to values must happen after the write to keys */
313 mono_memory_barrier ();
315 ++hash_table
->element_count
;
318 if (key
== kvs
[i
].key
) {
319 gpointer value
= kvs
[i
].value
;
322 i
= (i
+ 1) & table_mask
;
325 GEqualFunc equal
= hash_table
->equal_func
;
327 if (!kvs
[i
].key
|| kvs
[i
].key
== TOMBSTONE
) {
328 kvs
[i
].value
= value
;
329 /* The write to values must happen after the write to keys */
330 mono_memory_barrier ();
332 ++hash_table
->element_count
;
335 if (equal (key
, kvs
[i
].key
)) {
336 gpointer value
= kvs
[i
].value
;
339 i
= (i
+ 1) & table_mask
;
345 * mono_conc_hashtable_foreach:
346 * Calls \p func for each value in the hashtable. Requires external locking.
349 mono_conc_hashtable_foreach (MonoConcurrentHashTable
*hash_table
, GHFunc func
, gpointer userdata
)
352 conc_table
*table
= (conc_table
*)hash_table
->table
;
353 key_value_pair
*kvs
= table
->kvs
;
355 for (i
= 0; i
< table
->table_size
; ++i
) {
356 if (kvs
[i
].key
&& kvs
[i
].key
!= TOMBSTONE
) {
357 func (kvs
[i
].key
, kvs
[i
].value
, userdata
);