diff --git a/tests/gobject/Makefile.am b/tests/gobject/Makefile.am index d6103664b..8a9934c83 100644 --- a/tests/gobject/Makefile.am +++ b/tests/gobject/Makefile.am @@ -59,9 +59,11 @@ test_programs = \ ifaceinherit \ ifaceproperties \ override \ + performance \ singleton \ references +performance_LDADD = $(libgobject) $(libgthread) check_PROGRAMS = $(test_programs) TESTS = $(test_programs) diff --git a/tests/gobject/performance.c b/tests/gobject/performance.c new file mode 100644 index 000000000..6f1b5d077 --- /dev/null +++ b/tests/gobject/performance.c @@ -0,0 +1,735 @@ +/* GObject - GLib Type, Object, Parameter and Signal Library + * Copyright (C) 2009 Red Hat, Inc. + * + * 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 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, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#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 + * enought that the risk of any random slowness will miss the + * running window */ +#define TARGET_ROUND_TIME 0.004 + +static gboolean verbose = FALSE; +static gboolean init_threads = FALSE; +static int test_length = DEFAULT_TEST_TIME; + +static GOptionEntry cmd_entries[] = { + {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, + "Print extra information", NULL}, + {"threads", 't', 0, G_OPTION_ARG_NONE, &init_threads, + "Initialize threads", NULL}, + {"seconds", 's', 0, G_OPTION_ARG_INT, &test_length, + "Time to run each test in seconds", NULL}, + {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, factor; + GTimer *timer; + + 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"); + + /* 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); + } + + if (verbose) + g_print ("Estimating round time\n"); + + /* Estimate time for one run by doing a few test rounds */ + min_elapsed = 0; + 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: %f.4 secs, correction factor %f.2\n", 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 */ + 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) + min_elapsed = elapsed; + else + min_elapsed = MIN (min_elapsed, elapsed); + } + + if (verbose) + g_print ("Minimum corrected round time: %f secs\n", min_elapsed); + + /* Print the results */ + 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 + *************************************************************/ + +#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); +}; + +#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. + *************************************************************/ + +#define COMPLEX_TYPE_OBJECT (complex_object_get_type ()) +typedef struct _ComplexObject ComplexObject; +typedef struct _ComplexObjectClass ComplexObjectClass; + +struct _ComplexObject +{ + GObject parent_instance; + int val1; + int val2; +}; + +struct _ComplexObjectClass +{ + GObjectClass parent_class; + + void (*signal) (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 +}; + +enum { + COMPLEX_SIGNAL, + COMPLEX_LAST_SIGNAL +}; + +static guint complex_signals[COMPLEX_LAST_SIGNAL] = { 0 }; + +static void +complex_object_finalize (GObject *object) +{ + 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: + complex->val2 = g_value_get_int (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_int (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); + + g_object_class_install_property (object_class, + PROP_VAL1, + g_param_spec_int ("val1", + "val1", + "val1", + 0, + G_MAXINT, + 42, + G_PARAM_CONSTRUCT | G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_VAL2, + g_param_spec_int ("val2", + "val2", + "val2", + 0, + G_MAXINT, + 43, + G_PARAM_READWRITE)); + + +} + +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->val2 = 43; +} + +/************************************************************* + * 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 (*)())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_new (GObject *, 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_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_teardown (PerformanceTest *test, + gpointer _data) +{ + struct ConstructionTest *data = _data; + g_free (data->objects); + g_free (data); +} + +static void +test_construction_print_result (PerformanceTest *test, + gpointer _data, + double time) +{ + struct ConstructionTest *data = _data; + + g_print ("Number of constructed objects per second: %.0f\n", + data->n_objects / time); +} + +/************************************************************* + * 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; + volatile GObject *object = data->object; + volatile 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 + *************************************************************/ + +#define NUM_EMISSIONS_PER_ROUND 10000 + +struct EmissionTest { + GObject *object; + int n_checks; +}; +static gpointer +test_emission_setup (PerformanceTest *test) +{ + struct EmissionTest *data; + + data = g_new0 (struct EmissionTest, 1); + data->object = g_object_new (COMPLEX_TYPE_OBJECT, NULL); + + return data; +} + +static void +test_emission_init (PerformanceTest *test, + gpointer _data, + double factor) +{ + struct EmissionTest *data = _data; + + data->n_checks = factor * NUM_EMISSIONS_PER_ROUND; +} + +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, + complex_signals[COMPLEX_SIGNAL], + 0); +} + +static void +test_emission_finish (PerformanceTest *test, + gpointer data) +{ +} + +static void +test_emission_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_teardown (PerformanceTest *test, + gpointer _data) +{ + struct EmissionTest *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 + }, + { + "complex-construction", + complex_object_get_type, + test_construction_setup, + test_construction_init, + test_construction_run, + test_construction_finish, + test_construction_teardown, + test_construction_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", + NULL, + test_emission_setup, + test_emission_init, + test_emission_run, + test_emission_finish, + test_emission_teardown, + test_emission_print_result + } +}; + +static PerformanceTest * +find_test (const char *name) +{ + int 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; + int i; + + g_type_init (); + + 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 (init_threads) + g_thread_init (NULL); + + if (argc > 1) + { + for (i = 1; i < argc; i++) + { + test = find_test (argv[i]); + if (test) + run_test (test); + } + } + else + { + for (i = 0; i < G_N_ELEMENTS (tests); i++) + run_test (&tests[i]); + } + + return 0; +} diff --git a/tests/gobject/run-performance.sh b/tests/gobject/run-performance.sh new file mode 100755 index 000000000..d2f92cacb --- /dev/null +++ b/tests/gobject/run-performance.sh @@ -0,0 +1,7 @@ +#!/bin/sh +DIR=`dirname $0`; +(cd $DIR; make performance) +ID=`git rev-list --max-count=1 HEAD` +echo "Testing revision ${ID}" +$DIR/performance | tee "perf-${ID}-normal.log" +$DIR/performance -t | tee "perf-${ID}-threaded.log"