/* GObject - GLib Type, Object, Parameter and Signal Library * Copyright (C) 2009 Red Hat, Inc. * Copyright (C) 2022 Canonical Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, see . */ #include #include #include #include "../testcommon.h" #define WARM_UP_N_RUNS 50 #define ESTIMATE_ROUND_TIME_N_RUNS 5 #define DEFAULT_TEST_TIME 15 /* seconds */ /* The time we want each round to take, in seconds, this should * be large enough compared to the timer resolution, but small * enough that the risk of any random slowness will miss the * running window */ #define TARGET_ROUND_TIME 0.008 static gboolean verbose = FALSE; static gboolean quiet = FALSE; static int test_length = DEFAULT_TEST_TIME; static double test_factor = 0; static GOptionEntry cmd_entries[] = { {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Print extra information", NULL}, {"quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, "Print extra information", NULL}, {"seconds", 's', 0, G_OPTION_ARG_INT, &test_length, "Time to run each test in seconds", NULL}, {"factor", 'f', 0, G_OPTION_ARG_DOUBLE, &test_factor, "Use a fixed factor for sample runs (also $GLIB_PERFORMANCE_FACTOR)", NULL}, G_OPTION_ENTRY_NULL }; typedef struct _PerformanceTest PerformanceTest; struct _PerformanceTest { const char *name; gpointer extra_data; gpointer (*setup) (PerformanceTest *test); void (*init) (PerformanceTest *test, gpointer data, double factor); void (*run) (PerformanceTest *test, gpointer data); void (*finish) (PerformanceTest *test, gpointer data); void (*teardown) (PerformanceTest *test, gpointer data); void (*print_result) (PerformanceTest *test, gpointer data, double time); }; static void run_test (PerformanceTest *test) { gpointer data = NULL; guint64 i, num_rounds; double elapsed, min_elapsed, max_elapsed, avg_elapsed, factor; GTimer *timer; if (verbose || !quiet) g_print ("Running test %s\n", test->name); /* Set up test */ timer = g_timer_new (); data = test->setup (test); if (verbose) g_print ("Warming up\n"); g_timer_start (timer); /* Warm up the test by doing a few runs */ for (i = 0; i < WARM_UP_N_RUNS; i++) { test->init (test, data, 1.0); test->run (test, data); test->finish (test, data); } g_timer_stop (timer); elapsed = g_timer_elapsed (timer, NULL); if (verbose) { g_print ("Warm up time: %.2f secs\n", elapsed); g_print ("Estimating round time\n"); } min_elapsed = 0; if (test_factor > 0) { factor = test_factor; } else { /* Estimate time for one run by doing a few test rounds. */ for (i = 0; i < ESTIMATE_ROUND_TIME_N_RUNS; i++) { test->init (test, data, 1.0); g_timer_start (timer); test->run (test, data); g_timer_stop (timer); test->finish (test, data); elapsed = g_timer_elapsed (timer, NULL); if (i == 0) min_elapsed = elapsed; else min_elapsed = MIN (min_elapsed, elapsed); } factor = TARGET_ROUND_TIME / min_elapsed; } if (verbose) g_print ("Uncorrected round time: %.4f msecs, correction factor %.2f\n", 1000*min_elapsed, factor); /* Calculate number of rounds needed */ num_rounds = (test_length / TARGET_ROUND_TIME) + 1; if (verbose) g_print ("Running %"G_GINT64_MODIFIER"d rounds\n", num_rounds); /* Run the test */ avg_elapsed = 0.0; min_elapsed = 0.0; max_elapsed = 0.0; for (i = 0; i < num_rounds; i++) { test->init (test, data, factor); g_timer_start (timer); test->run (test, data); g_timer_stop (timer); test->finish (test, data); elapsed = g_timer_elapsed (timer, NULL); if (i == 0) max_elapsed = min_elapsed = avg_elapsed = elapsed; else { min_elapsed = MIN (min_elapsed, elapsed); max_elapsed = MAX (max_elapsed, elapsed); avg_elapsed += elapsed; } } if (num_rounds > 1) avg_elapsed = avg_elapsed / num_rounds; if (verbose) { g_print ("Minimum corrected round time: %.2f msecs\n", min_elapsed * 1000); g_print ("Maximum corrected round time: %.2f msecs\n", max_elapsed * 1000); g_print ("Average corrected round time: %.2f msecs\n", avg_elapsed * 1000); } /* Print the results */ g_print ("%s: ", test->name); test->print_result (test, data, min_elapsed); /* Tear down */ test->teardown (test, data); g_timer_destroy (timer); } /************************************************************* * Simple object is a very simple small GObject subclass * with no properties, no signals, implementing no interfaces *************************************************************/ static GType simple_object_get_type (void); #define SIMPLE_TYPE_OBJECT (simple_object_get_type ()) typedef struct _SimpleObject SimpleObject; typedef struct _SimpleObjectClass SimpleObjectClass; struct _SimpleObject { GObject parent_instance; int val; }; struct _SimpleObjectClass { GObjectClass parent_class; }; G_DEFINE_TYPE (SimpleObject, simple_object, G_TYPE_OBJECT) static void simple_object_finalize (GObject *object) { G_OBJECT_CLASS (simple_object_parent_class)->finalize (object); } static void simple_object_class_init (SimpleObjectClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); object_class->finalize = simple_object_finalize; } static void simple_object_init (SimpleObject *simple_object) { simple_object->val = 42; } typedef struct _TestIfaceClass TestIfaceClass; typedef struct _TestIfaceClass TestIface1Class; typedef struct _TestIfaceClass TestIface2Class; typedef struct _TestIfaceClass TestIface3Class; typedef struct _TestIfaceClass TestIface4Class; typedef struct _TestIfaceClass TestIface5Class; typedef struct _TestIface TestIface; struct _TestIfaceClass { GTypeInterface base_iface; void (*method) (TestIface *obj); }; static GType test_iface1_get_type (void); static GType test_iface2_get_type (void); static GType test_iface3_get_type (void); static GType test_iface4_get_type (void); static GType test_iface5_get_type (void); #define TEST_TYPE_IFACE1 (test_iface1_get_type ()) #define TEST_TYPE_IFACE2 (test_iface2_get_type ()) #define TEST_TYPE_IFACE3 (test_iface3_get_type ()) #define TEST_TYPE_IFACE4 (test_iface4_get_type ()) #define TEST_TYPE_IFACE5 (test_iface5_get_type ()) static DEFINE_IFACE (TestIface1, test_iface1, NULL, NULL) static DEFINE_IFACE (TestIface2, test_iface2, NULL, NULL) static DEFINE_IFACE (TestIface3, test_iface3, NULL, NULL) static DEFINE_IFACE (TestIface4, test_iface4, NULL, NULL) static DEFINE_IFACE (TestIface5, test_iface5, NULL, NULL) /************************************************************* * Complex object is a GObject subclass with a properties, * construct properties, signals and implementing an interface. *************************************************************/ static GType complex_object_get_type (void); #define COMPLEX_TYPE_OBJECT (complex_object_get_type ()) typedef struct _ComplexObject ComplexObject; typedef struct _ComplexObjectClass ComplexObjectClass; struct _ComplexObject { GObject parent_instance; int val1; char *val2; }; struct _ComplexObjectClass { GObjectClass parent_class; void (*signal) (ComplexObject *obj); void (*signal_empty) (ComplexObject *obj); }; static void complex_test_iface_init (gpointer g_iface, gpointer iface_data); G_DEFINE_TYPE_EXTENDED (ComplexObject, complex_object, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE (TEST_TYPE_IFACE1, complex_test_iface_init) G_IMPLEMENT_INTERFACE (TEST_TYPE_IFACE2, complex_test_iface_init) G_IMPLEMENT_INTERFACE (TEST_TYPE_IFACE3, complex_test_iface_init) G_IMPLEMENT_INTERFACE (TEST_TYPE_IFACE4, complex_test_iface_init) G_IMPLEMENT_INTERFACE (TEST_TYPE_IFACE5, complex_test_iface_init)) #define COMPLEX_OBJECT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), COMPLEX_TYPE_OBJECT, ComplexObject)) enum { PROP_0, PROP_VAL1, PROP_VAL2, N_PROPERTIES }; static GParamSpec *pspecs[N_PROPERTIES] = { NULL, }; enum { COMPLEX_SIGNAL, COMPLEX_SIGNAL_EMPTY, COMPLEX_SIGNAL_GENERIC, COMPLEX_SIGNAL_GENERIC_EMPTY, COMPLEX_SIGNAL_ARGS, COMPLEX_LAST_SIGNAL }; static guint complex_signals[COMPLEX_LAST_SIGNAL] = { 0 }; static void complex_object_finalize (GObject *object) { ComplexObject *c = COMPLEX_OBJECT (object); g_free (c->val2); G_OBJECT_CLASS (complex_object_parent_class)->finalize (object); } static void complex_object_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ComplexObject *complex = COMPLEX_OBJECT (object); switch (prop_id) { case PROP_VAL1: complex->val1 = g_value_get_int (value); break; case PROP_VAL2: g_free (complex->val2); complex->val2 = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void complex_object_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ComplexObject *complex = COMPLEX_OBJECT (object); switch (prop_id) { case PROP_VAL1: g_value_set_int (value, complex->val1); break; case PROP_VAL2: g_value_set_string (value, complex->val2); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void complex_object_real_signal (ComplexObject *obj) { } static void complex_object_class_init (ComplexObjectClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); object_class->finalize = complex_object_finalize; object_class->set_property = complex_object_set_property; object_class->get_property = complex_object_get_property; class->signal = complex_object_real_signal; complex_signals[COMPLEX_SIGNAL] = g_signal_new ("signal", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (ComplexObjectClass, signal), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); complex_signals[COMPLEX_SIGNAL_EMPTY] = g_signal_new ("signal-empty", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (ComplexObjectClass, signal_empty), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); complex_signals[COMPLEX_SIGNAL_GENERIC] = g_signal_new ("signal-generic", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (ComplexObjectClass, signal), NULL, NULL, NULL, G_TYPE_NONE, 0); complex_signals[COMPLEX_SIGNAL_GENERIC_EMPTY] = g_signal_new ("signal-generic-empty", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (ComplexObjectClass, signal_empty), NULL, NULL, NULL, G_TYPE_NONE, 0); complex_signals[COMPLEX_SIGNAL_ARGS] = g_signal_new ("signal-args", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (ComplexObjectClass, signal), NULL, NULL, g_cclosure_marshal_VOID__UINT_POINTER, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_POINTER); pspecs[PROP_VAL1] = g_param_spec_int ("val1", "val1", "val1", 0, G_MAXINT, 42, G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); pspecs[PROP_VAL2] = g_param_spec_string ("val2", "val2", "val2", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); g_object_class_install_properties (object_class, N_PROPERTIES, pspecs); } static void complex_object_iface_method (TestIface *obj) { ComplexObject *complex = COMPLEX_OBJECT (obj); complex->val1++; } static void complex_test_iface_init (gpointer g_iface, gpointer iface_data) { TestIfaceClass *iface = g_iface; iface->method = complex_object_iface_method; } static void complex_object_init (ComplexObject *complex_object) { complex_object->val1 = 42; } /************************************************************* * Test object construction performance *************************************************************/ #define NUM_OBJECT_TO_CONSTRUCT 10000 struct ConstructionTest { GObject **objects; int n_objects; GType type; }; static gpointer test_construction_setup (PerformanceTest *test) { struct ConstructionTest *data; data = g_new0 (struct ConstructionTest, 1); data->type = ((GType (*)(void))test->extra_data)(); return data; } static void test_construction_init (PerformanceTest *test, gpointer _data, double count_factor) { struct ConstructionTest *data = _data; int n; n = NUM_OBJECT_TO_CONSTRUCT * count_factor; if (data->n_objects != n) { data->n_objects = n; data->objects = g_renew (GObject *, data->objects, n); } } static void test_construction_run (PerformanceTest *test, gpointer _data) { struct ConstructionTest *data = _data; GObject **objects = data->objects; GType type = data->type; int i, n_objects; n_objects = data->n_objects; for (i = 0; i < n_objects; i++) objects[i] = g_object_new (type, NULL); } static void test_construction_run1 (PerformanceTest *test, gpointer _data) { struct ConstructionTest *data = _data; GObject **objects = data->objects; int i, n_objects; n_objects = data->n_objects; for (i = 0; i < n_objects; i++) objects[i] = (GObject *) g_slice_new0 (SimpleObject); } static void test_complex_construction_run (PerformanceTest *test, gpointer _data) { struct ConstructionTest *data = _data; GObject **objects = data->objects; GType type = data->type; int i, n_objects; n_objects = data->n_objects; for (i = 0; i < n_objects; i++) objects[i] = g_object_new (type, "val1", 5, "val2", "thousand", NULL); } static void test_complex_construction_run1 (PerformanceTest *test, gpointer _data) { struct ConstructionTest *data = _data; GObject **objects = data->objects; GType type = data->type; int i, n_objects; n_objects = data->n_objects; for (i = 0; i < n_objects; i++) { ComplexObject *object; object = (ComplexObject *)g_object_new (type, NULL); object->val1 = 5; object->val2 = g_strdup ("thousand"); objects[i] = (GObject *)object; } } static void test_complex_construction_run2 (PerformanceTest *test, gpointer _data) { struct ConstructionTest *data = _data; GObject **objects = data->objects; GType type = data->type; int i, n_objects; n_objects = data->n_objects; for (i = 0; i < n_objects; i++) { objects[i] = g_object_new (type, NULL); } } static void test_construction_finish (PerformanceTest *test, gpointer _data) { struct ConstructionTest *data = _data; int i; for (i = 0; i < data->n_objects; i++) g_object_unref (data->objects[i]); } static void test_construction_finish1 (PerformanceTest *test, gpointer _data) { struct ConstructionTest *data = _data; int i; for (i = 0; i < data->n_objects; i++) g_slice_free (SimpleObject, (SimpleObject *)data->objects[i]); } static void test_construction_teardown (PerformanceTest *test, gpointer _data) { struct ConstructionTest *data = _data; g_free (data->objects); g_free (data); } static void test_finalization_init (PerformanceTest *test, gpointer _data, double count_factor) { struct ConstructionTest *data = _data; int n; n = NUM_OBJECT_TO_CONSTRUCT * count_factor; if (data->n_objects != n) { data->n_objects = n; data->objects = g_renew (GObject *, data->objects, n); } for (int i = 0; i < data->n_objects; i++) { data->objects[i] = g_object_new (data->type, NULL); } } static void test_finalization_run (PerformanceTest *test, gpointer _data) { struct ConstructionTest *data = _data; GObject **objects = data->objects; int i, n_objects; n_objects = data->n_objects; for (i = 0; i < n_objects; i++) { g_object_unref (objects[i]); } } static void test_finalization_finish (PerformanceTest *test, gpointer _data) { } static void test_construction_print_result (PerformanceTest *test, gpointer _data, double time) { struct ConstructionTest *data = _data; g_print ("Millions of constructed objects per second: %.3f\n", data->n_objects / (time * 1000000)); } static void test_finalization_print_result (PerformanceTest *test, gpointer _data, double time) { struct ConstructionTest *data = _data; g_print ("Millions of finalized objects per second: %.3f\n", data->n_objects / (time * 1000000)); } /************************************************************* * Test runtime type check performance *************************************************************/ #define NUM_KILO_CHECKS_PER_ROUND 50 struct TypeCheckTest { GObject *object; int n_checks; }; static gpointer test_type_check_setup (PerformanceTest *test) { struct TypeCheckTest *data; data = g_new0 (struct TypeCheckTest, 1); data->object = g_object_new (COMPLEX_TYPE_OBJECT, NULL); return data; } static void test_type_check_init (PerformanceTest *test, gpointer _data, double factor) { struct TypeCheckTest *data = _data; data->n_checks = factor * NUM_KILO_CHECKS_PER_ROUND; } /* Work around g_type_check_instance_is_a being marked "pure", and thus only called once for the loop. */ gboolean (*my_type_check_instance_is_a) (GTypeInstance *type_instance, GType iface_type) = &g_type_check_instance_is_a; static void test_type_check_run (PerformanceTest *test, gpointer _data) { struct TypeCheckTest *data = _data; GObject *object = data->object; GType type, types[5]; int i, j; types[0] = test_iface1_get_type (); types[1] = test_iface2_get_type (); types[2] = test_iface3_get_type (); types[3] = test_iface4_get_type (); types[4] = test_iface5_get_type (); for (i = 0; i < data->n_checks; i++) { type = types[i%5]; for (j = 0; j < 1000; j++) { my_type_check_instance_is_a ((GTypeInstance *)object, type); } } } static void test_type_check_finish (PerformanceTest *test, gpointer data) { } static void test_type_check_print_result (PerformanceTest *test, gpointer _data, double time) { struct TypeCheckTest *data = _data; g_print ("Million type checks per second: %.2f\n", data->n_checks / (1000*time)); } static void test_type_check_teardown (PerformanceTest *test, gpointer _data) { struct TypeCheckTest *data = _data; g_object_unref (data->object); g_free (data); } /************************************************************* * Test signal emissions performance (common code) *************************************************************/ #define NUM_EMISSIONS_PER_ROUND 10000 struct EmissionTest { GObject *object; int n_checks; int signal_id; }; static void test_emission_run (PerformanceTest *test, gpointer _data) { struct EmissionTest *data = _data; GObject *object = data->object; int i; for (i = 0; i < data->n_checks; i++) g_signal_emit (object, data->signal_id, 0); } static void test_emission_run_args (PerformanceTest *test, gpointer _data) { struct EmissionTest *data = _data; GObject *object = data->object; int i; for (i = 0; i < data->n_checks; i++) g_signal_emit (object, data->signal_id, 0, 0, NULL); } /************************************************************* * Test signal unhandled emissions performance *************************************************************/ static gpointer test_emission_unhandled_setup (PerformanceTest *test) { struct EmissionTest *data; data = g_new0 (struct EmissionTest, 1); data->object = g_object_new (COMPLEX_TYPE_OBJECT, NULL); data->signal_id = complex_signals[GPOINTER_TO_INT (test->extra_data)]; return data; } static void test_emission_unhandled_init (PerformanceTest *test, gpointer _data, double factor) { struct EmissionTest *data = _data; data->n_checks = factor * NUM_EMISSIONS_PER_ROUND; } static void test_emission_unhandled_finish (PerformanceTest *test, gpointer data) { } static void test_emission_unhandled_print_result (PerformanceTest *test, gpointer _data, double time) { struct EmissionTest *data = _data; g_print ("Emissions per second: %.0f\n", data->n_checks / time); } static void test_emission_unhandled_teardown (PerformanceTest *test, gpointer _data) { struct EmissionTest *data = _data; g_object_unref (data->object); g_free (data); } /************************************************************* * Test signal handled emissions performance *************************************************************/ static void test_emission_handled_handler (ComplexObject *obj, gpointer data) { } static gpointer test_emission_handled_setup (PerformanceTest *test) { struct EmissionTest *data; data = g_new0 (struct EmissionTest, 1); data->object = g_object_new (COMPLEX_TYPE_OBJECT, NULL); data->signal_id = complex_signals[GPOINTER_TO_INT (test->extra_data)]; g_signal_connect (data->object, "signal", G_CALLBACK (test_emission_handled_handler), NULL); g_signal_connect (data->object, "signal-empty", G_CALLBACK (test_emission_handled_handler), NULL); g_signal_connect (data->object, "signal-generic", G_CALLBACK (test_emission_handled_handler), NULL); g_signal_connect (data->object, "signal-generic-empty", G_CALLBACK (test_emission_handled_handler), NULL); g_signal_connect (data->object, "signal-args", G_CALLBACK (test_emission_handled_handler), NULL); return data; } static void test_emission_handled_init (PerformanceTest *test, gpointer _data, double factor) { struct EmissionTest *data = _data; data->n_checks = factor * NUM_EMISSIONS_PER_ROUND; } static void test_emission_handled_finish (PerformanceTest *test, gpointer data) { } static void test_emission_handled_print_result (PerformanceTest *test, gpointer _data, double time) { struct EmissionTest *data = _data; g_print ("Emissions per second: %.0f\n", data->n_checks / time); } static void test_emission_handled_teardown (PerformanceTest *test, gpointer _data) { struct EmissionTest *data = _data; g_object_unref (data->object); g_free (data); } /************************************************************* * Test object notify performance (common code) *************************************************************/ #define NUM_NOTIFY_PER_ROUND 10000 struct NotifyTest { GObject *object; unsigned n_checks; }; static void test_notify_run (PerformanceTest *test, void *_data) { struct NotifyTest *data = _data; GObject *object = data->object; for (unsigned i = 0; i < data->n_checks; i++) g_object_notify (object, "val1"); } static void test_notify_by_pspec_run (PerformanceTest *test, void *_data) { struct NotifyTest *data = _data; GObject *object = data->object; for (unsigned i = 0; i < data->n_checks; i++) g_object_notify_by_pspec (object, pspecs[PROP_VAL1]); } /************************************************************* * Test notify unhandled performance *************************************************************/ static void * test_notify_unhandled_setup (PerformanceTest *test) { struct NotifyTest *data; data = g_new0 (struct NotifyTest, 1); data->object = g_object_new (COMPLEX_TYPE_OBJECT, NULL); return data; } static void test_notify_unhandled_init (PerformanceTest *test, void *_data, double factor) { struct NotifyTest *data = _data; data->n_checks = factor * NUM_NOTIFY_PER_ROUND; } static void test_notify_unhandled_finish (PerformanceTest *test, void *data) { } static void test_notify_unhandled_print_result (PerformanceTest *test, void *_data, double time) { struct NotifyTest *data = _data; g_print ("Notify (unhandled) per second: %.0f\n", data->n_checks / time); } static void test_notify_unhandled_teardown (PerformanceTest *test, void *_data) { struct NotifyTest *data = _data; g_object_unref (data->object); g_free (data); } /************************************************************* * Test notify handled performance *************************************************************/ static void test_notify_handled_handler (ComplexObject *obj, GParamSpec *pspec, void *data) { } static void * test_notify_handled_setup (PerformanceTest *test) { struct NotifyTest *data; data = g_new0 (struct NotifyTest, 1); data->object = g_object_new (COMPLEX_TYPE_OBJECT, NULL); g_signal_connect (data->object, "notify::val1", G_CALLBACK (test_notify_handled_handler), data); g_signal_connect (data->object, "notify::val2", G_CALLBACK (test_notify_handled_handler), data); return data; } static void test_notify_handled_init (PerformanceTest *test, void *_data, double factor) { struct NotifyTest *data = _data; data->n_checks = factor * NUM_NOTIFY_PER_ROUND; } static void test_notify_handled_finish (PerformanceTest *test, void *data) { } static void test_notify_handled_print_result (PerformanceTest *test, void *_data, double time) { struct NotifyTest *data = _data; g_print ("Notify per second: %.0f\n", data->n_checks / time); } static void test_notify_handled_teardown (PerformanceTest *test, void *_data) { struct NotifyTest *data = _data; g_assert_cmpuint ( g_signal_handlers_disconnect_by_func (data->object, test_notify_handled_handler, data), ==, 2); g_object_unref (data->object); g_free (data); } /************************************************************* * Test object set performance *************************************************************/ #define NUM_SET_PER_ROUND 10000 struct SetTest { GObject *object; unsigned n_checks; }; static void test_set_run (PerformanceTest *test, void *_data) { struct SetTest *data = _data; GObject *object = data->object; for (unsigned i = 0; i < data->n_checks; i++) g_object_set (object, "val1", i, NULL); } static void * test_set_setup (PerformanceTest *test) { struct SetTest *data; data = g_new0 (struct SetTest, 1); data->object = g_object_new (COMPLEX_TYPE_OBJECT, NULL); return data; } static void test_set_init (PerformanceTest *test, void *_data, double factor) { struct SetTest *data = _data; data->n_checks = factor * NUM_SET_PER_ROUND; } static void test_set_finish (PerformanceTest *test, void *data) { } static void test_set_print_result (PerformanceTest *test, void *_data, double time) { struct SetTest *data = _data; g_print ("Property set per second: %.0f\n", data->n_checks / time); } static void test_set_teardown (PerformanceTest *test, void *_data) { struct SetTest *data = _data; g_object_unref (data->object); g_free (data); } /************************************************************* * Test object get performance *************************************************************/ #define NUM_GET_PER_ROUND 10000 struct GetTest { GObject *object; unsigned n_checks; }; static void test_get_run (PerformanceTest *test, void *_data) { struct GetTest *data = _data; GObject *object = data->object; int val; for (unsigned i = 0; i < data->n_checks; i++) g_object_get (object, "val1", &val, NULL); } static void * test_get_setup (PerformanceTest *test) { struct GetTest *data; data = g_new0 (struct GetTest, 1); data->object = g_object_new (COMPLEX_TYPE_OBJECT, NULL); return data; } static void test_get_init (PerformanceTest *test, void *_data, double factor) { struct GetTest *data = _data; data->n_checks = factor * NUM_GET_PER_ROUND; } static void test_get_finish (PerformanceTest *test, void *data) { } static void test_get_print_result (PerformanceTest *test, void *_data, double time) { struct GetTest *data = _data; g_print ("Property get per second: %.0f\n", data->n_checks / time); } static void test_get_teardown (PerformanceTest *test, gpointer _data) { struct GetTest *data = _data; g_object_unref (data->object); g_free (data); } /************************************************************* * Test object refcount performance *************************************************************/ #define NUM_KILO_REFS_PER_ROUND 100000 struct RefcountTest { GObject *object; int n_checks; }; static gpointer test_refcount_setup (PerformanceTest *test) { struct RefcountTest *data; data = g_new0 (struct RefcountTest, 1); data->object = g_object_new (COMPLEX_TYPE_OBJECT, NULL); return data; } static void test_refcount_init (PerformanceTest *test, gpointer _data, double factor) { struct RefcountTest *data = _data; data->n_checks = factor * NUM_KILO_REFS_PER_ROUND; } static void test_refcount_run (PerformanceTest *test, gpointer _data) { struct RefcountTest *data = _data; GObject *object = data->object; int i; for (i = 0; i < data->n_checks; i++) { g_object_ref (object); g_object_ref (object); g_object_ref (object); g_object_unref (object); g_object_unref (object); g_object_ref (object); g_object_ref (object); g_object_unref (object); g_object_unref (object); g_object_unref (object); } } static void test_refcount_finish (PerformanceTest *test, gpointer _data) { } static void test_refcount_print_result (PerformanceTest *test, gpointer _data, double time) { struct RefcountTest *data = _data; g_print ("Million refs+unref per second: %.2f\n", data->n_checks * 5 / (time * 1000000 )); } static void test_refcount_teardown (PerformanceTest *test, gpointer _data) { struct RefcountTest *data = _data; g_object_unref (data->object); g_free (data); } /************************************************************* * Main test code *************************************************************/ static PerformanceTest tests[] = { { "simple-construction", simple_object_get_type, test_construction_setup, test_construction_init, test_construction_run, test_construction_finish, test_construction_teardown, test_construction_print_result }, { "simple-construction1", simple_object_get_type, test_construction_setup, test_construction_init, test_construction_run1, test_construction_finish1, test_construction_teardown, test_construction_print_result }, { "complex-construction", complex_object_get_type, test_construction_setup, test_construction_init, test_complex_construction_run, test_construction_finish, test_construction_teardown, test_construction_print_result }, { "complex-construction1", complex_object_get_type, test_construction_setup, test_construction_init, test_complex_construction_run1, test_construction_finish, test_construction_teardown, test_construction_print_result }, { "complex-construction2", complex_object_get_type, test_construction_setup, test_construction_init, test_complex_construction_run2, test_construction_finish, test_construction_teardown, test_construction_print_result }, { "finalization", simple_object_get_type, test_construction_setup, test_finalization_init, test_finalization_run, test_finalization_finish, test_construction_teardown, test_finalization_print_result }, { "type-check", NULL, test_type_check_setup, test_type_check_init, test_type_check_run, test_type_check_finish, test_type_check_teardown, test_type_check_print_result }, { "emit-unhandled", GINT_TO_POINTER (COMPLEX_SIGNAL), test_emission_unhandled_setup, test_emission_unhandled_init, test_emission_run, test_emission_unhandled_finish, test_emission_unhandled_teardown, test_emission_unhandled_print_result }, { "emit-unhandled-empty", GINT_TO_POINTER (COMPLEX_SIGNAL_EMPTY), test_emission_unhandled_setup, test_emission_unhandled_init, test_emission_run, test_emission_unhandled_finish, test_emission_unhandled_teardown, test_emission_unhandled_print_result }, { "emit-unhandled-generic", GINT_TO_POINTER (COMPLEX_SIGNAL_GENERIC), test_emission_unhandled_setup, test_emission_unhandled_init, test_emission_run, test_emission_unhandled_finish, test_emission_unhandled_teardown, test_emission_unhandled_print_result }, { "emit-unhandled-generic-empty", GINT_TO_POINTER (COMPLEX_SIGNAL_GENERIC_EMPTY), test_emission_unhandled_setup, test_emission_unhandled_init, test_emission_run, test_emission_unhandled_finish, test_emission_unhandled_teardown, test_emission_unhandled_print_result }, { "emit-unhandled-args", GINT_TO_POINTER (COMPLEX_SIGNAL_ARGS), test_emission_unhandled_setup, test_emission_unhandled_init, test_emission_run_args, test_emission_unhandled_finish, test_emission_unhandled_teardown, test_emission_unhandled_print_result }, { "emit-handled", GINT_TO_POINTER (COMPLEX_SIGNAL), test_emission_handled_setup, test_emission_handled_init, test_emission_run, test_emission_handled_finish, test_emission_handled_teardown, test_emission_handled_print_result }, { "emit-handled-empty", GINT_TO_POINTER (COMPLEX_SIGNAL_EMPTY), test_emission_handled_setup, test_emission_handled_init, test_emission_run, test_emission_handled_finish, test_emission_handled_teardown, test_emission_handled_print_result }, { "emit-handled-generic", GINT_TO_POINTER (COMPLEX_SIGNAL_GENERIC), test_emission_handled_setup, test_emission_handled_init, test_emission_run, test_emission_handled_finish, test_emission_handled_teardown, test_emission_handled_print_result }, { "emit-handled-generic-empty", GINT_TO_POINTER (COMPLEX_SIGNAL_GENERIC_EMPTY), test_emission_handled_setup, test_emission_handled_init, test_emission_run, test_emission_handled_finish, test_emission_handled_teardown, test_emission_handled_print_result }, { "emit-handled-args", GINT_TO_POINTER (COMPLEX_SIGNAL_ARGS), test_emission_handled_setup, test_emission_handled_init, test_emission_run_args, test_emission_handled_finish, test_emission_handled_teardown, test_emission_handled_print_result }, { "notify-unhandled", complex_object_get_type, test_notify_unhandled_setup, test_notify_unhandled_init, test_notify_run, test_notify_unhandled_finish, test_notify_unhandled_teardown, test_notify_unhandled_print_result }, { "notify-by-pspec-unhandled", complex_object_get_type, test_notify_unhandled_setup, test_notify_unhandled_init, test_notify_by_pspec_run, test_notify_unhandled_finish, test_notify_unhandled_teardown, test_notify_unhandled_print_result }, { "notify-handled", complex_object_get_type, test_notify_handled_setup, test_notify_handled_init, test_notify_run, test_notify_handled_finish, test_notify_handled_teardown, test_notify_handled_print_result }, { "notify-by-pspec-handled", complex_object_get_type, test_notify_handled_setup, test_notify_handled_init, test_notify_by_pspec_run, test_notify_handled_finish, test_notify_handled_teardown, test_notify_handled_print_result }, { "property-set", complex_object_get_type, test_set_setup, test_set_init, test_set_run, test_set_finish, test_set_teardown, test_set_print_result }, { "property-get", complex_object_get_type, test_get_setup, test_get_init, test_get_run, test_get_finish, test_get_teardown, test_get_print_result }, { "refcount", NULL, test_refcount_setup, test_refcount_init, test_refcount_run, test_refcount_finish, test_refcount_teardown, test_refcount_print_result } }; static PerformanceTest * find_test (const char *name) { gsize i; for (i = 0; i < G_N_ELEMENTS (tests); i++) { if (strcmp (tests[i].name, name) == 0) return &tests[i]; } return NULL; } int main (int argc, char *argv[]) { PerformanceTest *test; GOptionContext *context; GError *error = NULL; const char *str; int i; if ((str = g_getenv ("GLIB_PERFORMANCE_FACTOR")) && str[0]) { test_factor = g_strtod (str, NULL); } context = g_option_context_new ("GObject performance tests"); g_option_context_add_main_entries (context, cmd_entries, NULL); if (!g_option_context_parse (context, &argc, &argv, &error)) { g_printerr ("%s: %s\n", argv[0], error->message); return 1; } if (test_factor < 0) { g_printerr ("%s: test factor must be positive\n", argv[0]); return 1; } if (argc > 1) { for (i = 1; i < argc; i++) { test = find_test (argv[i]); if (test) run_test (test); } } else { gsize k; for (k = 0; k < G_N_ELEMENTS (tests); k++) run_test (&tests[k]); } g_option_context_free (context); return 0; }