diff options
author | Matthias Clasen <mclasen@redhat.com> | 2022-05-18 12:17:13 -0400 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2022-05-18 14:06:11 -0400 |
commit | c3152630a107665c35db438f5e1707455b674942 (patch) | |
tree | 7f57c2990b159b7f57b9f72be735ab39b73faaaf | |
parent | a2ccd36a42e039d3600b04fe37fdc47f267d90c7 (diff) | |
download | pango-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.c | 472 | ||||
-rw-r--r-- | tests/meson.build | 7 |
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) + |