summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2022-05-18 12:17:13 -0400
committerMatthias Clasen <mclasen@redhat.com>2022-05-18 14:06:11 -0400
commitc3152630a107665c35db438f5e1707455b674942 (patch)
tree7f57c2990b159b7f57b9f72be735ab39b73faaaf
parenta2ccd36a42e039d3600b04fe37fdc47f267d90c7 (diff)
downloadpango-c3152630a107665c35db438f5e1707455b674942.tar.gz
Add a performance test for PangoLayout
This is just some initial work, inspired by the gobject performance test in glib.
-rw-r--r--tests/layout-performance.c472
-rw-r--r--tests/meson.build7
2 files changed, 479 insertions, 0 deletions
diff --git a/tests/layout-performance.c b/tests/layout-performance.c
new file mode 100644
index 00000000..f9427d5c
--- /dev/null
+++ b/tests/layout-performance.c
@@ -0,0 +1,472 @@
+#include <pango/pango.h>
+#include <pango/pangocairo.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 int test_length = DEFAULT_TEST_TIME;
+static char *filename;
+
+static GOptionEntry cmd_entries[] = {
+ {"data", 0, 0, G_OPTION_ARG_FILENAME, &filename, "Test data", "FILE"},
+ {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
+ "Print extra information", NULL},
+ {"seconds", 's', 0, G_OPTION_ARG_INT, &test_length,
+ "Time to run each test in seconds", NULL},
+ { NULL, 0, },
+};
+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;
+
+ 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");
+ }
+
+ /* 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: %.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 */
+ test->print_result (test, data, min_elapsed);
+
+ /* Tear down */
+ test->teardown (test, data);
+ g_timer_destroy (timer);
+}
+
+const char default_text[] =
+ "'Twas brillig, and the slithy toves\n"
+ "Did gyre and gimble in the wabe;\n"
+ "All mimsy were the borogoves,\n"
+ "And the mome raths outgrabe.\n"
+ "\n"
+ "'Beware the Jabberwock, my son!\n"
+ "The jaws that bite, the claws that catch!\n"
+ "Beware the Jubjub bird, and shun\n"
+ "The frumious Bandersnatch!'";
+
+static void
+get_test_text (char **text,
+ PangoAttrList **attrs)
+{
+ if (filename)
+ {
+ char *buffer;
+ gsize len;
+ GError *error = NULL;
+
+ if (!g_file_get_contents (filename, &buffer, &len, &error))
+ {
+ g_error ("%s", error->message);
+ exit (1);
+ }
+
+ if (!pango_parse_markup (buffer, len, 0, attrs, text, NULL, &error))
+ {
+ g_error ("%s", error->message);
+ exit (1);
+ }
+
+ return;
+ }
+
+ *text = g_strdup (default_text);
+ *attrs = NULL;
+}
+
+#define NUM_OBJECT_TO_CONSTRUCT 10000
+
+typedef struct _CreateTest CreateTest;
+struct _CreateTest {
+ PangoContext *context;
+ PangoLayout **layouts;
+ int n_layouts;
+ char *text;
+ PangoAttrList *attrs;
+};
+
+static gpointer
+test_create_setup (PerformanceTest *test)
+{
+ CreateTest *data;
+ PangoFontMap *fontmap;
+
+ data = g_new0 (CreateTest, 1);
+
+ fontmap = pango_cairo_font_map_get_default ();
+ data->context = pango_font_map_create_context (fontmap);
+
+ get_test_text (&data->text, &data->attrs);
+
+ return data;
+}
+
+static void
+test_create_init (PerformanceTest *test,
+ gpointer _data,
+ double count_factor)
+{
+ CreateTest *data = _data;
+ int n;
+
+ n = NUM_OBJECT_TO_CONSTRUCT * count_factor;
+ if (data->n_layouts != n)
+ {
+ data->n_layouts = n;
+ data->layouts = g_new (PangoLayout *, n);
+ }
+}
+
+static void
+test_create_run (PerformanceTest *test,
+ gpointer _data)
+{
+ CreateTest *data = _data;
+ PangoContext *context = data->context;
+ PangoLayout **layouts = data->layouts;
+ int n_layouts = data->n_layouts;
+ int i;
+
+ for (i = 0; i < n_layouts; i++)
+ {
+ layouts[i] = pango_layout_new (context);
+ pango_layout_set_text (layouts[i], data->text, -1);
+ pango_layout_set_attributes (layouts[i], data->attrs);
+ }
+}
+
+static void
+test_create_finish (PerformanceTest *test,
+ gpointer _data)
+{
+ CreateTest *data = _data;
+ int i;
+
+ for (i = 0; i < data->n_layouts; i++)
+ g_object_unref (data->layouts[i]);
+}
+
+static void
+test_create_teardown (PerformanceTest *test,
+ gpointer _data)
+{
+ CreateTest *data = _data;
+ g_object_unref (data->context);
+ g_free (data->layouts);
+ g_free (data->text);
+ pango_attr_list_unref (data->attrs);
+ g_free (data);
+}
+
+static void
+test_create_print_result (PerformanceTest *test,
+ gpointer _data,
+ double time)
+{
+ CreateTest *data = _data;
+
+ g_print ("Millions of constructed layouts per second: %.3f\n",
+ data->n_layouts / (time * 1000000));
+}
+
+/* Test shaping */
+
+typedef struct _LayoutTest LayoutTest;
+struct _LayoutTest {
+ PangoContext *context;
+ PangoLayout *layout;
+ int n_layouts;
+};
+
+static gpointer
+test_layout_setup (PerformanceTest *test)
+{
+ LayoutTest *data;
+ PangoFontMap *fontmap;
+ char *text;
+ PangoAttrList *attrs;
+
+ data = g_new0 (LayoutTest, 1);
+
+ fontmap = pango_cairo_font_map_get_default ();
+ data->context = pango_font_map_create_context (fontmap);
+
+ data->layout = pango_layout_new (data->context);
+
+ get_test_text (&text, &attrs);
+
+ pango_layout_set_text (data->layout, text, -1);
+ pango_layout_set_attributes (data->layout, attrs);
+
+ g_free (text);
+ pango_attr_list_unref (attrs);
+
+ return data;
+}
+
+static void
+test_layout_init (PerformanceTest *test,
+ gpointer _data,
+ double count_factor)
+{
+ LayoutTest *data = _data;
+ int n;
+
+ n = NUM_OBJECT_TO_CONSTRUCT * count_factor;
+ if (data->n_layouts != n)
+ data->n_layouts = n;
+}
+
+static void
+test_ellipsis_init (PerformanceTest *test,
+ gpointer _data,
+ double count_factor)
+{
+ LayoutTest *data = _data;
+
+ test_layout_init (test, _data, count_factor);
+
+ pango_layout_set_ellipsize (data->layout, PANGO_ELLIPSIZE_MIDDLE);
+}
+
+static void
+test_layout_run (PerformanceTest *test,
+ gpointer _data)
+{
+ LayoutTest *data = _data;
+ PangoLayout *layout = data->layout;
+ int n_layouts = data->n_layouts;
+ int i;
+ int width;
+
+ for (i = 0; i < n_layouts; i++)
+ {
+ width = g_random_int_range (50, 200);
+ pango_layout_set_width (layout, width * PANGO_SCALE);
+ if (pango_layout_get_line_count (layout) <= 0)
+ g_printerr ("layout produced no lines\n");
+ }
+}
+
+static void
+test_layout_finish (PerformanceTest *test,
+ gpointer _data)
+{
+}
+
+static void
+test_layout_teardown (PerformanceTest *test,
+ gpointer _data)
+{
+ LayoutTest *data = _data;
+ g_object_unref (data->layout);
+ g_object_unref (data->context);
+ g_free (data);
+}
+
+static void
+test_layout_print_result (PerformanceTest *test,
+ gpointer _data,
+ double time)
+{
+ LayoutTest *data = _data;
+
+ g_print ("Re-layouts per second: %.3f\n",
+ data->n_layouts / (time));
+}
+
+static PerformanceTest tests[] = {
+ {
+ "create-layout",
+ 0,
+ test_create_setup,
+ test_create_init,
+ test_create_run,
+ test_create_finish,
+ test_create_teardown,
+ test_create_print_result
+ },
+ {
+ "shape-layout",
+ 0,
+ test_layout_setup,
+ test_layout_init,
+ test_layout_run,
+ test_layout_finish,
+ test_layout_teardown,
+ test_layout_print_result
+ },
+ {
+ "shape-with-ellipsis",
+ 0,
+ test_layout_setup,
+ test_ellipsis_init,
+ test_layout_run,
+ test_layout_finish,
+ test_layout_teardown,
+ test_layout_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;
+ int i;
+
+ context = g_option_context_new ("Pango 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 (argc > 1)
+ {
+ for (i = 1; i < argc; i++)
+ {
+ test = find_test (argv[i]);
+ if (test)
+ run_test (test);
+ else
+ g_printerr ("No such test: %s\n", argv[i]);
+ }
+ }
+ else
+ {
+ gsize k;
+ for (k = 0; k < G_N_ELEMENTS (tests); k++)
+ run_test (&tests[k]);
+ }
+
+ return 0;
+}
diff --git a/tests/meson.build b/tests/meson.build
index 955dbc99..85edb824 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -304,3 +304,10 @@ foreach t: tests
protocol: 'tap',
)
endforeach
+
+executable('layout-performance', [ 'layout-performance.c' ],
+ dependencies: [ libpango_dep, libpangocairo_dep ],
+ include_directories: root_inc,
+ c_args: common_cflags + pango_debug_cflags + test_cflags,
+ cpp_args: common_cppflags + pango_debug_cflags + test_cflags)
+