mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-10 03:16:17 +01:00
Optimize single-handler va_marshaller case
When there is only one closure handling a signal emission and it doesn't have a bunch of complicated features enabled we can short circuit the va_args collection into GValues and call the callback via the va_marshaller directly. https://bugzilla.gnome.org/show_bug.cgi?id=661140
This commit is contained in:
parent
a3e91088ce
commit
f02ec2f2de
@ -106,7 +106,9 @@
|
||||
|
||||
#define REPORT_BUG "please report occurrence circumstances to gtk-devel-list@gnome.org"
|
||||
#ifdef G_ENABLE_DEBUG
|
||||
#define IF_DEBUG(debug_type, cond) if ((_g_type_debug_flags & G_TYPE_DEBUG_ ## debug_type) || cond)
|
||||
#define COND_DEBUG(debug_type, cond) ((_g_type_debug_flags & G_TYPE_DEBUG_ ## debug_type) || (cond))
|
||||
#define IF_DEBUG(debug_type, cond) if (COND_DEBUG(debug_type, cond))
|
||||
|
||||
static volatile gpointer g_trace_instance_signals = NULL;
|
||||
static volatile gpointer g_trap_instance_signals = NULL;
|
||||
#endif /* G_ENABLE_DEBUG */
|
||||
@ -181,6 +183,7 @@ static gboolean signal_emit_unlocked_R (SignalNode *node,
|
||||
const GValue *instance_and_params);
|
||||
static const gchar * type_debug_name (GType type);
|
||||
static void node_check_deprecated (const SignalNode *node);
|
||||
static void node_update_single_va_closure (SignalNode *node);
|
||||
|
||||
|
||||
/* --- structures --- */
|
||||
@ -205,9 +208,10 @@ struct _SignalNode
|
||||
guint destroyed : 1;
|
||||
|
||||
/* reinitializable portion */
|
||||
guint test_class_offset : 12;
|
||||
guint flags : 9;
|
||||
guint n_params : 8;
|
||||
guint single_va_closure_is_valid : 1;
|
||||
guint single_va_closure_is_after : 1;
|
||||
GType *param_types; /* mangled with G_SIGNAL_TYPE_STATIC_SCOPE flag */
|
||||
GType return_type; /* mangled with G_SIGNAL_TYPE_STATIC_SCOPE flag */
|
||||
GBSearchArray *class_closure_bsa;
|
||||
@ -215,9 +219,11 @@ struct _SignalNode
|
||||
GSignalCMarshaller c_marshaller;
|
||||
GSignalCVaMarshaller va_marshaller;
|
||||
GHookList *emission_hooks;
|
||||
|
||||
GClosure *single_va_closure;
|
||||
};
|
||||
#define MAX_TEST_CLASS_OFFSET (4096) /* 2^12, 12 bits for test_class_offset */
|
||||
#define TEST_CLASS_MAGIC (1) /* indicates NULL class closure, candidate for NOP optimization */
|
||||
|
||||
#define SINGLE_VA_CLOSURE_EMPTY_MAGIC GINT_TO_POINTER(1) /* indicates single_va_closure is valid but empty */
|
||||
|
||||
struct _SignalKey
|
||||
{
|
||||
@ -687,6 +693,47 @@ handler_insert (guint signal_id,
|
||||
hlist->tail_after = handler;
|
||||
}
|
||||
|
||||
static void
|
||||
node_update_single_va_closure (SignalNode *node)
|
||||
{
|
||||
GClosure *closure = NULL;
|
||||
gboolean is_after = FALSE;
|
||||
|
||||
/* Fast path single-handler without boxing the arguments in GValues */
|
||||
if (G_TYPE_IS_OBJECT (node->itype) &&
|
||||
(node->flags & (G_SIGNAL_NO_RECURSE|G_SIGNAL_MUST_COLLECT)) == 0 &&
|
||||
(node->emission_hooks == NULL || node->emission_hooks->hooks == NULL))
|
||||
{
|
||||
GSignalFlags run_type;
|
||||
ClassClosure * cc;
|
||||
GBSearchArray *bsa = node->class_closure_bsa;
|
||||
|
||||
if (bsa == NULL || bsa->n_nodes == 0)
|
||||
closure = SINGLE_VA_CLOSURE_EMPTY_MAGIC;
|
||||
else if (bsa->n_nodes == 1)
|
||||
{
|
||||
/* Look for default class closure (can't support non-default as it
|
||||
chains up using GValues */
|
||||
cc = g_bsearch_array_get_nth (bsa, &g_class_closure_bconfig, 0);
|
||||
if (cc->instance_type == 0)
|
||||
{
|
||||
run_type = node->flags & (G_SIGNAL_RUN_FIRST|G_SIGNAL_RUN_LAST|G_SIGNAL_RUN_CLEANUP);
|
||||
/* Only support *one* of run-first or run-last, not multiple or cleanup */
|
||||
if (run_type == G_SIGNAL_RUN_FIRST ||
|
||||
run_type == G_SIGNAL_RUN_LAST)
|
||||
{
|
||||
closure = cc->closure;
|
||||
is_after = (run_type == G_SIGNAL_RUN_LAST);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node->single_va_closure_is_valid = TRUE;
|
||||
node->single_va_closure = closure;
|
||||
node->single_va_closure_is_after = is_after;
|
||||
}
|
||||
|
||||
static inline void
|
||||
emission_push (Emission **emission_list_p,
|
||||
Emission *emission)
|
||||
@ -943,6 +990,7 @@ g_signal_add_emission_hook (guint signal_id,
|
||||
node->emission_hooks->seq_id = seq_hook_id;
|
||||
g_hook_append (node->emission_hooks, hook);
|
||||
seq_hook_id = node->emission_hooks->seq_id;
|
||||
|
||||
SIGNAL_UNLOCK ();
|
||||
|
||||
return hook->hook_id;
|
||||
@ -971,6 +1019,9 @@ g_signal_remove_emission_hook (guint signal_id,
|
||||
g_warning ("%s: invalid signal id `%u'", G_STRLOC, signal_id);
|
||||
else if (!node->emission_hooks || !g_hook_destroy (node->emission_hooks, hook_id))
|
||||
g_warning ("%s: signal \"%s\" had no hook (%lu) to remove", G_STRLOC, node->name, hook_id);
|
||||
|
||||
node->single_va_closure_is_valid = FALSE;
|
||||
|
||||
SIGNAL_UNLOCK ();
|
||||
}
|
||||
|
||||
@ -1346,19 +1397,6 @@ g_signal_new (const gchar *signal_name,
|
||||
|
||||
va_end (args);
|
||||
|
||||
/* optimize NOP emissions with NULL class handlers */
|
||||
if (signal_id && G_TYPE_IS_INSTANTIATABLE (itype) && return_type == G_TYPE_NONE &&
|
||||
class_offset && class_offset < MAX_TEST_CLASS_OFFSET &&
|
||||
~signal_flags & G_SIGNAL_MUST_COLLECT)
|
||||
{
|
||||
SignalNode *node;
|
||||
|
||||
SIGNAL_LOCK ();
|
||||
node = LOOKUP_SIGNAL_NODE (signal_id);
|
||||
node->test_class_offset = class_offset;
|
||||
SIGNAL_UNLOCK ();
|
||||
}
|
||||
|
||||
return signal_id;
|
||||
}
|
||||
|
||||
@ -1481,8 +1519,7 @@ signal_add_class_closure (SignalNode *node,
|
||||
{
|
||||
ClassClosure key;
|
||||
|
||||
/* can't optimize NOP emissions with overridden class closures */
|
||||
node->test_class_offset = 0;
|
||||
node->single_va_closure_is_valid = FALSE;
|
||||
|
||||
if (!node->class_closure_bsa)
|
||||
node->class_closure_bsa = g_bsearch_array_create (&g_class_closure_bconfig);
|
||||
@ -1636,9 +1673,9 @@ g_signal_newv (const gchar *signal_name,
|
||||
TRACE(GOBJECT_SIGNAL_NEW(signal_id, name, itype));
|
||||
}
|
||||
node->destroyed = FALSE;
|
||||
node->test_class_offset = 0;
|
||||
|
||||
/* setup reinitializable portion */
|
||||
node->single_va_closure_is_valid = FALSE;
|
||||
node->flags = signal_flags & G_SIGNAL_FLAGS_MASK;
|
||||
node->n_params = n_params;
|
||||
node->param_types = g_memdup (param_types, sizeof (GType) * n_params);
|
||||
@ -1708,13 +1745,7 @@ g_signal_newv (const gchar *signal_name,
|
||||
node->emission_hooks = NULL;
|
||||
if (class_closure)
|
||||
signal_add_class_closure (node, 0, class_closure);
|
||||
else if (G_TYPE_IS_INSTANTIATABLE (itype) &&
|
||||
return_type == G_TYPE_NONE &&
|
||||
~signal_flags & G_SIGNAL_MUST_COLLECT)
|
||||
{
|
||||
/* optimize NOP emissions */
|
||||
node->test_class_offset = TEST_CLASS_MAGIC;
|
||||
}
|
||||
|
||||
SIGNAL_UNLOCK ();
|
||||
|
||||
g_free (name);
|
||||
@ -1744,6 +1775,9 @@ g_signal_set_va_marshaller (guint signal_id,
|
||||
_g_closure_set_va_marshal (cc->closure, va_marshaller);
|
||||
}
|
||||
}
|
||||
|
||||
node->single_va_closure_is_valid = FALSE;
|
||||
|
||||
SIGNAL_UNLOCK ();
|
||||
}
|
||||
|
||||
@ -1817,7 +1851,7 @@ signal_destroy_R (SignalNode *signal_node)
|
||||
signal_node->destroyed = TRUE;
|
||||
|
||||
/* reentrancy caution, zero out real contents first */
|
||||
signal_node->test_class_offset = 0;
|
||||
signal_node->single_va_closure_is_valid = FALSE;
|
||||
signal_node->n_params = 0;
|
||||
signal_node->param_types = NULL;
|
||||
signal_node->return_type = 0;
|
||||
@ -2887,50 +2921,6 @@ g_signal_has_handler_pending (gpointer instance,
|
||||
return has_pending;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
signal_check_skip_emission (SignalNode *node,
|
||||
gpointer instance,
|
||||
GQuark detail)
|
||||
{
|
||||
HandlerList *hlist;
|
||||
|
||||
/* are we able to check for NULL class handlers? */
|
||||
if (!node->test_class_offset)
|
||||
return FALSE;
|
||||
|
||||
/* are there emission hooks pending? */
|
||||
if (node->emission_hooks && node->emission_hooks->hooks)
|
||||
return FALSE;
|
||||
|
||||
/* is there a non-NULL class handler? */
|
||||
if (node->test_class_offset != TEST_CLASS_MAGIC)
|
||||
{
|
||||
GTypeClass *class = G_TYPE_INSTANCE_GET_CLASS (instance, G_TYPE_FROM_INSTANCE (instance), GTypeClass);
|
||||
|
||||
if (G_STRUCT_MEMBER (gpointer, class, node->test_class_offset))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* are signals being debugged? */
|
||||
#ifdef G_ENABLE_DEBUG
|
||||
IF_DEBUG (SIGNALS, g_trace_instance_signals || g_trap_instance_signals)
|
||||
return FALSE;
|
||||
#endif /* G_ENABLE_DEBUG */
|
||||
|
||||
/* is this a no-recurse signal already in emission? */
|
||||
if (node->flags & G_SIGNAL_NO_RECURSE &&
|
||||
emission_find (g_restart_emissions, node->signal_id, detail, instance))
|
||||
return FALSE;
|
||||
|
||||
/* do we have pending handlers? */
|
||||
hlist = handler_list_lookup (node->signal_id, instance);
|
||||
if (hlist && hlist->handlers)
|
||||
return FALSE;
|
||||
|
||||
/* none of the above, no emission required */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_signal_emitv:
|
||||
* @instance_and_params: (array): argument list for the signal emission.
|
||||
@ -3021,18 +3011,49 @@ g_signal_emitv (const GValue *instance_and_params,
|
||||
#endif /* G_ENABLE_DEBUG */
|
||||
|
||||
/* optimize NOP emissions */
|
||||
if (signal_check_skip_emission (node, instance, detail))
|
||||
if (!node->single_va_closure_is_valid)
|
||||
node_update_single_va_closure (node);
|
||||
|
||||
if (node->single_va_closure != NULL &&
|
||||
(node->single_va_closure == SINGLE_VA_CLOSURE_EMPTY_MAGIC ||
|
||||
_g_closure_is_void (node->single_va_closure, instance))
|
||||
#ifdef G_ENABLE_DEBUG
|
||||
&& !COND_DEBUG (SIGNALS, g_trace_instance_signals != instance &&
|
||||
g_trap_instance_signals == instance)
|
||||
#endif /* G_ENABLE_DEBUG */
|
||||
)
|
||||
{
|
||||
/* nothing to do to emit this signal */
|
||||
SIGNAL_UNLOCK ();
|
||||
/* g_printerr ("omitting emission of \"%s\"\n", node->name); */
|
||||
return;
|
||||
HandlerList* hlist = handler_list_lookup (node->signal_id, instance);
|
||||
if (hlist == NULL || hlist->handlers == NULL)
|
||||
{
|
||||
/* nothing to do to emit this signal */
|
||||
SIGNAL_UNLOCK ();
|
||||
/* g_printerr ("omitting emission of \"%s\"\n", node->name); */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SIGNAL_UNLOCK ();
|
||||
signal_emit_unlocked_R (node, detail, instance, return_value, instance_and_params);
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
accumulate (GSignalInvocationHint *ihint,
|
||||
GValue *return_accu,
|
||||
GValue *handler_return,
|
||||
SignalAccumulator *accumulator)
|
||||
{
|
||||
gboolean continue_emission;
|
||||
|
||||
if (!accumulator)
|
||||
return TRUE;
|
||||
|
||||
continue_emission = accumulator->func (ihint, return_accu, handler_return, accumulator->data);
|
||||
g_value_reset (handler_return);
|
||||
|
||||
return continue_emission;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_signal_emit_valist:
|
||||
* @instance: the instance the signal is being emitted on.
|
||||
@ -3079,13 +3100,153 @@ g_signal_emit_valist (gpointer instance,
|
||||
}
|
||||
#endif /* !G_DISABLE_CHECKS */
|
||||
|
||||
/* optimize NOP emissions */
|
||||
if (signal_check_skip_emission (node, instance, detail))
|
||||
if (!node->single_va_closure_is_valid)
|
||||
node_update_single_va_closure (node);
|
||||
|
||||
if (node->single_va_closure != NULL
|
||||
#ifdef G_ENABLE_DEBUG
|
||||
&& !COND_DEBUG (SIGNALS, g_trace_instance_signals != instance &&
|
||||
g_trap_instance_signals == instance)
|
||||
#endif /* G_ENABLE_DEBUG */
|
||||
)
|
||||
{
|
||||
/* nothing to do to emit this signal */
|
||||
SIGNAL_UNLOCK ();
|
||||
/* g_printerr ("omitting emission of \"%s\"\n", node->name); */
|
||||
return;
|
||||
HandlerList* hlist = handler_list_lookup (node->signal_id, instance);
|
||||
Handler *l;
|
||||
GClosure *closure = NULL;
|
||||
gboolean fastpath = TRUE;
|
||||
GSignalFlags run_type = G_SIGNAL_RUN_FIRST;
|
||||
|
||||
if (node->single_va_closure != SINGLE_VA_CLOSURE_EMPTY_MAGIC &&
|
||||
!_g_closure_is_void (node->single_va_closure, instance))
|
||||
{
|
||||
if (_g_closure_supports_invoke_va (node->single_va_closure))
|
||||
{
|
||||
closure = node->single_va_closure;
|
||||
if (node->single_va_closure_is_after)
|
||||
run_type = G_SIGNAL_RUN_LAST;
|
||||
else
|
||||
run_type = G_SIGNAL_RUN_FIRST;
|
||||
}
|
||||
else
|
||||
fastpath = FALSE;
|
||||
}
|
||||
|
||||
for (l = hlist ? hlist->handlers : NULL; fastpath && l != NULL; l = l->next)
|
||||
{
|
||||
if (!l->block_count &&
|
||||
(!l->detail || l->detail == detail))
|
||||
{
|
||||
if (closure != NULL || !_g_closure_supports_invoke_va (l->closure))
|
||||
{
|
||||
fastpath = FALSE;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
closure = l->closure;
|
||||
if (l->after)
|
||||
run_type = G_SIGNAL_RUN_LAST;
|
||||
else
|
||||
run_type = G_SIGNAL_RUN_FIRST;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fastpath && closure == NULL && node->return_type == G_TYPE_NONE)
|
||||
{
|
||||
SIGNAL_UNLOCK ();
|
||||
return;
|
||||
}
|
||||
|
||||
if (fastpath)
|
||||
{
|
||||
SignalAccumulator *accumulator;
|
||||
Emission emission;
|
||||
GValue *return_accu, accu = G_VALUE_INIT;
|
||||
guint signal_id;
|
||||
GValue emission_return = G_VALUE_INIT;
|
||||
GType rtype = node->return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE;
|
||||
gboolean static_scope = node->return_type & G_SIGNAL_TYPE_STATIC_SCOPE;
|
||||
|
||||
signal_id = node->signal_id;
|
||||
accumulator = node->accumulator;
|
||||
if (rtype == G_TYPE_NONE)
|
||||
return_accu = NULL;
|
||||
else if (accumulator)
|
||||
return_accu = &accu;
|
||||
else
|
||||
return_accu = &emission_return;
|
||||
|
||||
emission.instance = instance;
|
||||
emission.ihint.signal_id = node->signal_id;
|
||||
emission.ihint.detail = detail;
|
||||
emission.ihint.run_type = run_type;
|
||||
emission.state = EMISSION_RUN;
|
||||
emission.chain_type = G_TYPE_FROM_INSTANCE (instance);
|
||||
emission_push (&g_recursive_emissions, &emission);
|
||||
|
||||
SIGNAL_UNLOCK ();
|
||||
|
||||
TRACE(GOBJECT_SIGNAL_EMIT(signal_id, detail, instance, G_TYPE_FROM_INSTANCE (instance)));
|
||||
|
||||
if (rtype != G_TYPE_NONE)
|
||||
g_value_init (&emission_return, rtype);
|
||||
|
||||
if (accumulator)
|
||||
g_value_init (&accu, rtype);
|
||||
|
||||
if (closure != NULL)
|
||||
{
|
||||
g_object_ref (instance);
|
||||
_g_closure_invoke_va (closure,
|
||||
return_accu,
|
||||
instance,
|
||||
var_args,
|
||||
node->n_params,
|
||||
node->param_types);
|
||||
accumulate (&emission.ihint, &emission_return, &accu, accumulator);
|
||||
g_object_unref (instance);
|
||||
}
|
||||
|
||||
SIGNAL_LOCK ();
|
||||
|
||||
emission.chain_type = G_TYPE_NONE;
|
||||
emission_pop (&g_recursive_emissions, &emission);
|
||||
|
||||
SIGNAL_UNLOCK ();
|
||||
|
||||
if (accumulator)
|
||||
g_value_unset (&accu);
|
||||
|
||||
if (rtype != G_TYPE_NONE)
|
||||
{
|
||||
gchar *error = NULL;
|
||||
for (i = 0; i < node->n_params; i++)
|
||||
{
|
||||
GType ptype = node->param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE;
|
||||
G_VALUE_COLLECT_SKIP (ptype, var_args);
|
||||
}
|
||||
|
||||
G_VALUE_LCOPY (&emission_return,
|
||||
var_args,
|
||||
static_scope ? G_VALUE_NOCOPY_CONTENTS : 0,
|
||||
&error);
|
||||
if (!error)
|
||||
g_value_unset (&emission_return);
|
||||
else
|
||||
{
|
||||
g_warning ("%s: %s", G_STRLOC, error);
|
||||
g_free (error);
|
||||
/* we purposely leak the value here, it might not be
|
||||
* in a sane state if an error condition occurred
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
TRACE(GOBJECT_SIGNAL_EMIT_END(signal_id, detail, instance, G_TYPE_FROM_INSTANCE (instance)));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
n_params = node->n_params;
|
||||
@ -3227,23 +3388,6 @@ g_signal_emit_by_name (gpointer instance,
|
||||
g_warning ("%s: signal name `%s' is invalid for instance `%p'", G_STRLOC, detailed_signal, instance);
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
accumulate (GSignalInvocationHint *ihint,
|
||||
GValue *return_accu,
|
||||
GValue *handler_return,
|
||||
SignalAccumulator *accumulator)
|
||||
{
|
||||
gboolean continue_emission;
|
||||
|
||||
if (!accumulator)
|
||||
return TRUE;
|
||||
|
||||
continue_emission = accumulator->func (ihint, return_accu, handler_return, accumulator->data);
|
||||
g_value_reset (handler_return);
|
||||
|
||||
return continue_emission;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
signal_emit_unlocked_R (SignalNode *node,
|
||||
GQuark detail,
|
||||
|
@ -158,6 +158,36 @@ G_STMT_START { \
|
||||
G_VALUE_COLLECT_INIT(value, _value_type, var_args, flags, __error); \
|
||||
} G_STMT_END
|
||||
|
||||
#define G_VALUE_COLLECT_SKIP(_value_type, var_args) \
|
||||
G_STMT_START { \
|
||||
GTypeValueTable *_vtable = g_type_value_table_peek (_value_type); \
|
||||
gchar *_collect_format = _vtable->collect_format; \
|
||||
\
|
||||
while (*_collect_format) \
|
||||
{ \
|
||||
switch (*_collect_format++) \
|
||||
{ \
|
||||
case G_VALUE_COLLECT_INT: \
|
||||
va_arg ((var_args), gint); \
|
||||
break; \
|
||||
case G_VALUE_COLLECT_LONG: \
|
||||
va_arg ((var_args), glong); \
|
||||
break; \
|
||||
case G_VALUE_COLLECT_INT64: \
|
||||
va_arg ((var_args), gint64); \
|
||||
break; \
|
||||
case G_VALUE_COLLECT_DOUBLE: \
|
||||
va_arg ((var_args), gdouble); \
|
||||
break; \
|
||||
case G_VALUE_COLLECT_POINTER: \
|
||||
va_arg ((var_args), gpointer); \
|
||||
break; \
|
||||
default: \
|
||||
g_assert_not_reached (); \
|
||||
} \
|
||||
} \
|
||||
} G_STMT_END
|
||||
|
||||
/**
|
||||
* G_VALUE_LCOPY:
|
||||
* @value: a #GValue return location. @value is supposed to be initialized
|
||||
|
Loading…
Reference in New Issue
Block a user