[glib/gobject-performance: 1/10] Add performance tests for GObject primitives



commit 44fe937be76580c3e5afa044de4a28a611958076
Author: Alexander Larsson <alexl redhat com>
Date:   Thu Aug 20 14:34:51 2009 +0200

    Add performance tests for GObject primitives
    
    These are basic performance test for a couple of basic gobject
    primitives:
    
    * construction of simple objects. Simple is a bare gobject derived
      class with no properties, signals or interfaces.
    
    * construction of complex objects. Complex is a gobject subclass
      with construct properties, normal properties, signals, and
      implements an interface.
    
    * run-time type check of complex objects
    
    * signal emissions
    
    Lots of care is taken to try to make the results reproducible. Each
    test is run for multible "rounds", where we try to make each round be
    "not too short" in order to be significant wrt timer accuracy, but
    also "not to long" to make the probability of some other random event
    happening on the system (interrupts, other process scheduled, etc)
    during the round less likely.
    The current target round time is 10msecs, which was picked without
    rigour, but seems small wrt e.g. scheduler time.
    
    For each test we then run the calculated round size for 60 seconds,
    and then report the performance based on the minimal time of one
    round. The model here is that any random stuff that happens during a
    round can only slow it down, there is nothing that can make it go
    faster, so the minimal time is the best estimate of how fast one round
    goes.
    
    The result is not ideal, even on a "idle" system the results vary
    from round to round, but the variation seems to be less than 1%.
    So, any performance difference reported by this test over 1% is
    probably statistically significant.
    
    Additionally the tests can be run with or without threads being
    initialized. The script tests/gobject/run-performance.sh makes
    it easy to produce a performance report for the current checkout.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=557100

 tests/gobject/Makefile.am        |    2 +
 tests/gobject/performance.c      |  735 ++++++++++++++++++++++++++++++++++++++
 tests/gobject/run-performance.sh |    7 +
 3 files changed, 744 insertions(+), 0 deletions(-)
---
diff --git a/tests/gobject/Makefile.am b/tests/gobject/Makefile.am
index d610366..8a9934c 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 0000000..fbed66e
--- /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 <math.h>
+#include <string.h>
+#include <glib-object.h>
+#include "testcommon.h"
+
+#define WARM_UP_N_RUNS 50
+#define ESTIMATE_ROUND_TIME_N_RUNS 5
+#define DEFAULT_TEST_TIME 60 /* 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.01
+
+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 100000
+
+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_MEGA_CHECKS_PER_ROUND 100
+
+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_MEGA_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 100000
+
+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 0000000..d2f92ca
--- /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"



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]