mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-25 15:06:14 +01:00
Rewrite most of GHashTable to use open addressing with quadratic probing
2008-09-19 Hans Petter Jansson <hpj@novell.com> Rewrite most of GHashTable to use open addressing with quadratic probing instead of chaining. This has the potential to reduce memory fragmentation significantly, while being slightly faster due to better locality and no need to call alloc/free functions for nodes. Benchmarks suggest it also uses less memory overall. * glib/ghash.c (prime_mod): Table of suitable primes for initial-probe distribution. (g_hash_table_set_shift): New function. (g_hash_table_find_closest_shift): New function. (g_hash_table_set_shift_from_size): New function. (g_hash_table_lookup_node_for_insertion): New function. (g_hash_table_lookup_node): Rewritten to return node index instead of pointer, use quadratic probe on flat table, and not return insertion data. The latter saves some computation for read-only lookups. (g_hash_table_remove_node): Rewrite to take a pointer directly to the node structure to remove, and clear that. Remove unlinking code. (g_hash_table_remove_all_nodes): Rewrite to not clear nodes individually, but en masse using memset () after potentially calling notify functions. (iter_remove_or_steal): Use new data structure and algorithm. Vastly simplified - now just a call to g_hash_table_remove_node (). (g_hash_table_resize): New resize code, re-indexing with new prime and cleaning up tombstones. (g_hash_table_maybe_resize): Table may hold 8 buckets minimum, no less than 1/4 load excluding tombstones, and no more than 15/16 load including tombstones. These numbers are the results of a lot of benchmarking with multiple complex applications, and should not be changed lightly. (g_hash_table_iter_next) (g_hash_table_lookup) (g_hash_table_lookup_extended) (g_hash_table_insert_internal) (g_hash_table_remove_internal) (g_hash_table_foreach_remove_or_steal) (g_hash_table_foreach) (g_hash_table_find) (g_hash_table_get_keys) (g_hash_table_get_values): Use new data structure and algorithm, fairly trivial changes. svn path=/trunk/; revision=7518
This commit is contained in:
parent
39a0716c92
commit
991b625aea
43
ChangeLog
43
ChangeLog
@ -1,3 +1,46 @@
|
||||
2008-09-19 Hans Petter Jansson <hpj@novell.com>
|
||||
|
||||
Rewrite most of GHashTable to use open addressing with quadratic
|
||||
probing instead of chaining. This has the potential to reduce memory
|
||||
fragmentation significantly, while being slightly faster due to
|
||||
better locality and no need to call alloc/free functions for nodes.
|
||||
Benchmarks suggest it also uses less memory overall.
|
||||
|
||||
* glib/ghash.c (prime_mod): Table of suitable primes for
|
||||
initial-probe distribution.
|
||||
(g_hash_table_set_shift): New function.
|
||||
(g_hash_table_find_closest_shift): New function.
|
||||
(g_hash_table_set_shift_from_size): New function.
|
||||
(g_hash_table_lookup_node_for_insertion): New function.
|
||||
(g_hash_table_lookup_node): Rewritten to return node index instead of
|
||||
pointer, use quadratic probe on flat table, and not return insertion
|
||||
data. The latter saves some computation for read-only lookups.
|
||||
(g_hash_table_remove_node): Rewrite to take a pointer directly to the
|
||||
node structure to remove, and clear that. Remove unlinking code.
|
||||
(g_hash_table_remove_all_nodes): Rewrite to not clear nodes
|
||||
individually, but en masse using memset () after potentially calling
|
||||
notify functions.
|
||||
(iter_remove_or_steal): Use new data structure and algorithm. Vastly
|
||||
simplified - now just a call to g_hash_table_remove_node ().
|
||||
(g_hash_table_resize): New resize code, re-indexing with new prime
|
||||
and cleaning up tombstones.
|
||||
(g_hash_table_maybe_resize): Table may hold 8 buckets minimum, no less
|
||||
than 1/4 load excluding tombstones, and no more than 15/16 load
|
||||
including tombstones. These numbers are the results of a lot of
|
||||
benchmarking with multiple complex applications, and should not be
|
||||
changed lightly.
|
||||
(g_hash_table_iter_next)
|
||||
(g_hash_table_lookup)
|
||||
(g_hash_table_lookup_extended)
|
||||
(g_hash_table_insert_internal)
|
||||
(g_hash_table_remove_internal)
|
||||
(g_hash_table_foreach_remove_or_steal)
|
||||
(g_hash_table_foreach)
|
||||
(g_hash_table_find)
|
||||
(g_hash_table_get_keys)
|
||||
(g_hash_table_get_values): Use new data structure and algorithm,
|
||||
fairly trivial changes.
|
||||
|
||||
2008-09-19 Tor Lillqvist <tml@novell.com>
|
||||
|
||||
* glib-zip.in: Look for man pages in share/man.
|
||||
|
581
glib/ghash.c
581
glib/ghash.c
@ -30,13 +30,12 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <string.h> /* memset */
|
||||
|
||||
#include "glib.h"
|
||||
#include "galias.h"
|
||||
|
||||
|
||||
#define HASH_TABLE_MIN_SIZE 11
|
||||
#define HASH_TABLE_MAX_SIZE 13845163
|
||||
|
||||
#define HASH_TABLE_MIN_SHIFT 3 /* 1 << 3 == 8 buckets */
|
||||
|
||||
typedef struct _GHashNode GHashNode;
|
||||
|
||||
@ -44,15 +43,21 @@ struct _GHashNode
|
||||
{
|
||||
gpointer key;
|
||||
gpointer value;
|
||||
GHashNode *next;
|
||||
|
||||
/* If key_hash == 0, node is not in use
|
||||
* If key_hash == 1, node is a tombstone
|
||||
* If key_hash >= 2, node contains data */
|
||||
guint key_hash;
|
||||
};
|
||||
|
||||
struct _GHashTable
|
||||
{
|
||||
gint size;
|
||||
gint mod;
|
||||
guint mask;
|
||||
gint nnodes;
|
||||
GHashNode **nodes;
|
||||
gint noccupied; /* nnodes + tombstones */
|
||||
GHashNode *nodes;
|
||||
GHashFunc hash_func;
|
||||
GEqualFunc key_equal_func;
|
||||
volatile gint ref_count;
|
||||
@ -70,20 +75,100 @@ struct _GHashTable
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GHashTable *hash_table;
|
||||
GHashNode *prev_node;
|
||||
GHashNode *node;
|
||||
int position;
|
||||
gboolean pre_advanced;
|
||||
int version;
|
||||
GHashTable *hash_table;
|
||||
gpointer dummy1;
|
||||
gpointer dummy2;
|
||||
int position;
|
||||
gboolean dummy3;
|
||||
int version;
|
||||
} RealIter;
|
||||
|
||||
/* Each table size has an associated prime modulo (the first prime
|
||||
* lower than the table size) used to find the initial bucket. Probing
|
||||
* then works modulo 2^n. The prime modulo is necessary to get a
|
||||
* good distribution with poor hash functions. */
|
||||
static const gint prime_mod [] =
|
||||
{
|
||||
1, /* For 1 << 0 */
|
||||
2,
|
||||
3,
|
||||
7,
|
||||
13,
|
||||
31,
|
||||
61,
|
||||
127,
|
||||
251,
|
||||
509,
|
||||
1021,
|
||||
2039,
|
||||
4093,
|
||||
8191,
|
||||
16381,
|
||||
32749,
|
||||
65521, /* For 1 << 16 */
|
||||
131071,
|
||||
262139,
|
||||
524287,
|
||||
1048573,
|
||||
2097143,
|
||||
4194301,
|
||||
8388593,
|
||||
16777213,
|
||||
33554393,
|
||||
67108859,
|
||||
134217689,
|
||||
268435399,
|
||||
536870909,
|
||||
1073741789,
|
||||
2147483647 /* For 1 << 31 */
|
||||
};
|
||||
|
||||
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->mask = mask;
|
||||
}
|
||||
|
||||
static gint
|
||||
g_hash_table_find_closest_shift (gint n)
|
||||
{
|
||||
gint i;
|
||||
|
||||
for (i = 0; n; i++)
|
||||
n >>= 1;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static void
|
||||
g_hash_table_set_shift_from_size (GHashTable *hash_table, gint size)
|
||||
{
|
||||
gint shift;
|
||||
|
||||
shift = g_hash_table_find_closest_shift (size);
|
||||
shift = MAX (shift, HASH_TABLE_MIN_SHIFT);
|
||||
|
||||
g_hash_table_set_shift (hash_table, shift);
|
||||
}
|
||||
|
||||
/*
|
||||
* g_hash_table_lookup_node:
|
||||
* @hash_table: our #GHashTable
|
||||
* @key: the key to lookup against
|
||||
* @hash_return: optional key hash return location
|
||||
* Return value: a pointer to the described #GHashNode pointer
|
||||
* Return value: index of the described #GHashNode
|
||||
*
|
||||
* Performs a lookup in the hash table. Virtually all hash operations
|
||||
* will use this function internally.
|
||||
@ -92,118 +177,169 @@ typedef struct
|
||||
* user's hash function.
|
||||
*
|
||||
* If an entry in the table matching @key is found then this function
|
||||
* returns a pointer to the pointer to that entry in the table. In
|
||||
* the case that the entry is at the head of a chain, this pointer
|
||||
* will be an item in the nodes[] array. In the case that the entry
|
||||
* is not at the head of a chain, this pointer will be the ->next
|
||||
* pointer on the node that preceeds it.
|
||||
*
|
||||
* In the case that no matching entry exists in the table, a pointer
|
||||
* to a %NULL pointer will be returned. To insert a item, this %NULL
|
||||
* pointer should be updated to point to the new #GHashNode.
|
||||
*
|
||||
* If @hash_return is a pass-by-reference parameter. If it is
|
||||
* non-%NULL then the computed hash value is returned. This is to
|
||||
* save insertions from having to compute the hash record again for
|
||||
* the new record.
|
||||
* returns the index of that entry in the table, and if not, the
|
||||
* index of an empty node (never a tombstone).
|
||||
*/
|
||||
static inline GHashNode **
|
||||
static inline guint
|
||||
g_hash_table_lookup_node (GHashTable *hash_table,
|
||||
gconstpointer key,
|
||||
guint *hash_return)
|
||||
gconstpointer key)
|
||||
{
|
||||
GHashNode **node_ptr, *node;
|
||||
GHashNode *node;
|
||||
guint node_index;
|
||||
guint hash_value;
|
||||
guint step = 0;
|
||||
|
||||
/* Empty buckets have hash_value set to 0, and for tombstones, it's 1.
|
||||
* We need to make sure our hash value is not one of these. */
|
||||
|
||||
hash_value = (* hash_table->hash_func) (key);
|
||||
node_ptr = &hash_table->nodes[hash_value % hash_table->size];
|
||||
if (G_UNLIKELY (hash_value <= 1))
|
||||
hash_value = 2;
|
||||
|
||||
if (hash_return)
|
||||
*hash_return = hash_value;
|
||||
node_index = hash_value % hash_table->mod;
|
||||
node = &hash_table->nodes [node_index];
|
||||
|
||||
/* Hash table lookup needs to be fast.
|
||||
* We therefore remove the extra conditional of testing
|
||||
* whether to call the key_equal_func or not from
|
||||
* the inner loop.
|
||||
*
|
||||
* Additional optimisation: first check if our full hash
|
||||
* values are equal so we can avoid calling the full-blown
|
||||
* key equality function in most cases.
|
||||
*/
|
||||
if (hash_table->key_equal_func)
|
||||
while (node->key_hash)
|
||||
{
|
||||
while ((node = *node_ptr))
|
||||
{
|
||||
if (node->key_hash == hash_value &&
|
||||
hash_table->key_equal_func (node->key, key))
|
||||
break;
|
||||
/* We first check if our full hash values
|
||||
* are equal so we can avoid calling the full-blown
|
||||
* key equality function in most cases.
|
||||
*/
|
||||
|
||||
node_ptr = &(*node_ptr)->next;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while ((node = *node_ptr))
|
||||
if (node->key_hash == hash_value)
|
||||
{
|
||||
if (node->key == key)
|
||||
break;
|
||||
|
||||
node_ptr = &(*node_ptr)->next;
|
||||
if (hash_table->key_equal_func)
|
||||
{
|
||||
if (hash_table->key_equal_func (node->key, key))
|
||||
break;
|
||||
}
|
||||
else if (node->key == key)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
step++;
|
||||
node_index += step;
|
||||
node_index &= hash_table->mask;
|
||||
node = &hash_table->nodes [node_index];
|
||||
}
|
||||
|
||||
return node_ptr;
|
||||
return node_index;
|
||||
}
|
||||
|
||||
/*
|
||||
* g_hash_table_lookup_node_for_insertion:
|
||||
* @hash_table: our #GHashTable
|
||||
* @key: the key to lookup against
|
||||
* @hash_return: key hash return location
|
||||
* Return value: index of the described #GHashNode
|
||||
*
|
||||
* Performs a lookup in the hash table, preserving extra information
|
||||
* usually needed for insertion.
|
||||
*
|
||||
* This function first computes the hash value of the key using the
|
||||
* user's hash function.
|
||||
*
|
||||
* If an entry in the table matching @key is found then this function
|
||||
* returns the index of that entry in the table, and if not, the
|
||||
* index of an unused node (empty or tombstone) where the key can be
|
||||
* inserted.
|
||||
*
|
||||
* The computed hash value is returned in the variable pointed to
|
||||
* by @hash_return. This is to save insertions from having to compute
|
||||
* the hash record again for the new record.
|
||||
*/
|
||||
static inline guint
|
||||
g_hash_table_lookup_node_for_insertion (GHashTable *hash_table,
|
||||
gconstpointer key,
|
||||
guint *hash_return)
|
||||
{
|
||||
GHashNode *node;
|
||||
guint node_index;
|
||||
guint hash_value;
|
||||
guint first_tombstone;
|
||||
gboolean have_tombstone = FALSE;
|
||||
guint step = 0;
|
||||
|
||||
/* Empty buckets have hash_value set to 0, and for tombstones, it's 1.
|
||||
* We need to make sure our hash value is not one of these. */
|
||||
|
||||
hash_value = (* hash_table->hash_func) (key);
|
||||
if (G_UNLIKELY (hash_value <= 1))
|
||||
hash_value = 2;
|
||||
|
||||
*hash_return = hash_value;
|
||||
|
||||
node_index = hash_value % hash_table->mod;
|
||||
node = &hash_table->nodes [node_index];
|
||||
|
||||
while (node->key_hash)
|
||||
{
|
||||
/* We first check if our full hash values
|
||||
* are equal so we can avoid calling the full-blown
|
||||
* key equality function in most cases.
|
||||
*/
|
||||
|
||||
if (node->key_hash == hash_value)
|
||||
{
|
||||
if (hash_table->key_equal_func)
|
||||
{
|
||||
if (hash_table->key_equal_func (node->key, key))
|
||||
return node_index;
|
||||
}
|
||||
else if (node->key == key)
|
||||
{
|
||||
return node_index;
|
||||
}
|
||||
}
|
||||
else if (node->key_hash == 1 && !have_tombstone)
|
||||
{
|
||||
first_tombstone = node_index;
|
||||
have_tombstone = TRUE;
|
||||
}
|
||||
|
||||
step++;
|
||||
node_index += step;
|
||||
node_index &= hash_table->mask;
|
||||
node = &hash_table->nodes [node_index];
|
||||
}
|
||||
|
||||
if (have_tombstone)
|
||||
return first_tombstone;
|
||||
|
||||
return node_index;
|
||||
}
|
||||
|
||||
/*
|
||||
* g_hash_table_remove_node:
|
||||
* @hash_table: our #GHashTable
|
||||
* @node_ptr_ptr: a pointer to the return value from
|
||||
* g_hash_table_lookup_node()
|
||||
* @node: pointer to node to remove
|
||||
* @notify: %TRUE if the destroy notify handlers are to be called
|
||||
*
|
||||
* Removes a node from the hash table and updates the node count. The
|
||||
* node is freed. No table resize is performed.
|
||||
* Removes a node from the hash table and updates the node count.
|
||||
* The node is replaced by a tombstone. No table resize is performed.
|
||||
*
|
||||
* If @notify is %TRUE then the destroy notify functions are called
|
||||
* for the key and value of the hash node.
|
||||
*
|
||||
* @node_ptr_ptr is a pass-by-reference in/out parameter. When the
|
||||
* function is called, it should point to the pointer to the node to
|
||||
* remove. This level of indirection is required so that the pointer
|
||||
* may be updated appropriately once the node has been removed.
|
||||
*
|
||||
* Before the function returns, the pointer at @node_ptr_ptr will be
|
||||
* updated to point to the position in the table that contains the
|
||||
* pointer to the "next" node in the chain. This makes this function
|
||||
* convenient to use from functions that iterate over the entire
|
||||
* table. If there is no further item in the chain then the
|
||||
* #GHashNode pointer will be %NULL (ie: **node_ptr_ptr == %NULL).
|
||||
*
|
||||
* Since the pointer in the table to the removed node is replaced with
|
||||
* either a pointer to the next node or a %NULL pointer as
|
||||
* appropriate, the pointer at the end of @node_ptr_ptr will never be
|
||||
* modified at all. Stay tuned. :)
|
||||
*/
|
||||
static void
|
||||
g_hash_table_remove_node (GHashTable *hash_table,
|
||||
GHashNode ***node_ptr_ptr,
|
||||
GHashNode *node,
|
||||
gboolean notify)
|
||||
{
|
||||
GHashNode **node_ptr, *node;
|
||||
|
||||
node_ptr = *node_ptr_ptr;
|
||||
node = *node_ptr;
|
||||
|
||||
*node_ptr = node->next;
|
||||
|
||||
if (notify && hash_table->key_destroy_func)
|
||||
hash_table->key_destroy_func (node->key);
|
||||
|
||||
if (notify && hash_table->value_destroy_func)
|
||||
hash_table->value_destroy_func (node->value);
|
||||
|
||||
g_slice_free (GHashNode, node);
|
||||
/* Erect tombstone */
|
||||
node->key_hash = 1;
|
||||
|
||||
/* Be GC friendly */
|
||||
node->key = NULL;
|
||||
node->value = NULL;
|
||||
|
||||
hash_table->nnodes--;
|
||||
}
|
||||
@ -223,14 +359,28 @@ static void
|
||||
g_hash_table_remove_all_nodes (GHashTable *hash_table,
|
||||
gboolean notify)
|
||||
{
|
||||
GHashNode **node_ptr;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < hash_table->size; i++)
|
||||
for (node_ptr = &hash_table->nodes[i]; *node_ptr != NULL;)
|
||||
g_hash_table_remove_node (hash_table, &node_ptr, notify);
|
||||
{
|
||||
GHashNode *node = &hash_table->nodes [i];
|
||||
|
||||
if (node->key_hash > 1)
|
||||
{
|
||||
if (notify && hash_table->key_destroy_func)
|
||||
hash_table->key_destroy_func (node->key);
|
||||
|
||||
if (notify && hash_table->value_destroy_func)
|
||||
hash_table->value_destroy_func (node->value);
|
||||
}
|
||||
}
|
||||
|
||||
/* We need to set node->key_hash = 0 for all nodes - might as well be GC
|
||||
* friendly and clear everything */
|
||||
memset (hash_table->nodes, 0, hash_table->size * sizeof (GHashNode));
|
||||
|
||||
hash_table->nnodes = 0;
|
||||
hash_table->noccupied = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -241,36 +391,50 @@ g_hash_table_remove_all_nodes (GHashTable *hash_table,
|
||||
* nodes currently held. If you call this function then a resize will
|
||||
* occur, even if one does not need to occur. Use
|
||||
* g_hash_table_maybe_resize() instead.
|
||||
*
|
||||
* This function may "resize" the hash table to its current size, with
|
||||
* the side effect of cleaning up tombstones and otherwise optimizing
|
||||
* the probe sequences.
|
||||
*/
|
||||
static void
|
||||
g_hash_table_resize (GHashTable *hash_table)
|
||||
{
|
||||
GHashNode **new_nodes;
|
||||
GHashNode *node;
|
||||
GHashNode *next;
|
||||
guint hash_val;
|
||||
gint new_size;
|
||||
GHashNode *new_nodes;
|
||||
gint old_size;
|
||||
gint i;
|
||||
|
||||
new_size = g_spaced_primes_closest (hash_table->nnodes);
|
||||
new_size = CLAMP (new_size, HASH_TABLE_MIN_SIZE, HASH_TABLE_MAX_SIZE);
|
||||
old_size = hash_table->size;
|
||||
g_hash_table_set_shift_from_size (hash_table, hash_table->nnodes * 2);
|
||||
|
||||
new_nodes = g_new0 (GHashNode*, new_size);
|
||||
new_nodes = g_new0 (GHashNode, hash_table->size);
|
||||
|
||||
for (i = 0; i < hash_table->size; i++)
|
||||
for (node = hash_table->nodes[i]; node; node = next)
|
||||
{
|
||||
next = node->next;
|
||||
for (i = 0; i < old_size; i++)
|
||||
{
|
||||
GHashNode *node = &hash_table->nodes [i];
|
||||
GHashNode *new_node;
|
||||
guint hash_val;
|
||||
guint step = 0;
|
||||
|
||||
hash_val = node->key_hash % new_size;
|
||||
if (node->key_hash <= 1)
|
||||
continue;
|
||||
|
||||
node->next = new_nodes[hash_val];
|
||||
new_nodes[hash_val] = node;
|
||||
}
|
||||
hash_val = node->key_hash % hash_table->mod;
|
||||
new_node = &new_nodes [hash_val];
|
||||
|
||||
while (new_node->key_hash)
|
||||
{
|
||||
step++;
|
||||
hash_val += step;
|
||||
hash_val &= hash_table->mask;
|
||||
new_node = &new_nodes [hash_val];
|
||||
}
|
||||
|
||||
*new_node = *node;
|
||||
}
|
||||
|
||||
g_free (hash_table->nodes);
|
||||
hash_table->nodes = new_nodes;
|
||||
hash_table->size = new_size;
|
||||
hash_table->noccupied = hash_table->nnodes;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -285,11 +449,11 @@ g_hash_table_resize (GHashTable *hash_table)
|
||||
static inline void
|
||||
g_hash_table_maybe_resize (GHashTable *hash_table)
|
||||
{
|
||||
gint nnodes = hash_table->nnodes;
|
||||
gint noccupied = hash_table->noccupied;
|
||||
gint size = hash_table->size;
|
||||
|
||||
if ((size >= 3 * nnodes && size > HASH_TABLE_MIN_SIZE) ||
|
||||
(3 * size <= nnodes && size < HASH_TABLE_MAX_SIZE))
|
||||
if ((size > hash_table->nnodes * 4 && size > 1 << HASH_TABLE_MIN_SHIFT) ||
|
||||
(size <= noccupied + (noccupied / 16)))
|
||||
g_hash_table_resize (hash_table);
|
||||
}
|
||||
|
||||
@ -345,8 +509,9 @@ g_hash_table_new_full (GHashFunc hash_func,
|
||||
GHashTable *hash_table;
|
||||
|
||||
hash_table = g_slice_new (GHashTable);
|
||||
hash_table->size = HASH_TABLE_MIN_SIZE;
|
||||
g_hash_table_set_shift (hash_table, HASH_TABLE_MIN_SHIFT);
|
||||
hash_table->nnodes = 0;
|
||||
hash_table->noccupied = 0;
|
||||
hash_table->hash_func = hash_func ? hash_func : g_direct_hash;
|
||||
hash_table->key_equal_func = key_equal_func;
|
||||
hash_table->ref_count = 1;
|
||||
@ -355,7 +520,7 @@ 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->nodes = g_new0 (GHashNode*, hash_table->size);
|
||||
hash_table->nodes = g_new0 (GHashNode, hash_table->size);
|
||||
|
||||
return hash_table;
|
||||
}
|
||||
@ -391,10 +556,7 @@ g_hash_table_iter_init (GHashTableIter *iter,
|
||||
g_return_if_fail (hash_table != NULL);
|
||||
|
||||
ri->hash_table = hash_table;
|
||||
ri->prev_node = NULL;
|
||||
ri->node = NULL;
|
||||
ri->position = -1;
|
||||
ri->pre_advanced = FALSE;
|
||||
#ifndef G_DISABLE_ASSERT
|
||||
ri->version = hash_table->version;
|
||||
#endif
|
||||
@ -420,43 +582,36 @@ g_hash_table_iter_next (GHashTableIter *iter,
|
||||
gpointer *value)
|
||||
{
|
||||
RealIter *ri = (RealIter *) iter;
|
||||
GHashNode *node;
|
||||
gint position;
|
||||
|
||||
g_return_val_if_fail (iter != NULL, FALSE);
|
||||
#ifndef G_DISABLE_ASSERT
|
||||
g_return_val_if_fail (ri->version == ri->hash_table->version, FALSE);
|
||||
#endif
|
||||
g_return_val_if_fail (ri->position < ri->hash_table->size, FALSE);
|
||||
|
||||
if (ri->pre_advanced)
|
||||
position = ri->position;
|
||||
|
||||
do
|
||||
{
|
||||
ri->pre_advanced = FALSE;
|
||||
position++;
|
||||
if (position >= ri->hash_table->size)
|
||||
{
|
||||
ri->position = position;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (ri->node == NULL)
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ri->node != NULL)
|
||||
{
|
||||
ri->prev_node = ri->node;
|
||||
ri->node = ri->node->next;
|
||||
}
|
||||
|
||||
while (ri->node == NULL)
|
||||
{
|
||||
ri->position++;
|
||||
if (ri->position >= ri->hash_table->size)
|
||||
return FALSE;
|
||||
|
||||
ri->prev_node = NULL;
|
||||
ri->node = ri->hash_table->nodes[ri->position];
|
||||
}
|
||||
node = &ri->hash_table->nodes [position];
|
||||
}
|
||||
while (node->key_hash <= 1);
|
||||
|
||||
if (key != NULL)
|
||||
*key = ri->node->key;
|
||||
*key = node->key;
|
||||
if (value != NULL)
|
||||
*value = ri->node->value;
|
||||
*value = node->value;
|
||||
|
||||
ri->position = position;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -481,55 +636,14 @@ g_hash_table_iter_get_hash_table (GHashTableIter *iter)
|
||||
static void
|
||||
iter_remove_or_steal (RealIter *ri, gboolean notify)
|
||||
{
|
||||
GHashNode *prev;
|
||||
GHashNode *node;
|
||||
int position;
|
||||
|
||||
g_return_if_fail (ri != NULL);
|
||||
#ifndef G_DISABLE_ASSERT
|
||||
g_return_if_fail (ri->version == ri->hash_table->version);
|
||||
#endif
|
||||
g_return_if_fail (ri->node != NULL);
|
||||
g_return_if_fail (ri->position >= 0);
|
||||
g_return_if_fail (ri->position < ri->hash_table->size);
|
||||
|
||||
prev = ri->prev_node;
|
||||
node = ri->node;
|
||||
position = ri->position;
|
||||
|
||||
/* pre-advance the iterator since we will remove the node */
|
||||
|
||||
ri->node = ri->node->next;
|
||||
/* ri->prev_node is still the correct previous node */
|
||||
|
||||
while (ri->node == NULL)
|
||||
{
|
||||
ri->position++;
|
||||
if (ri->position >= ri->hash_table->size)
|
||||
break;
|
||||
|
||||
ri->prev_node = NULL;
|
||||
ri->node = ri->hash_table->nodes[ri->position];
|
||||
}
|
||||
|
||||
ri->pre_advanced = TRUE;
|
||||
|
||||
/* remove the node */
|
||||
|
||||
if (prev != NULL)
|
||||
prev->next = node->next;
|
||||
else
|
||||
ri->hash_table->nodes[position] = node->next;
|
||||
|
||||
if (notify)
|
||||
{
|
||||
if (ri->hash_table->key_destroy_func)
|
||||
ri->hash_table->key_destroy_func(node->key);
|
||||
if (ri->hash_table->value_destroy_func)
|
||||
ri->hash_table->value_destroy_func(node->value);
|
||||
}
|
||||
|
||||
g_slice_free (GHashNode, node);
|
||||
|
||||
ri->hash_table->nnodes--;
|
||||
g_hash_table_remove_node (ri->hash_table, &ri->hash_table->nodes [ri->position], notify);
|
||||
|
||||
#ifndef G_DISABLE_ASSERT
|
||||
ri->version++;
|
||||
@ -662,12 +776,14 @@ g_hash_table_lookup (GHashTable *hash_table,
|
||||
gconstpointer key)
|
||||
{
|
||||
GHashNode *node;
|
||||
guint node_index;
|
||||
|
||||
g_return_val_if_fail (hash_table != NULL, NULL);
|
||||
|
||||
node = *g_hash_table_lookup_node (hash_table, key, NULL);
|
||||
node_index = g_hash_table_lookup_node (hash_table, key);
|
||||
node = &hash_table->nodes [node_index];
|
||||
|
||||
return node ? node->value : NULL;
|
||||
return node->key_hash ? node->value : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -691,12 +807,14 @@ g_hash_table_lookup_extended (GHashTable *hash_table,
|
||||
gpointer *value)
|
||||
{
|
||||
GHashNode *node;
|
||||
guint node_index;
|
||||
|
||||
g_return_val_if_fail (hash_table != NULL, FALSE);
|
||||
|
||||
node = *g_hash_table_lookup_node (hash_table, lookup_key, NULL);
|
||||
node_index = g_hash_table_lookup_node (hash_table, lookup_key);
|
||||
node = &hash_table->nodes [node_index];
|
||||
|
||||
if (node == NULL)
|
||||
if (!node->key_hash)
|
||||
return FALSE;
|
||||
|
||||
if (orig_key)
|
||||
@ -730,15 +848,20 @@ g_hash_table_insert_internal (GHashTable *hash_table,
|
||||
gpointer value,
|
||||
gboolean keep_new_key)
|
||||
{
|
||||
GHashNode **node_ptr, *node;
|
||||
GHashNode *node;
|
||||
guint node_index;
|
||||
guint key_hash;
|
||||
guint old_hash;
|
||||
|
||||
g_return_if_fail (hash_table != NULL);
|
||||
g_return_if_fail (hash_table->ref_count > 0);
|
||||
|
||||
node_ptr = g_hash_table_lookup_node (hash_table, key, &key_hash);
|
||||
node_index = g_hash_table_lookup_node_for_insertion (hash_table, key, &key_hash);
|
||||
node = &hash_table->nodes [node_index];
|
||||
|
||||
if ((node = *node_ptr))
|
||||
old_hash = node->key_hash;
|
||||
|
||||
if (old_hash > 1)
|
||||
{
|
||||
if (keep_new_key)
|
||||
{
|
||||
@ -759,16 +882,18 @@ g_hash_table_insert_internal (GHashTable *hash_table,
|
||||
}
|
||||
else
|
||||
{
|
||||
node = g_slice_new (GHashNode);
|
||||
|
||||
node->key = key;
|
||||
node->value = value;
|
||||
node->key_hash = key_hash;
|
||||
node->next = NULL;
|
||||
|
||||
*node_ptr = node;
|
||||
hash_table->nnodes++;
|
||||
g_hash_table_maybe_resize (hash_table);
|
||||
|
||||
if (old_hash == 0)
|
||||
{
|
||||
/* We replaced an empty node, and not a tombstone */
|
||||
hash_table->noccupied++;
|
||||
g_hash_table_maybe_resize (hash_table);
|
||||
}
|
||||
|
||||
#ifndef G_DISABLE_ASSERT
|
||||
hash_table->version++;
|
||||
@ -837,15 +962,19 @@ g_hash_table_remove_internal (GHashTable *hash_table,
|
||||
gconstpointer key,
|
||||
gboolean notify)
|
||||
{
|
||||
GHashNode **node_ptr;
|
||||
GHashNode *node;
|
||||
guint node_index;
|
||||
|
||||
g_return_val_if_fail (hash_table != NULL, FALSE);
|
||||
|
||||
node_ptr = g_hash_table_lookup_node (hash_table, key, NULL);
|
||||
if (*node_ptr == NULL)
|
||||
node_index = g_hash_table_lookup_node (hash_table, key);
|
||||
node = &hash_table->nodes [node_index];
|
||||
|
||||
/* g_hash_table_lookup_node() never returns a tombstone, so this is safe */
|
||||
if (!node->key_hash)
|
||||
return FALSE;
|
||||
|
||||
g_hash_table_remove_node (hash_table, &node_ptr, notify);
|
||||
g_hash_table_remove_node (hash_table, node, notify);
|
||||
g_hash_table_maybe_resize (hash_table);
|
||||
|
||||
#ifndef G_DISABLE_ASSERT
|
||||
@ -966,19 +1095,19 @@ g_hash_table_foreach_remove_or_steal (GHashTable *hash_table,
|
||||
gpointer user_data,
|
||||
gboolean notify)
|
||||
{
|
||||
GHashNode *node, **node_ptr;
|
||||
guint deleted = 0;
|
||||
gint i;
|
||||
|
||||
for (i = 0; i < hash_table->size; i++)
|
||||
for (node_ptr = &hash_table->nodes[i]; (node = *node_ptr) != NULL;)
|
||||
if ((* func) (node->key, node->value, user_data))
|
||||
{
|
||||
GHashNode *node = &hash_table->nodes [i];
|
||||
|
||||
if (node->key_hash > 1 && (* func) (node->key, node->value, user_data))
|
||||
{
|
||||
g_hash_table_remove_node (hash_table, &node_ptr, notify);
|
||||
g_hash_table_remove_node (hash_table, node, notify);
|
||||
deleted++;
|
||||
}
|
||||
else
|
||||
node_ptr = &node->next;
|
||||
}
|
||||
|
||||
g_hash_table_maybe_resize (hash_table);
|
||||
|
||||
@ -1065,15 +1194,18 @@ g_hash_table_foreach (GHashTable *hash_table,
|
||||
GHFunc func,
|
||||
gpointer user_data)
|
||||
{
|
||||
GHashNode *node;
|
||||
gint i;
|
||||
|
||||
g_return_if_fail (hash_table != NULL);
|
||||
g_return_if_fail (func != NULL);
|
||||
|
||||
for (i = 0; i < hash_table->size; i++)
|
||||
for (node = hash_table->nodes[i]; node; node = node->next)
|
||||
(* func) (node->key, node->value, user_data);
|
||||
{
|
||||
GHashNode *node = &hash_table->nodes [i];
|
||||
|
||||
if (node->key_hash > 1)
|
||||
(* func) (node->key, node->value, user_data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1107,16 +1239,19 @@ g_hash_table_find (GHashTable *hash_table,
|
||||
GHRFunc predicate,
|
||||
gpointer user_data)
|
||||
{
|
||||
GHashNode *node;
|
||||
gint i;
|
||||
|
||||
g_return_val_if_fail (hash_table != NULL, NULL);
|
||||
g_return_val_if_fail (predicate != NULL, NULL);
|
||||
|
||||
for (i = 0; i < hash_table->size; i++)
|
||||
for (node = hash_table->nodes[i]; node; node = node->next)
|
||||
if (predicate (node->key, node->value, user_data))
|
||||
{
|
||||
GHashNode *node = &hash_table->nodes [i];
|
||||
|
||||
if (node->key_hash > 1 && predicate (node->key, node->value, user_data))
|
||||
return node->value;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -1153,7 +1288,6 @@ g_hash_table_size (GHashTable *hash_table)
|
||||
GList *
|
||||
g_hash_table_get_keys (GHashTable *hash_table)
|
||||
{
|
||||
GHashNode *node;
|
||||
gint i;
|
||||
GList *retval;
|
||||
|
||||
@ -1161,8 +1295,12 @@ g_hash_table_get_keys (GHashTable *hash_table)
|
||||
|
||||
retval = NULL;
|
||||
for (i = 0; i < hash_table->size; i++)
|
||||
for (node = hash_table->nodes[i]; node; node = node->next)
|
||||
retval = g_list_prepend (retval, node->key);
|
||||
{
|
||||
GHashNode *node = &hash_table->nodes [i];
|
||||
|
||||
if (node->key_hash > 1)
|
||||
retval = g_list_prepend (retval, node->key);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
@ -1184,7 +1322,6 @@ g_hash_table_get_keys (GHashTable *hash_table)
|
||||
GList *
|
||||
g_hash_table_get_values (GHashTable *hash_table)
|
||||
{
|
||||
GHashNode *node;
|
||||
gint i;
|
||||
GList *retval;
|
||||
|
||||
@ -1192,8 +1329,12 @@ g_hash_table_get_values (GHashTable *hash_table)
|
||||
|
||||
retval = NULL;
|
||||
for (i = 0; i < hash_table->size; i++)
|
||||
for (node = hash_table->nodes[i]; node; node = node->next)
|
||||
retval = g_list_prepend (retval, node->value);
|
||||
{
|
||||
GHashNode *node = &hash_table->nodes [i];
|
||||
|
||||
if (node->key_hash > 1)
|
||||
retval = g_list_prepend (retval, node->value);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user