summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Withnall <philip@tecnocode.co.uk>2021-09-21 10:40:48 +0000
committerPhilip Withnall <philip@tecnocode.co.uk>2021-09-21 10:40:48 +0000
commitbbd1350beb2c261dba4b93b3740f46e287bf98f7 (patch)
tree0489612c037644a63513a813695897c214338740
parent6483808df691544397dd0d8e2fe9a793367bc317 (diff)
parent59e5612339785938fe137bed17d84c0f2f3455bb (diff)
downloadglib-bbd1350beb2c261dba4b93b3740f46e287bf98f7.tar.gz
Merge branch '#0434_GSequenceSlowsDown_counter' into 'main'
gsequence: make treap priorities more random to avoid worst-case scenarios Closes #2468 See merge request GNOME/glib!2236
-rw-r--r--glib/gsequence.c54
-rw-r--r--glib/tests/sequence.c13
2 files changed, 54 insertions, 13 deletions
diff --git a/glib/gsequence.c b/glib/gsequence.c
index 9d76dbb22..2b443fe5b 100644
--- a/glib/gsequence.c
+++ b/glib/gsequence.c
@@ -120,6 +120,7 @@ struct _GSequence
struct _GSequenceNode
{
gint n_nodes;
+ guint32 priority;
GSequenceNode * parent;
GSequenceNode * left;
GSequenceNode * right;
@@ -1572,11 +1573,9 @@ g_sequence_swap (GSequenceIter *a,
*
*
*/
-static guint
-get_priority (GSequenceNode *node)
+static guint32
+hash_uint32 (guint32 key)
{
- guint key = GPOINTER_TO_UINT (node);
-
/* This hash function is based on one found on Thomas Wang's
* web page at
*
@@ -1590,6 +1589,20 @@ get_priority (GSequenceNode *node)
key = key + (key << 3) + (key << 11);
key = key ^ (key >> 16);
+ return key;
+}
+
+static inline guint
+get_priority (GSequenceNode *node)
+{
+ return node->priority;
+}
+
+static guint
+make_priority (guint32 key)
+{
+ key = hash_uint32 (key);
+
/* We rely on 0 being less than all other priorities */
return key? key : 1;
}
@@ -1608,7 +1621,40 @@ node_new (gpointer data)
{
GSequenceNode *node = g_slice_new0 (GSequenceNode);
+ /*
+ * Make a random number quickly. Some binary magic is used to avoid
+ * the costs of proper RNG, such as locking around global GRand.
+ *
+ * Using just the node pointer alone is not enough, because in this
+ * case freeing and re-allocating sequence causes node's priorities
+ * to no longer be random. This happens for two reasons:
+ * 1) Nodes are freed from the root and the treap's property is that
+ * node's priority is >= than its children's priorities.
+ * 2) g_slice_new0() will reuse freed nodes in the order similar to
+ * the order of freeing.
+ * As a result, there are severe problems where building the treap is
+ * much slower (100x and more after a few sequence new/free
+ * iterations) and treap becomes more like a list (tree height
+ * approaches tree's number of elements), which increases costs of
+ * using the built treap.
+ *
+ * Note that for performance reasons, counter completely ignores
+ * multi-threading issues. This is fine because it's merely a source
+ * of additional randomness. Even if it fails to ++ sometimes, this
+ * won't really matter for its goal.
+ *
+ * Note that 64-bit counter is used to avoid undefined behavior on
+ * overflow.
+ *
+ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2468
+ */
+ static guint64 counter = 0;
+ guint32 hash_key = (guint32) GPOINTER_TO_UINT (node);
+ hash_key ^= (guint32) counter;
+ counter++;
+
node->n_nodes = 1;
+ node->priority = make_priority (hash_key);
node->data = data;
node->left = NULL;
node->right = NULL;
diff --git a/glib/tests/sequence.c b/glib/tests/sequence.c
index c2eba2c79..b52ca38fb 100644
--- a/glib/tests/sequence.c
+++ b/glib/tests/sequence.c
@@ -15,7 +15,8 @@ struct _GSequence
struct _GSequenceNode
{
- guint n_nodes;
+ gint n_nodes;
+ guint32 priority;
GSequenceNode * parent;
GSequenceNode * left;
GSequenceNode * right;
@@ -25,15 +26,9 @@ struct _GSequenceNode
static guint
get_priority (GSequenceNode *node)
{
- guint key = GPOINTER_TO_UINT (node);
-
- key = (key << 15) - key - 1;
- key = key ^ (key >> 12);
- key = key + (key << 2);
- key = key ^ (key >> 4);
- key = key + (key << 3) + (key << 11);
- key = key ^ (key >> 16);
+ guint key = node->priority;
+ /* We rely on 0 being less than all other priorities */
return key? key : 1;
}