mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-03-28 02:20:04 +01:00
Merge branch 'ghash-hpj-2018' into 'master'
GHashTable improvements See merge request GNOME/glib!208
This commit is contained in:
commit
efda2be302
@ -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);
|
||||
|
||||
|
497
glib/ghash.c
497
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;
|
||||
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user