diff --git a/gio/tests/gdbus-export.c b/gio/tests/gdbus-export.c index 4d6d3a43e..506c7458a 100644 --- a/gio/tests/gdbus-export.c +++ b/gio/tests/gdbus-export.c @@ -337,6 +337,22 @@ introspect_callback (GDBusProxy *proxy, g_main_loop_quit (loop); } +static gint +compare_strings (gconstpointer a, + gconstpointer b) +{ + const gchar *sa = *(const gchar **) a; + const gchar *sb = *(const gchar **) b; + + /* Array terminator must sort last */ + if (sa == NULL) + return 1; + if (sb == NULL) + return -1; + + return strcmp (sa, sb); +} + static gchar ** get_nodes_at (GDBusConnection *c, const gchar *object_path) @@ -390,6 +406,9 @@ get_nodes_at (GDBusConnection *c, g_free (xml_data); g_dbus_node_info_unref (node_info); + /* Nodes are semantically unordered; sort array so tests can rely on order */ + g_ptr_array_sort (p, compare_strings); + return (gchar **) g_ptr_array_free (p, FALSE); } @@ -1240,9 +1259,9 @@ test_object_registration (void) nodes = get_nodes_at (c, "/foo/dyna"); g_assert (nodes != NULL); g_assert_cmpint (g_strv_length (nodes), ==, 3); - g_assert_cmpstr (nodes[0], ==, "lol"); - g_assert_cmpstr (nodes[1], ==, "cat"); - g_assert_cmpstr (nodes[2], ==, "cheezburger"); + g_assert_cmpstr (nodes[0], ==, "cat"); + g_assert_cmpstr (nodes[1], ==, "cheezburger"); + g_assert_cmpstr (nodes[2], ==, "lol"); g_strfreev (nodes); g_assert_cmpint (count_interfaces (c, "/foo/dyna/lol"), ==, 4); g_assert_cmpint (count_interfaces (c, "/foo/dyna/cat"), ==, 4); @@ -1253,10 +1272,10 @@ test_object_registration (void) nodes = get_nodes_at (c, "/foo/dyna"); g_assert (nodes != NULL); g_assert_cmpint (g_strv_length (nodes), ==, 4); - g_assert_cmpstr (nodes[0], ==, "lol"); - g_assert_cmpstr (nodes[1], ==, "cat"); - g_assert_cmpstr (nodes[2], ==, "cheezburger"); - g_assert_cmpstr (nodes[3], ==, "dynamicallycreated"); + g_assert_cmpstr (nodes[0], ==, "cat"); + g_assert_cmpstr (nodes[1], ==, "cheezburger"); + g_assert_cmpstr (nodes[2], ==, "dynamicallycreated"); + g_assert_cmpstr (nodes[3], ==, "lol"); g_strfreev (nodes); g_assert_cmpint (count_interfaces (c, "/foo/dyna/dynamicallycreated"), ==, 4); diff --git a/glib/ghash.c b/glib/ghash.c index 18c7185ef..d305530a8 100644 --- a/glib/ghash.c +++ b/glib/ghash.c @@ -39,6 +39,25 @@ #include "gslice.h" #include "grefcount.h" +/* The following #pragma is here so we can do this... + * + * #ifndef USE_SMALL_ARRAYS + * is_big = TRUE; + * #endif + * return is_big ? *(((gpointer *) a) + index) : GUINT_TO_POINTER (*(((guint *) a) + index)); + * + * ...instead of this... + * + * #ifndef USE_SMALL_ARRAYS + * return *(((gpointer *) a) + index); + * #else + * return is_big ? *(((gpointer *) a) + index) : GUINT_TO_POINTER (*(((guint *) a) + index)); + * #endif + * + * ...and still compile successfully when -Werror=duplicated-branches is passed. */ + +#pragma GCC diagnostic ignored "-Wduplicated-branches" + /** * SECTION:hash_tables * @title: Hash Tables @@ -213,6 +232,18 @@ #define HASH_IS_TOMBSTONE(h_) ((h_) == TOMBSTONE_HASH_VALUE) #define HASH_IS_REAL(h_) ((h_) >= 2) +/* If int is smaller than void * on our arch, we start out with + * int-sized keys and values and resize to pointer-sized entries as + * needed. This saves a good amount of memory when the HT is being + * used with e.g. GUINT_TO_POINTER(). */ + +#define BIG_ENTRY_SIZE (SIZEOF_VOID_P) +#define SMALL_ENTRY_SIZE (SIZEOF_INT) + +#if SMALL_ENTRY_SIZE < BIG_ENTRY_SIZE +# define USE_SMALL_ARRAYS +#endif + struct _GHashTable { gint size; @@ -221,9 +252,12 @@ struct _GHashTable gint nnodes; gint noccupied; /* nnodes + tombstones */ - gpointer *keys; + guint have_big_keys : 1; + guint have_big_values : 1; + + gpointer keys; guint *hashes; - gpointer *values; + gpointer values; GHashFunc hash_func; GEqualFunc key_equal_func; @@ -297,19 +331,15 @@ static const gint prime_mod [] = static void g_hash_table_set_shift (GHashTable *hash_table, gint shift) { - gint i; - guint mask = 0; - hash_table->size = 1 << shift; hash_table->mod = prime_mod [shift]; - for (i = 0; i < shift; i++) - { - mask <<= 1; - mask |= 1; - } + /* hash_table->size is always a power of two, so we can calculate the mask + * by simply subtracting 1 from it. The leading assertion ensures that + * we're really dealing with a power of two. */ - hash_table->mask = mask; + g_assert ((hash_table->size & (hash_table->size - 1)) == 0); + hash_table->mask = hash_table->size - 1; } static gint @@ -334,6 +364,67 @@ g_hash_table_set_shift_from_size (GHashTable *hash_table, gint size) g_hash_table_set_shift (hash_table, shift); } +static inline gpointer +g_hash_table_realloc_key_or_value_array (gpointer a, guint size, G_GNUC_UNUSED gboolean is_big) +{ +#ifdef USE_SMALL_ARRAYS + return g_realloc (a, size * (is_big ? BIG_ENTRY_SIZE : SMALL_ENTRY_SIZE)); +#else + return g_renew (gpointer, a, size); +#endif +} + +static inline gpointer +g_hash_table_fetch_key_or_value (gpointer a, guint index, gboolean is_big) +{ +#ifndef USE_SMALL_ARRAYS + is_big = TRUE; +#endif + return is_big ? *(((gpointer *) a) + index) : GUINT_TO_POINTER (*(((guint *) a) + index)); +} + +static inline void +g_hash_table_assign_key_or_value (gpointer a, guint index, gboolean is_big, gpointer v) +{ +#ifndef USE_SMALL_ARRAYS + is_big = TRUE; +#endif + if (is_big) + *(((gpointer *) a) + index) = v; + else + *(((guint *) a) + index) = GPOINTER_TO_UINT (v); +} + +static inline gpointer +g_hash_table_evict_key_or_value (gpointer a, guint index, gboolean is_big, gpointer v) +{ +#ifndef USE_SMALL_ARRAYS + is_big = TRUE; +#endif + if (is_big) + { + gpointer r = *(((gpointer *) a) + index); + *(((gpointer *) a) + index) = v; + return r; + } + else + { + gpointer r = GUINT_TO_POINTER (*(((guint *) a) + index)); + *(((guint *) a) + index) = GPOINTER_TO_UINT (v); + return r; + } +} + +static inline guint +g_hash_table_hash_to_index (GHashTable *hash_table, guint hash) +{ + /* Multiply the hash by a small prime before applying the modulo. This + * prevents the table from becoming densely packed, even with a poor hash + * function. A densely packed table would have poor performance on + * workloads with many failed lookups or a high degree of churn. */ + return (hash * 11) % hash_table->mod; +} + /* * g_hash_table_lookup_node: * @hash_table: our #GHashTable @@ -382,7 +473,7 @@ g_hash_table_lookup_node (GHashTable *hash_table, *hash_return = hash_value; - node_index = hash_value % hash_table->mod; + node_index = g_hash_table_hash_to_index (hash_table, hash_value); node_hash = hash_table->hashes[node_index]; while (!HASH_IS_UNUSED (node_hash)) @@ -393,7 +484,7 @@ g_hash_table_lookup_node (GHashTable *hash_table, */ if (node_hash == hash_value) { - gpointer node_key = hash_table->keys[node_index]; + gpointer node_key = g_hash_table_fetch_key_or_value (hash_table->keys, node_index, hash_table->have_big_keys); if (hash_table->key_equal_func) { @@ -443,15 +534,15 @@ g_hash_table_remove_node (GHashTable *hash_table, gpointer key; gpointer value; - key = hash_table->keys[i]; - value = hash_table->values[i]; + key = g_hash_table_fetch_key_or_value (hash_table->keys, i, hash_table->have_big_keys); + value = g_hash_table_fetch_key_or_value (hash_table->values, i, hash_table->have_big_values); /* Erect tombstone */ hash_table->hashes[i] = TOMBSTONE_HASH_VALUE; /* Be GC friendly */ - hash_table->keys[i] = NULL; - hash_table->values[i] = NULL; + g_hash_table_assign_key_or_value (hash_table->keys, i, hash_table->have_big_keys, NULL); + g_hash_table_assign_key_or_value (hash_table->values, i, hash_table->have_big_values, NULL); hash_table->nnodes--; @@ -501,8 +592,14 @@ g_hash_table_remove_all_nodes (GHashTable *hash_table, if (!destruction) { memset (hash_table->hashes, 0, hash_table->size * sizeof (guint)); + +#ifdef USE_SMALL_ARRAYS + memset (hash_table->keys, 0, hash_table->size * (hash_table->have_big_keys ? BIG_ENTRY_SIZE : SMALL_ENTRY_SIZE)); + memset (hash_table->values, 0, hash_table->size * (hash_table->have_big_values ? BIG_ENTRY_SIZE : SMALL_ENTRY_SIZE)); +#else memset (hash_table->keys, 0, hash_table->size * sizeof (gpointer)); memset (hash_table->values, 0, hash_table->size * sizeof (gpointer)); +#endif } return; @@ -523,7 +620,7 @@ g_hash_table_remove_all_nodes (GHashTable *hash_table, g_hash_table_set_shift (hash_table, HASH_TABLE_MIN_SHIFT); if (!destruction) { - hash_table->keys = g_new0 (gpointer, hash_table->size); + hash_table->keys = g_hash_table_realloc_key_or_value_array (NULL, hash_table->size, FALSE); hash_table->values = hash_table->keys; hash_table->hashes = g_new0 (guint, hash_table->size); } @@ -538,12 +635,13 @@ g_hash_table_remove_all_nodes (GHashTable *hash_table, { if (HASH_IS_REAL (old_hashes[i])) { - key = old_keys[i]; - value = old_values[i]; + key = g_hash_table_fetch_key_or_value (old_keys, i, hash_table->have_big_keys); + value = g_hash_table_fetch_key_or_value (old_values, i, hash_table->have_big_values); old_hashes[i] = UNUSED_HASH_VALUE; - old_keys[i] = NULL; - old_values[i] = NULL; + + g_hash_table_assign_key_or_value (old_keys, i, hash_table->have_big_keys, NULL); + g_hash_table_assign_key_or_value (old_values, i, hash_table->have_big_values, NULL); if (hash_table->key_destroy_func != NULL) hash_table->key_destroy_func (key); @@ -553,6 +651,9 @@ g_hash_table_remove_all_nodes (GHashTable *hash_table, } } + hash_table->have_big_keys = FALSE; + hash_table->have_big_values = FALSE; + /* Destroy old storage space. */ if (old_keys != old_values) g_free (old_values); @@ -561,6 +662,125 @@ g_hash_table_remove_all_nodes (GHashTable *hash_table, g_free (old_hashes); } +static void +realloc_arrays (GHashTable *hash_table, gboolean is_a_set) +{ + hash_table->hashes = g_renew (guint, hash_table->hashes, hash_table->size); + hash_table->keys = g_hash_table_realloc_key_or_value_array (hash_table->keys, hash_table->size, hash_table->have_big_keys); + + if (is_a_set) + hash_table->values = hash_table->keys; + else + hash_table->values = g_hash_table_realloc_key_or_value_array (hash_table->values, hash_table->size, hash_table->have_big_values); +} + +/* When resizing the table in place, we use a temporary bit array to keep + * track of which entries have been assigned a proper location in the new + * table layout. + * + * Each bit corresponds to a bucket. A bit is set if an entry was assigned + * its corresponding location during the resize and thus should not be + * evicted. The array starts out cleared to zero. */ + +static inline gboolean +get_status_bit (const guint32 *bitmap, guint index) +{ + return (bitmap[index / 32] >> (index % 32)) & 1; +} + +static inline void +set_status_bit (guint32 *bitmap, guint index) +{ + bitmap[index / 32] |= 1 << (index % 32); +} + +/* By calling dedicated resize functions for sets and maps, we avoid 2x + * test-and-branch per key in the inner loop. This yields a small + * performance improvement at the cost of a bit of macro gunk. */ + +#define DEFINE_RESIZE_FUNC(fname) \ +static void fname (GHashTable *hash_table, guint old_size, guint32 *reallocated_buckets_bitmap) \ +{ \ + guint i; \ + \ + for (i = 0; i < old_size; i++) \ + { \ + guint node_hash = hash_table->hashes[i]; \ + gpointer key, value G_GNUC_UNUSED; \ + \ + if (!HASH_IS_REAL (node_hash)) \ + { \ + /* Clear tombstones */ \ + hash_table->hashes[i] = UNUSED_HASH_VALUE; \ + continue; \ + } \ + \ + /* Skip entries relocated through eviction */ \ + if (get_status_bit (reallocated_buckets_bitmap, i)) \ + continue; \ + \ + hash_table->hashes[i] = UNUSED_HASH_VALUE; \ + EVICT_KEYVAL (hash_table, i, NULL, NULL, key, value); \ + \ + for (;;) \ + { \ + guint hash_val; \ + guint replaced_hash; \ + guint step = 0; \ + \ + hash_val = g_hash_table_hash_to_index (hash_table, node_hash); \ + \ + while (get_status_bit (reallocated_buckets_bitmap, hash_val)) \ + { \ + step++; \ + hash_val += step; \ + hash_val &= hash_table->mask; \ + } \ + \ + set_status_bit (reallocated_buckets_bitmap, hash_val); \ + \ + replaced_hash = hash_table->hashes[hash_val]; \ + hash_table->hashes[hash_val] = node_hash; \ + if (!HASH_IS_REAL (replaced_hash)) \ + { \ + ASSIGN_KEYVAL (hash_table, hash_val, key, value); \ + break; \ + } \ + \ + node_hash = replaced_hash; \ + EVICT_KEYVAL (hash_table, hash_val, key, value, key, value); \ + } \ + } \ +} + +#define ASSIGN_KEYVAL(ht, index, key, value) G_STMT_START{ \ + g_hash_table_assign_key_or_value ((ht)->keys, (index), (ht)->have_big_keys, (key)); \ + g_hash_table_assign_key_or_value ((ht)->values, (index), (ht)->have_big_values, (value)); \ + }G_STMT_END + +#define EVICT_KEYVAL(ht, index, key, value, outkey, outvalue) G_STMT_START{ \ + (outkey) = g_hash_table_evict_key_or_value ((ht)->keys, (index), (ht)->have_big_keys, (key)); \ + (outvalue) = g_hash_table_evict_key_or_value ((ht)->values, (index), (ht)->have_big_values, (value)); \ + }G_STMT_END + +DEFINE_RESIZE_FUNC (resize_map) + +#undef ASSIGN_KEYVAL +#undef EVICT_KEYVAL + +#define ASSIGN_KEYVAL(ht, index, key, value) G_STMT_START{ \ + g_hash_table_assign_key_or_value ((ht)->keys, (index), (ht)->have_big_keys, (key)); \ + }G_STMT_END + +#define EVICT_KEYVAL(ht, index, key, value, outkey, outvalue) G_STMT_START{ \ + (outkey) = g_hash_table_evict_key_or_value ((ht)->keys, (index), (ht)->have_big_keys, (key)); \ + }G_STMT_END + +DEFINE_RESIZE_FUNC (resize_set) + +#undef ASSIGN_KEYVAL +#undef EVICT_KEYVAL + /* * g_hash_table_resize: * @hash_table: our #GHashTable @@ -577,54 +797,47 @@ g_hash_table_remove_all_nodes (GHashTable *hash_table, static void g_hash_table_resize (GHashTable *hash_table) { - gpointer *new_keys; - gpointer *new_values; - guint *new_hashes; - gint old_size; - gint i; + guint32 *reallocated_buckets_bitmap; + guint old_size; + gboolean is_a_set; old_size = hash_table->size; - g_hash_table_set_shift_from_size (hash_table, hash_table->nnodes * 2); + is_a_set = hash_table->keys == hash_table->values; - new_keys = g_new0 (gpointer, hash_table->size); - if (hash_table->keys == hash_table->values) - new_values = new_keys; - else - new_values = g_new0 (gpointer, hash_table->size); - new_hashes = g_new0 (guint, hash_table->size); + /* The outer checks in g_hash_table_maybe_resize() will only consider + * cleanup/resize when the load factor goes below .25 (1/4, ignoring + * tombstones) or above .9375 (15/16, including tombstones). + * + * Once this happens, tombstones will always be cleaned out. If our + * load sans tombstones is greater than .75 (1/1.333, see below), we'll + * take this opportunity to grow the table too. + * + * Immediately after growing, the load factor will be in the range + * .375 .. .469. After shrinking, it will be exactly .5. */ - for (i = 0; i < old_size; i++) + g_hash_table_set_shift_from_size (hash_table, hash_table->nnodes * 1.333); + + if (hash_table->size > old_size) { - guint node_hash = hash_table->hashes[i]; - guint hash_val; - guint step = 0; + realloc_arrays (hash_table, is_a_set); + memset (&hash_table->hashes[old_size], 0, (hash_table->size - old_size) * sizeof (guint)); - if (!HASH_IS_REAL (node_hash)) - continue; - - hash_val = node_hash % hash_table->mod; - - while (!HASH_IS_UNUSED (new_hashes[hash_val])) - { - step++; - hash_val += step; - hash_val &= hash_table->mask; - } - - new_hashes[hash_val] = hash_table->hashes[i]; - new_keys[hash_val] = hash_table->keys[i]; - new_values[hash_val] = hash_table->values[i]; + reallocated_buckets_bitmap = g_new0 (guint32, (hash_table->size + 31) / 32); + } + else + { + reallocated_buckets_bitmap = g_new0 (guint32, (old_size + 31) / 32); } - if (hash_table->keys != hash_table->values) - g_free (hash_table->values); + if (is_a_set) + resize_set (hash_table, old_size, reallocated_buckets_bitmap); + else + resize_map (hash_table, old_size, reallocated_buckets_bitmap); - g_free (hash_table->keys); - g_free (hash_table->hashes); + g_free (reallocated_buckets_bitmap); - hash_table->keys = new_keys; - hash_table->values = new_values; - hash_table->hashes = new_hashes; + if (hash_table->size < old_size) + realloc_arrays (hash_table, is_a_set); hash_table->noccupied = hash_table->nnodes; } @@ -649,6 +862,94 @@ g_hash_table_maybe_resize (GHashTable *hash_table) g_hash_table_resize (hash_table); } +#ifdef USE_SMALL_ARRAYS + +static inline gboolean +entry_is_big (gpointer v) +{ + return (((guintptr) v) >> ((BIG_ENTRY_SIZE - SMALL_ENTRY_SIZE) * 8)) != 0; +} + +static inline gboolean +g_hash_table_maybe_make_big_keys_or_values (gpointer *a_p, gpointer v, gint ht_size) +{ + if (entry_is_big (v)) + { + guint *a = (guint *) *a_p; + gpointer *a_new; + gint i; + + a_new = g_new (gpointer, ht_size); + + for (i = 0; i < ht_size; i++) + { + a_new[i] = GUINT_TO_POINTER (a[i]); + } + + g_free (a); + *a_p = a_new; + return TRUE; + } + + return FALSE; +} + +#endif + +static inline void +g_hash_table_ensure_keyval_fits (GHashTable *hash_table, gpointer key, gpointer value) +{ + gboolean is_a_set = (hash_table->keys == hash_table->values); + +#ifdef USE_SMALL_ARRAYS + + /* Convert from set to map? */ + if (is_a_set) + { + if (hash_table->have_big_keys) + { + if (key != value) + hash_table->values = g_memdup (hash_table->keys, sizeof (gpointer) * hash_table->size); + /* Keys and values are both big now, so no need for further checks */ + return; + } + else + { + if (key != value) + { + hash_table->values = g_memdup (hash_table->keys, sizeof (guint) * hash_table->size); + is_a_set = FALSE; + } + } + } + + /* Make keys big? */ + if (!hash_table->have_big_keys) + { + hash_table->have_big_keys = g_hash_table_maybe_make_big_keys_or_values (&hash_table->keys, key, hash_table->size); + + if (is_a_set) + { + hash_table->values = hash_table->keys; + hash_table->have_big_values = hash_table->have_big_keys; + } + } + + /* Make values big? */ + if (!is_a_set && !hash_table->have_big_values) + { + hash_table->have_big_values = g_hash_table_maybe_make_big_keys_or_values (&hash_table->values, value, hash_table->size); + } + +#else + + /* Just split if necessary */ + if (is_a_set && key != value) + hash_table->values = g_memdup (hash_table->keys, sizeof (gpointer) * hash_table->size); + +#endif +} + /** * g_hash_table_new: * @hash_func: a function to create a hash value from a key @@ -726,10 +1027,18 @@ g_hash_table_new_full (GHashFunc hash_func, #endif hash_table->key_destroy_func = key_destroy_func; hash_table->value_destroy_func = value_destroy_func; - hash_table->keys = g_new0 (gpointer, hash_table->size); + hash_table->keys = g_hash_table_realloc_key_or_value_array (NULL, hash_table->size, FALSE); hash_table->values = hash_table->keys; hash_table->hashes = g_new0 (guint, hash_table->size); +#ifdef USE_SMALL_ARRAYS + hash_table->have_big_keys = FALSE; + hash_table->have_big_values = FALSE; +#else + hash_table->have_big_keys = TRUE; + hash_table->have_big_values = TRUE; +#endif + return hash_table; } @@ -812,9 +1121,9 @@ g_hash_table_iter_next (GHashTableIter *iter, while (!HASH_IS_REAL (ri->hash_table->hashes[position])); if (key != NULL) - *key = ri->hash_table->keys[position]; + *key = g_hash_table_fetch_key_or_value (ri->hash_table->keys, position, ri->hash_table->have_big_keys); if (value != NULL) - *value = ri->hash_table->values[position]; + *value = g_hash_table_fetch_key_or_value (ri->hash_table->values, position, ri->hash_table->have_big_values); ri->position = position; return TRUE; @@ -917,6 +1226,7 @@ g_hash_table_insert_node (GHashTable *hash_table, gboolean already_exists; guint old_hash; gpointer key_to_free = NULL; + gpointer key_to_keep = NULL; gpointer value_to_free = NULL; old_hash = hash_table->hashes[node_index]; @@ -946,31 +1256,31 @@ g_hash_table_insert_node (GHashTable *hash_table, * because we might change the value in the event that the two * arrays are shared. */ - value_to_free = hash_table->values[node_index]; + value_to_free = g_hash_table_fetch_key_or_value (hash_table->values, node_index, hash_table->have_big_values); if (keep_new_key) { - key_to_free = hash_table->keys[node_index]; - hash_table->keys[node_index] = new_key; + key_to_free = g_hash_table_fetch_key_or_value (hash_table->keys, node_index, hash_table->have_big_keys); + key_to_keep = new_key; } else - key_to_free = new_key; + { + key_to_free = new_key; + key_to_keep = g_hash_table_fetch_key_or_value (hash_table->keys, node_index, hash_table->have_big_keys); + } } else { hash_table->hashes[node_index] = key_hash; - hash_table->keys[node_index] = new_key; + key_to_keep = new_key; } - /* Step two: check if the value that we are about to write to the - * table is the same as the key in the same position. If it's not, - * split the table. - */ - if (G_UNLIKELY (hash_table->keys == hash_table->values && hash_table->keys[node_index] != new_value)) - hash_table->values = g_memdup (hash_table->keys, sizeof (gpointer) * hash_table->size); + /* Resize key/value arrays and split table as necessary */ + g_hash_table_ensure_keyval_fits (hash_table, key_to_keep, new_value); + g_hash_table_assign_key_or_value (hash_table->keys, node_index, hash_table->have_big_keys, key_to_keep); /* Step 3: Actually do the write */ - hash_table->values[node_index] = new_value; + g_hash_table_assign_key_or_value (hash_table->values, node_index, hash_table->have_big_values, new_value); /* Now, the bookkeeping... */ if (!already_exists) @@ -1032,7 +1342,8 @@ g_hash_table_iter_replace (GHashTableIter *iter, g_return_if_fail (ri->position < ri->hash_table->size); node_hash = ri->hash_table->hashes[ri->position]; - key = ri->hash_table->keys[ri->position]; + + key = g_hash_table_fetch_key_or_value (ri->hash_table->keys, ri->position, ri->hash_table->have_big_keys); g_hash_table_insert_node (ri->hash_table, ri->position, node_hash, key, value, TRUE, TRUE); @@ -1153,7 +1464,7 @@ g_hash_table_lookup (GHashTable *hash_table, node_index = g_hash_table_lookup_node (hash_table, key, &node_hash); return HASH_IS_REAL (hash_table->hashes[node_index]) - ? hash_table->values[node_index] + ? g_hash_table_fetch_key_or_value (hash_table->values, node_index, hash_table->have_big_values) : NULL; } @@ -1200,10 +1511,10 @@ g_hash_table_lookup_extended (GHashTable *hash_table, } if (orig_key) - *orig_key = hash_table->keys[node_index]; + *orig_key = g_hash_table_fetch_key_or_value (hash_table->keys, node_index, hash_table->have_big_keys); if (value) - *value = hash_table->values[node_index]; + *value = g_hash_table_fetch_key_or_value (hash_table->values, node_index, hash_table->have_big_values); return TRUE; } @@ -1474,10 +1785,16 @@ g_hash_table_steal_extended (GHashTable *hash_table, } if (stolen_key != NULL) - *stolen_key = g_steal_pointer (&hash_table->keys[node_index]); + { + *stolen_key = g_hash_table_fetch_key_or_value (hash_table->keys, node_index, hash_table->have_big_keys); + g_hash_table_assign_key_or_value (hash_table->keys, node_index, hash_table->have_big_keys, NULL); + } if (stolen_value != NULL) - *stolen_value = g_steal_pointer (&hash_table->values[node_index]); + { + *stolen_value = g_hash_table_fetch_key_or_value (hash_table->values, node_index, hash_table->have_big_values); + g_hash_table_assign_key_or_value (hash_table->values, node_index, hash_table->have_big_values, NULL); + } g_hash_table_remove_node (hash_table, node_index, FALSE); g_hash_table_maybe_resize (hash_table); @@ -1571,8 +1888,8 @@ g_hash_table_foreach_remove_or_steal (GHashTable *hash_table, for (i = 0; i < hash_table->size; i++) { guint node_hash = hash_table->hashes[i]; - gpointer node_key = hash_table->keys[i]; - gpointer node_value = hash_table->values[i]; + gpointer node_key = g_hash_table_fetch_key_or_value (hash_table->keys, i, hash_table->have_big_keys); + gpointer node_value = g_hash_table_fetch_key_or_value (hash_table->values, i, hash_table->have_big_values); if (HASH_IS_REAL (node_hash) && (* func) (node_key, node_value, user_data)) @@ -1687,8 +2004,8 @@ g_hash_table_foreach (GHashTable *hash_table, for (i = 0; i < hash_table->size; i++) { guint node_hash = hash_table->hashes[i]; - gpointer node_key = hash_table->keys[i]; - gpointer node_value = hash_table->values[i]; + gpointer node_key = g_hash_table_fetch_key_or_value (hash_table->keys, i, hash_table->have_big_keys); + gpointer node_value = g_hash_table_fetch_key_or_value (hash_table->values, i, hash_table->have_big_values); if (HASH_IS_REAL (node_hash)) (* func) (node_key, node_value, user_data); @@ -1748,8 +2065,8 @@ g_hash_table_find (GHashTable *hash_table, for (i = 0; i < hash_table->size; i++) { guint node_hash = hash_table->hashes[i]; - gpointer node_key = hash_table->keys[i]; - gpointer node_value = hash_table->values[i]; + gpointer node_key = g_hash_table_fetch_key_or_value (hash_table->keys, i, hash_table->have_big_keys); + gpointer node_value = g_hash_table_fetch_key_or_value (hash_table->values, i, hash_table->have_big_values); if (HASH_IS_REAL (node_hash)) match = predicate (node_key, node_value, user_data); @@ -1811,7 +2128,7 @@ g_hash_table_get_keys (GHashTable *hash_table) for (i = 0; i < hash_table->size; i++) { if (HASH_IS_REAL (hash_table->hashes[i])) - retval = g_list_prepend (retval, hash_table->keys[i]); + retval = g_list_prepend (retval, g_hash_table_fetch_key_or_value (hash_table->keys, i, hash_table->have_big_keys)); } return retval; @@ -1856,7 +2173,7 @@ g_hash_table_get_keys_as_array (GHashTable *hash_table, for (i = 0; i < hash_table->size; i++) { if (HASH_IS_REAL (hash_table->hashes[i])) - result[j++] = hash_table->keys[i]; + result[j++] = g_hash_table_fetch_key_or_value (hash_table->keys, i, hash_table->have_big_keys); } g_assert_cmpint (j, ==, hash_table->nnodes); result[j] = NULL; @@ -1897,7 +2214,7 @@ g_hash_table_get_values (GHashTable *hash_table) for (i = 0; i < hash_table->size; i++) { if (HASH_IS_REAL (hash_table->hashes[i])) - retval = g_list_prepend (retval, hash_table->values[i]); + retval = g_list_prepend (retval, g_hash_table_fetch_key_or_value (hash_table->values, i, hash_table->have_big_values)); } return retval; diff --git a/glib/tests/hash.c b/glib/tests/hash.c index 7953fd3a7..16acbbf2a 100644 --- a/glib/tests/hash.c +++ b/glib/tests/hash.c @@ -1353,6 +1353,9 @@ struct _GHashTable gint nnodes; gint noccupied; /* nnodes + tombstones */ + guint have_big_keys : 1; + guint have_big_values : 1; + gpointer *keys; guint *hashes; gpointer *values; @@ -1387,6 +1390,23 @@ count_keys (GHashTable *h, gint *unused, gint *occupied, gint *tombstones) } } +#define BIG_ENTRY_SIZE (SIZEOF_VOID_P) +#define SMALL_ENTRY_SIZE (SIZEOF_INT) + +#if SMALL_ENTRY_SIZE < BIG_ENTRY_SIZE +# define USE_SMALL_ARRAYS +#endif + +static gpointer +fetch_key_or_value (gpointer a, guint index, gboolean is_big) +{ +#ifdef USE_SMALL_ARRAYS + return is_big ? *(((gpointer *) a) + index) : GUINT_TO_POINTER (*(((guint *) a) + index)); +#else + return *(((gpointer *) a) + index); +#endif +} + static void check_data (GHashTable *h) { @@ -1394,14 +1414,9 @@ check_data (GHashTable *h) for (i = 0; i < h->size; i++) { - if (h->hashes[i] < 2) + if (h->hashes[i] >= 2) { - g_assert (h->keys[i] == NULL); - g_assert (h->values[i] == NULL); - } - else - { - g_assert_cmpint (h->hashes[i], ==, h->hash_func (h->keys[i])); + g_assert_cmpint (h->hashes[i], ==, h->hash_func (fetch_key_or_value (h->keys, i, h->have_big_keys))); } } }