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:
Alexander Larsson 2012-02-22 19:36:05 +01:00
parent a3e91088ce
commit f02ec2f2de
2 changed files with 274 additions and 100 deletions

View File

@ -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,

View File

@ -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