diff --git a/benchmarks/benchmark.h b/benchmarks/benchmark.h new file mode 100644 index 0000000000..347de08a7a --- /dev/null +++ b/benchmarks/benchmark.h @@ -0,0 +1,156 @@ + +#ifndef __GTK_BENCHMARK_H__ +#define __GTK_BENCHMARK_H__ + +#include +#include + +#define SAMPLE_SIZE 5 + + +typedef struct _Benchmark Benchmark; +typedef void (*BenchmarkFunc)(Benchmark *b, gsize size, gpointer user_data); + +struct _Benchmark +{ + char *name; + gint64 start_time; + gint64 end_time; + gsize size; + BenchmarkFunc func; + guint profile : 1; + gpointer data; +}; + +static void +benchmark_destroy (Benchmark *b) +{ + g_free (b->name); +} + +static void +benchmark_start (Benchmark *b) +{ + b->start_time = g_get_monotonic_time (); + + if (b->profile) + CALLGRIND_START_INSTRUMENTATION; +} + +static void +benchmark_stop (Benchmark *b) +{ + if (b->profile) + CALLGRIND_STOP_INSTRUMENTATION; + + b->end_time = g_get_monotonic_time (); +} + +typedef struct +{ + GArray *benchmarks; + char *profile_benchmark_name; +} BenchmarkSuite; + +static void +benchmark_suite_init (BenchmarkSuite *bs, + const char *profile_benchmark_name) +{ + bs->benchmarks = g_array_new (FALSE, TRUE, sizeof (Benchmark)); + g_array_set_clear_func (bs->benchmarks, (GDestroyNotify)benchmark_destroy); + bs->profile_benchmark_name = (char *)profile_benchmark_name; // XXX strdup + + g_assert (SAMPLE_SIZE % 2 == 1); +} + +static void +benchmark_suite_add (BenchmarkSuite *bs, + const char *benchmark_name, + gsize size, + BenchmarkFunc benchmark_func, + gpointer user_data) +{ + Benchmark *b; + + g_array_set_size (bs->benchmarks, bs->benchmarks->len + 1); + b = &g_array_index (bs->benchmarks, Benchmark, bs->benchmarks->len - 1); + + b->name = (char *)benchmark_name; /* XXX strdup? */ + b->size = size; + b->func = benchmark_func; + b->data = user_data; +} + +static int +benchmark_suite_run (BenchmarkSuite *bs) +{ + const guint n_benchmarks = bs->benchmarks->len; + const gboolean profile = bs->profile_benchmark_name != NULL; + guint i; + + if (profile) + { + /* For profiling, we only run the selected benchmark. */ + gboolean found = FALSE; + + + for (i = 0; i < n_benchmarks; i ++) + { + Benchmark *b = &g_array_index (bs->benchmarks, Benchmark, i); + + if (strcmp (bs->profile_benchmark_name, b->name) == 0) + { + b->profile = TRUE; + b->func (b, b->size, b->data); + found = TRUE; + break; + } + } + + if (!found) + g_error ("No benchmark '%s' found", bs->profile_benchmark_name); + } + else + { + for (i = 0; i < n_benchmarks; i ++) + { + gint64 samples[SAMPLE_SIZE]; + Benchmark *b = &g_array_index (bs->benchmarks, Benchmark, i); + int s, x, y; + + for (s = 0; s < SAMPLE_SIZE; s ++) + { + b->start_time = 0; + b->end_time = 0; + + b->func (b, b->size, b->data); + + if (b->start_time == 0) + g_error ("Benchmark '%s' did not call benchmark_start()", b->name); + + if (b->end_time == 0) + g_error ("Benchmark '%s' did not call benchmark_stop()", b->name); + + samples[s] = b->end_time - b->start_time; + } + + /* Bubble sort \o/ */ + for (x = 0; x < SAMPLE_SIZE; x ++) + for (y = 0; y < SAMPLE_SIZE; y ++) + if (samples[x] < samples[y]) + { + int k = samples[x]; + samples[x] = samples[y]; + samples[y] = k; + } + + /* Median of SAMPLE_SIZE */ + printf ("%s (%" G_GSIZE_FORMAT ") | %.2f\n", b->name, b->size, + samples[SAMPLE_SIZE / 2 + 1] / 1000.0); + } + } + + return 0; +} + +#endif diff --git a/benchmarks/containers.c b/benchmarks/containers.c new file mode 100644 index 0000000000..567c0977dd --- /dev/null +++ b/benchmarks/containers.c @@ -0,0 +1,237 @@ +#include +#include "benchmark.h" + +/* Command line options */ +const char *profile_benchmark_name = NULL; + +static GOptionEntry options[] = { + { "profile", 'p', 0, G_OPTION_ARG_STRING, &profile_benchmark_name, "Benchmark name to profile using callgrind", NULL }, + { NULL } +}; + +typedef struct +{ + GType type; +} ContainerData; + +/* + * PROFILING: + * + * valgrind --tool=callgrind --instr-atstart=no benchmarks/name --profile="benchmark name" + * + * ENJOY. + */ + +static void +container_create_benchmark (Benchmark *b, + gsize size, + gpointer user_data) +{ + ContainerData *data = user_data; + guint i; + GtkWidget **widgets = g_malloc (sizeof (GtkWidget*) * size); + + benchmark_start (b); + for (i = 0; i < size; i ++) + widgets[i] = g_object_new (data->type, NULL); + benchmark_stop (b); + + g_free (widgets); +} + +static void +container_destroy_benchmark (Benchmark *b, + gsize size, + gpointer user_data) +{ + ContainerData *data = user_data; + guint i; + GtkWidget **widgets = g_malloc (sizeof (GtkWidget*) * size); + + for (i = 0; i < size; i ++) + { + widgets[i] = g_object_new (data->type, NULL); + g_object_ref_sink (widgets[i]); + } + + benchmark_start (b); + for (i = 0; i < size; i ++) + g_object_unref (widgets[i]); + benchmark_stop (b); + + g_free (widgets); +} + +static void +container_add_benchmark (Benchmark *b, + gsize size, + gpointer user_data) +{ + ContainerData *data = user_data; + guint i; + GtkWidget *container; + GtkWidget **buttons = g_malloc (sizeof (GtkWidget*) * size); + + for (i = 0; i < size; i ++) + buttons[i] = gtk_button_new (); + + container = g_object_new (data->type, NULL); + + benchmark_start (b); + + for (i = 0; i < size; i ++) + gtk_container_add ((GtkContainer *)container, buttons[i]); + + benchmark_stop (b); + + g_free (buttons); +} + +static void +container_remove_benchmark (Benchmark *b, + gsize size, + gpointer user_data) +{ + ContainerData *data = user_data; + guint i; + GtkWidget *container; + GtkWidget **buttons = g_malloc (sizeof (GtkWidget*) * size); + + for (i = 0; i < size; i ++) + { + buttons[i] = gtk_button_new (); + /* We add an extra ref here so the later remove() does NOT dispose the buttons. */ + g_object_ref_sink (buttons[i]); + g_object_ref (buttons[i]); + } + + container = g_object_new (data->type, NULL); + + for (i = 0; i < size; i ++) + gtk_container_add ((GtkContainer *)container, buttons[i]); + + benchmark_start (b); + + for (i = 0; i < size; i ++) + gtk_container_remove ((GtkContainer *)container, buttons[i]); + + benchmark_stop (b); + + g_free (buttons); +} + +static void +container_measure_benchmark (Benchmark *b, + gsize size, + gpointer user_data) +{ + ContainerData *data = user_data; + guint i; + GtkWidget *container; + GtkWidget **buttons = g_malloc (sizeof (GtkWidget*) * size); + + for (i = 0; i < size; i ++) + buttons[i] = gtk_button_new (); + + container = g_object_new (data->type, NULL); + + for (i = 0; i < size; i ++) + gtk_container_add ((GtkContainer *)container, buttons[i]); + + benchmark_start (b); + + gtk_widget_measure (container, GTK_ORIENTATION_HORIZONTAL, -1, + NULL, NULL, NULL, NULL); + + benchmark_stop (b); + + g_free (buttons); +} + +static void +container_allocate_benchmark (Benchmark *b, + gsize size, + gpointer user_data) +{ + ContainerData *data = user_data; + guint i; + GtkWidget *container; + GtkWidget **buttons = g_malloc (sizeof (GtkWidget*) * size); + int width, height; + + for (i = 0; i < size; i ++) + buttons[i] = gtk_button_new (); + + container = g_object_new (data->type, NULL); + + for (i = 0; i < size; i ++) + gtk_container_add ((GtkContainer *)container, buttons[i]); + + + gtk_widget_measure (container, GTK_ORIENTATION_HORIZONTAL, -1, + &width, NULL, NULL, NULL); + gtk_widget_measure (container, GTK_ORIENTATION_VERTICAL, width, + &height, NULL, NULL, NULL); + + benchmark_start (b); + gtk_widget_size_allocate (container, + &(GtkAllocation){0, 0, width, height}, + -1); + benchmark_stop (b); + + g_free (buttons); +} + +int +main (int argc, char **argv) +{ + BenchmarkSuite suite; + GOptionContext *option_context; + GError *error = NULL; + const GType types[] = { + GTK_TYPE_BOX, + GTK_TYPE_GRID, + GTK_TYPE_STACK, +/* GTK_TYPE_NOTEBOOK, XXX too slow! :( */ + }; + int i; + int N = 10000; + + option_context = g_option_context_new (""); + g_option_context_add_main_entries (option_context, options, NULL); + if (!g_option_context_parse (option_context, &argc, &argv, &error)) + { + g_printerr ("Option parsing failed: %s\n", error->message); + return 1; + } + + benchmark_suite_init (&suite, profile_benchmark_name); + gtk_init (); + + for (i = 0; i < G_N_ELEMENTS (types); i ++) + { + ContainerData *data = g_malloc (sizeof (ContainerData)); + data->type = types[i]; + + benchmark_suite_add (&suite, + g_strdup_printf ("%s create", g_type_name (types[i])), + N, container_create_benchmark, data); + benchmark_suite_add (&suite, + g_strdup_printf ("%s destroy", g_type_name (types[i])), + N, container_destroy_benchmark, data); + benchmark_suite_add (&suite, + g_strdup_printf ("%s add", g_type_name (types[i])), + N, container_add_benchmark, data); + benchmark_suite_add (&suite, + g_strdup_printf ("%s remove", g_type_name (types[i])), + N, container_remove_benchmark, data); + benchmark_suite_add (&suite, + g_strdup_printf ("%s measure", g_type_name (types[i])), + N, container_measure_benchmark, data); + benchmark_suite_add (&suite, + g_strdup_printf ("%s allocate", g_type_name (types[i])), + N, container_allocate_benchmark, data); + } + + return benchmark_suite_run (&suite); +} diff --git a/benchmarks/meson.build b/benchmarks/meson.build new file mode 100644 index 0000000000..51397e3d6e --- /dev/null +++ b/benchmarks/meson.build @@ -0,0 +1,18 @@ + +# benchmark name, optional extra sources +gtk_benchmarks = [ + ['containers'], +] + +foreach b : gtk_benchmarks + b_name = b.get(0) + b_sources = ['@0@.c'.format(b_name), b.get(1, [])] + b_exec = executable ( + b_name, + b_sources, + include_directories: [confinc, gdkinc], + dependencies: [libgtk_dep, libm] + ) + benchmark(b_name, b_exec) + +endforeach diff --git a/meson.build b/meson.build index 14ba3df876..8ec64fe024 100644 --- a/meson.build +++ b/meson.build @@ -645,6 +645,7 @@ subdir('gdk') subdir('gsk') subdir('gtk') subdir('modules') +subdir('benchmarks') if get_option('demos') subdir('demos') endif