From a2da2962302b652d2bd771d4525e6f4793ea42d1 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 26 Feb 2025 20:06:41 +0100 Subject: [PATCH] gobject: optimize notify-queue handling for a single freeze When we set a property we usually tend to freeze the queue notification and thaw it at the end. This always requires a per-object allocation that is necessary to track the freeze count and frozen properties. But there are cases cases, where we freeze only a single time and never add a property to unfreeze. In such cases, we can avoid allocating a new GObjectNotifyQueue instance. Optimize for that case by initially adding a global, immutable sentinel pointer "notify_queue_empty". Only when requiring a per-object queue, allocate one. This can be useful before calling dispose(). While there are probably dispose functions that still try to set properties on the object (which is the main reason to freeze the notification), most probably don't. In this case, we can avoid allocating the memory during g_object_unref(). Another such case is object construction. If the object has no construct properties and the user didn't specify any properties during g_object_new(), we may well freeze the object but never add properties to it. In that case too, we can get away without ever allocating the GObjectNotifyQueue. --- gobject/gobject.c | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/gobject/gobject.c b/gobject/gobject.c index 1c1595147..aaedddcb9 100644 --- a/gobject/gobject.c +++ b/gobject/gobject.c @@ -657,6 +657,24 @@ object_bit_unlock (GObject *object, guint lock_bit) /* --- functions --- */ +static const GObjectNotifyQueue notify_queue_empty = { + .freeze_count = 0, +}; + +G_ALWAYS_INLINE static inline gboolean +_is_notify_queue_empty (const GObjectNotifyQueue *nqueue) +{ + /* Only the notify_queue_empty as a zero freeze count. We check here for that + * condition instead of pointer comparing to ¬ify_queue_empty. That seems + * useful, because callers will afterwards anyway dereferenciate + * "freeze_count" from memory. + */ +#if G_ENABLE_DEBUG + g_assert ((nqueue == ¬ify_queue_empty) == (nqueue->freeze_count == 0)); +#endif + return nqueue->freeze_count == 0; +} + G_ALWAYS_INLINE static inline gsize g_object_notify_queue_alloc_size (gsize alloc) { @@ -688,9 +706,9 @@ g_object_notify_queue_freeze_cb (gpointer *data, if (!nqueue) { - /* The nqueue doesn't exist yet. We create it, and freeze thus 1 time. */ - *data = g_object_notify_queue_new_frozen (); - *destroy_notify = g_free; + /* The nqueue doesn't exist yet. We use the dummy object that is shared + * by all instances. */ + *data = (gpointer) ¬ify_queue_empty; } else if (!freeze_always) { @@ -701,7 +719,14 @@ g_object_notify_queue_freeze_cb (gpointer *data, } else { - if (G_UNLIKELY (nqueue->freeze_count == G_MAXUINT16)) + if (_is_notify_queue_empty (nqueue)) + { + nqueue = g_object_notify_queue_new_frozen (); + *data = nqueue; + *destroy_notify = g_free; + nqueue->freeze_count++; + } + else if (G_UNLIKELY (nqueue->freeze_count == G_MAXUINT16)) { g_critical ("Free queue for %s (%p) is larger than 65535," " called g_object_freeze_notify() too often." @@ -732,13 +757,19 @@ g_object_notify_queue_thaw_cb (gpointer *data, GObject *object = user_data; GObjectNotifyQueue *nqueue = *data; - if (G_UNLIKELY (!nqueue || nqueue->freeze_count == 0)) + if (G_UNLIKELY (!nqueue)) { g_critical ("%s: property-changed notification for %s(%p) is not frozen", G_STRFUNC, G_OBJECT_TYPE_NAME (object), object); return NULL; } + if (_is_notify_queue_empty (nqueue)) + { + *data = NULL; + return NULL; + } + nqueue->freeze_count--; if (nqueue->freeze_count > 0) @@ -820,6 +851,12 @@ g_object_notify_queue_add_cb (gpointer *data, *data = nqueue; *destroy_notify = g_free; } + else if (_is_notify_queue_empty (nqueue)) + { + nqueue = g_object_notify_queue_new_frozen (); + *data = nqueue; + *destroy_notify = g_free; + } else { for (i = 0; i < nqueue->len; i++)