summaryrefslogtreecommitdiff
path: root/perf/cairo-perf-micro.c
diff options
context:
space:
mode:
authorChris Wilson <chris@chris-wilson.co.uk>2009-09-15 14:22:57 +0100
committerChris Wilson <chris@chris-wilson.co.uk>2009-09-15 18:19:08 +0100
commitccf84a8883ebb8d2ee8f55eb53da4dfc96887de0 (patch)
treee8386343168e0912db1d3762dcecee95cfd2d072 /perf/cairo-perf-micro.c
parent4152cd90e25ffa9d2a3683c790bd2891a58c52de (diff)
downloadcairo-ccf84a8883ebb8d2ee8f55eb53da4dfc96887de0.tar.gz
[perf] Reorganise cairo-perf
In preparation to creating a new hub to control performance measuring and reporting, move the current cairo-perf out of the way.
Diffstat (limited to 'perf/cairo-perf-micro.c')
-rw-r--r--perf/cairo-perf-micro.c596
1 files changed, 596 insertions, 0 deletions
diff --git a/perf/cairo-perf-micro.c b/perf/cairo-perf-micro.c
new file mode 100644
index 000000000..f2699feb4
--- /dev/null
+++ b/perf/cairo-perf-micro.c
@@ -0,0 +1,596 @@
+/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
+/*
+ * Copyright © 2006 Mozilla Corporation
+ * Copyright © 2006 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of
+ * the authors not be used in advertising or publicity pertaining to
+ * distribution of the software without specific, written prior
+ * permission. The authors make no representations about the
+ * suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors: Vladimir Vukicevic <vladimir@pobox.com>
+ * Carl Worth <cworth@cworth.org>
+ */
+
+#define _GNU_SOURCE 1 /* for sched_getaffinity() */
+
+#include "../cairo-version.h" /* for the real version */
+
+#include "cairo-perf.h"
+#include "cairo-stats.h"
+
+#include "cairo-boilerplate-getopt.h"
+
+/* For basename */
+#ifdef HAVE_LIBGEN_H
+#include <libgen.h>
+#endif
+
+#if HAVE_FCFINI
+#include <fontconfig/fontconfig.h>
+#endif
+
+#ifdef HAVE_SCHED_H
+#include <sched.h>
+#endif
+
+#define CAIRO_PERF_ITERATIONS_DEFAULT 100
+#define CAIRO_PERF_LOW_STD_DEV 0.03
+#define CAIRO_PERF_STABLE_STD_DEV_COUNT 5
+#define CAIRO_PERF_ITERATION_MS_DEFAULT 2000
+#define CAIRO_PERF_ITERATION_MS_FAST 5
+
+typedef struct _cairo_perf_case {
+ CAIRO_PERF_DECL (*run);
+ unsigned int min_size;
+ unsigned int max_size;
+} cairo_perf_case_t;
+
+const cairo_perf_case_t perf_cases[];
+
+/* Some targets just aren't that interesting for performance testing,
+ * (not least because many of these surface types use a meta-surface
+ * and as such defer the "real" rendering to later, so our timing
+ * loops wouldn't count the real work, just the recording by the
+ * meta-surface. */
+static cairo_bool_t
+target_is_measurable (const cairo_boilerplate_target_t *target)
+{
+ switch ((int) target->expected_type) {
+ case CAIRO_SURFACE_TYPE_IMAGE:
+ if (strcmp (target->name, "pdf") == 0 ||
+ strcmp (target->name, "ps") == 0)
+ {
+ return FALSE;
+ }
+ else
+ {
+ return TRUE;
+ }
+ case CAIRO_SURFACE_TYPE_XLIB:
+ if (strcmp (target->name, "xlib-fallback") == 0 ||
+ strcmp (target->name, "xlib-reference") == 0)
+ {
+ return FALSE;
+ }
+ else
+ {
+ return TRUE;
+ }
+ case CAIRO_SURFACE_TYPE_XCB:
+ case CAIRO_SURFACE_TYPE_GLITZ:
+ case CAIRO_SURFACE_TYPE_QUARTZ:
+ case CAIRO_SURFACE_TYPE_WIN32:
+ case CAIRO_SURFACE_TYPE_BEOS:
+ case CAIRO_SURFACE_TYPE_DIRECTFB:
+#if CAIRO_VERSION > CAIRO_VERSION_ENCODE(1,1,2)
+ case CAIRO_SURFACE_TYPE_OS2:
+#endif
+#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,9,4)
+ case CAIRO_SURFACE_TYPE_QT:
+#endif
+#if CAIRO_HAS_GL_SURFACE
+ case CAIRO_SURFACE_TYPE_GL:
+#endif
+#if CAIRO_HAS_DRM_SURFACE
+ case CAIRO_SURFACE_TYPE_DRM:
+#endif
+#if CAIRO_HAS_SKIA_SURFACE
+ case CAIRO_SURFACE_TYPE_SKIA:
+#endif
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+static const char *
+_content_to_string (cairo_content_t content, cairo_bool_t similar)
+{
+ switch (content|similar) {
+ case CAIRO_CONTENT_COLOR:
+ return "rgb";
+ case CAIRO_CONTENT_COLOR|1:
+ return "rgb&";
+ case CAIRO_CONTENT_ALPHA:
+ return "a";
+ case CAIRO_CONTENT_ALPHA|1:
+ return "a&";
+ case CAIRO_CONTENT_COLOR_ALPHA:
+ return "rgba";
+ case CAIRO_CONTENT_COLOR_ALPHA|1:
+ return "rgba&";
+ default:
+ return "<unknown_content>";
+ }
+}
+
+static cairo_bool_t
+cairo_perf_has_similar (cairo_perf_t *perf)
+{
+ cairo_surface_t *target;
+
+ if (getenv ("CAIRO_TEST_SIMILAR") == NULL)
+ return FALSE;
+
+ /* exclude the image backend */
+ target = cairo_get_target (perf->cr);
+ if (cairo_surface_get_type (target) == CAIRO_SURFACE_TYPE_IMAGE)
+ return FALSE;
+
+ return TRUE;
+}
+
+cairo_bool_t
+cairo_perf_can_run (cairo_perf_t *perf,
+ const char *name,
+ cairo_bool_t *is_explicit)
+{
+ unsigned int i;
+
+ if (is_explicit)
+ *is_explicit = FALSE;
+
+ if (perf->num_names == 0)
+ return TRUE;
+
+ for (i = 0; i < perf->num_names; i++) {
+ if (strstr (name, perf->names[i])) {
+ if (is_explicit)
+ *is_explicit = FALSE;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static unsigned
+cairo_perf_calibrate (cairo_perf_t *perf,
+ cairo_perf_func_t perf_func)
+{
+ cairo_perf_ticks_t calibration0, calibration;
+ unsigned loops, min_loops;
+
+ calibration0 = perf_func (perf->cr, perf->size, perf->size, 1);
+ if (perf->fast_and_sloppy) {
+ calibration = calibration0;
+ } else {
+ loops = cairo_perf_ticks_per_second () / 100 / calibration0;
+ if (loops < 3)
+ loops = 3;
+ calibration = (calibration0 + perf_func (perf->cr, perf->size, perf->size, loops)) / (loops + 1);
+ }
+
+ /* XXX
+ * Compute the number of loops required for the timing
+ * interval to be perf->ms_per_iteration milliseconds. This
+ * helps to eliminate sampling variance due to timing and
+ * other systematic errors. However, it also hides
+ * synchronisation overhead as we attempt to process a large
+ * batch of identical operations in a single shot. This can be
+ * considered both good and bad... It would be good to perform
+ * a more rigorous analysis of the synchronisation overhead,
+ * that is to estimate the time for loop=0.
+ */
+ loops = perf->ms_per_iteration * 0.001 * cairo_perf_ticks_per_second () / calibration;
+ min_loops = perf->fast_and_sloppy ? 1 : 10;
+ if (loops < min_loops)
+ loops = min_loops;
+
+ return loops;
+}
+
+void
+cairo_perf_run (cairo_perf_t *perf,
+ const char *name,
+ cairo_perf_func_t perf_func)
+{
+ static cairo_bool_t first_run = TRUE;
+ unsigned int i, similar, has_similar;
+ cairo_perf_ticks_t *times;
+ cairo_stats_t stats = {0.0, 0.0};
+ int low_std_dev_count;
+
+ if (perf->list_only) {
+ printf ("%s\n", name);
+ return;
+ }
+
+ if (first_run) {
+ if (perf->raw) {
+ printf ("[ # ] %s.%-s %s %s %s ...\n",
+ "backend", "content", "test-size", "ticks-per-ms", "time(ticks)");
+ }
+
+ if (perf->summary) {
+ fprintf (perf->summary,
+ "[ # ] %8s.%-4s %28s %8s %8s %5s %5s %s %s\n",
+ "backend", "content", "test-size", "min(ticks)", "min(ms)", "median(ms)",
+ "stddev.", "iterations", "overhead");
+ }
+ first_run = FALSE;
+ }
+
+ times = perf->times;
+
+ if (getenv ("CAIRO_PERF_OUTPUT") != NULL) { /* check output */
+ char *filename;
+ cairo_status_t status;
+
+ xasprintf (&filename, "%s.%s.%s.%d.out.png",
+ name, perf->target->name,
+ _content_to_string (perf->target->content, 0),
+ perf->size);
+ perf_func (perf->cr, perf->size, perf->size, 1);
+ status = cairo_surface_write_to_png (cairo_get_target (perf->cr), filename);
+ if (status) {
+ fprintf (stderr, "Failed to generate output check '%s': %s\n",
+ filename, cairo_status_to_string (status));
+ return;
+ }
+
+ free (filename);
+ }
+
+ has_similar = cairo_perf_has_similar (perf);
+ for (similar = 0; similar <= has_similar; similar++) {
+ unsigned loops;
+
+ if (perf->summary) {
+ fprintf (perf->summary,
+ "[%3d] %8s.%-5s %26s.%-3d ",
+ perf->test_number, perf->target->name,
+ _content_to_string (perf->target->content, similar),
+ name, perf->size);
+ fflush (perf->summary);
+ }
+
+ /* We run one iteration in advance to warm caches and calibrate. */
+ cairo_perf_yield ();
+ if (similar)
+ cairo_push_group_with_content (perf->cr,
+ cairo_boilerplate_content (perf->target->content));
+ perf_func (perf->cr, perf->size, perf->size, 1);
+ loops = cairo_perf_calibrate (perf, perf_func);
+ if (similar)
+ cairo_pattern_destroy (cairo_pop_group (perf->cr));
+
+ low_std_dev_count = 0;
+ for (i =0; i < perf->iterations; i++) {
+ cairo_perf_yield ();
+ if (similar)
+ cairo_push_group_with_content (perf->cr,
+ cairo_boilerplate_content (perf->target->content));
+ times[i] = perf_func (perf->cr, perf->size, perf->size, loops) / loops;
+ if (similar)
+ cairo_pattern_destroy (cairo_pop_group (perf->cr));
+
+ if (perf->raw) {
+ if (i == 0)
+ printf ("[*] %s.%s %s.%d %g",
+ perf->target->name,
+ _content_to_string (perf->target->content, similar),
+ name, perf->size,
+ cairo_perf_ticks_per_second () / 1000.0);
+ printf (" %lld", (long long) times[i]);
+ } else if (! perf->exact_iterations) {
+ if (i > 0) {
+ _cairo_stats_compute (&stats, times, i+1);
+
+ if (stats.std_dev <= CAIRO_PERF_LOW_STD_DEV)
+ {
+ low_std_dev_count++;
+ if (low_std_dev_count >= CAIRO_PERF_STABLE_STD_DEV_COUNT)
+ break;
+ } else {
+ low_std_dev_count = 0;
+ }
+ }
+ }
+ }
+
+ if (perf->raw)
+ printf ("\n");
+
+ if (perf->summary) {
+ _cairo_stats_compute (&stats, times, i);
+ fprintf (perf->summary,
+ "%10lld %#8.3f %#8.3f %#5.2f%% %3d\n",
+ (long long) stats.min_ticks,
+ (stats.min_ticks * 1000.0) / cairo_perf_ticks_per_second (),
+ (stats.median_ticks * 1000.0) / cairo_perf_ticks_per_second (),
+ stats.std_dev * 100.0, stats.iterations);
+ fflush (perf->summary);
+ }
+
+ perf->test_number++;
+ }
+}
+
+static void
+usage (const char *argv0)
+{
+ fprintf (stderr,
+"Usage: %s [-l] [-r] [-v] [-i iterations] [test-names ...]\n"
+" %s -l\n"
+"\n"
+"Run the cairo performance test suite over the given tests (all by default)\n"
+"The command-line arguments are interpreted as follows:\n"
+"\n"
+" -r raw; display each time measurement instead of summary statistics\n"
+" -v verbose; in raw mode also show the summaries\n"
+" -i iterations; specify the number of iterations per test case\n"
+" -l list only; just list selected test case names without executing\n"
+"\n"
+"If test names are given they are used as sub-string matches so a command\n"
+"such as \"cairo-perf text\" can be used to run all text test cases.\n",
+ argv0, argv0);
+}
+
+static void
+parse_options (cairo_perf_t *perf, int argc, char *argv[])
+{
+ int c;
+ const char *iters;
+ const char *ms = NULL;
+ char *end;
+ int verbose = 0;
+
+ if ((iters = getenv("CAIRO_PERF_ITERATIONS")) && *iters)
+ perf->iterations = strtol(iters, NULL, 0);
+ else
+ perf->iterations = CAIRO_PERF_ITERATIONS_DEFAULT;
+ perf->exact_iterations = 0;
+
+ perf->fast_and_sloppy = FALSE;
+ perf->ms_per_iteration = CAIRO_PERF_ITERATION_MS_DEFAULT;
+ if ((ms = getenv("CAIRO_PERF_ITERATION_MS")) && *ms) {
+ perf->ms_per_iteration = atof(ms);
+ }
+
+ perf->raw = FALSE;
+ perf->list_only = FALSE;
+ perf->names = NULL;
+ perf->num_names = 0;
+ perf->summary = stdout;
+
+ while (1) {
+ c = _cairo_getopt (argc, argv, "i:lrvf");
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'i':
+ perf->exact_iterations = TRUE;
+ perf->iterations = strtoul (optarg, &end, 10);
+ if (*end != '\0') {
+ fprintf (stderr, "Invalid argument for -i (not an integer): %s\n",
+ optarg);
+ exit (1);
+ }
+ break;
+ case 'l':
+ perf->list_only = TRUE;
+ break;
+ case 'r':
+ perf->raw = TRUE;
+ perf->summary = NULL;
+ break;
+ case 'f':
+ perf->fast_and_sloppy = TRUE;
+ if (ms == NULL)
+ perf->ms_per_iteration = CAIRO_PERF_ITERATION_MS_FAST;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ fprintf (stderr, "Internal error: unhandled option: %c\n", c);
+ /* fall-through */
+ case '?':
+ usage (argv[0]);
+ exit (1);
+ }
+ }
+
+ if (verbose && perf->summary == NULL)
+ perf->summary = stderr;
+
+ if (optind < argc) {
+ perf->names = &argv[optind];
+ perf->num_names = argc - optind;
+ }
+}
+
+static int
+check_cpu_affinity(void)
+{
+#ifdef HAVE_SCHED_GETAFFINITY
+
+ cpu_set_t affinity;
+ int i, cpu_count;
+
+ if (sched_getaffinity(0, sizeof(affinity), &affinity)) {
+ perror("sched_getaffinity");
+ return -1;
+ }
+
+ for(i = 0, cpu_count = 0; i < CPU_SETSIZE; ++i) {
+ if (CPU_ISSET(i, &affinity))
+ ++cpu_count;
+ }
+
+ if (cpu_count > 1) {
+ fputs(
+ "WARNING: cairo-perf has not been bound to a single CPU.\n",
+ stderr);
+ return -1;
+ }
+
+ return 0;
+#else
+ fputs(
+ "WARNING: Cannot check CPU affinity for this platform.\n",
+ stderr);
+ return -1;
+#endif
+}
+
+static void
+cairo_perf_fini (cairo_perf_t *perf)
+{
+ cairo_boilerplate_free_targets (perf->targets);
+ free (perf->times);
+ cairo_debug_reset_static_data ();
+#if HAVE_FCFINI
+ FcFini ();
+#endif
+}
+
+
+int
+main (int argc, char *argv[])
+{
+ int i, j;
+ cairo_perf_t perf;
+ cairo_surface_t *surface;
+
+ parse_options (&perf, argc, argv);
+
+ if (check_cpu_affinity()) {
+ fputs(
+ "NOTICE: cairo-perf and the X server should be bound to CPUs (either the same\n"
+ "or separate) on SMP systems. Not doing so causes random results when the X\n"
+ "server is moved to or from cairo-perf's CPU during the benchmarks:\n"
+ "\n"
+ " $ sudo taskset -cp 0 $(pidof X)\n"
+ " $ taskset -cp 1 $$\n"
+ "\n"
+ "See taskset(1) for information about changing CPU affinity.\n",
+ stderr);
+ }
+
+ perf.targets = cairo_boilerplate_get_targets (&perf.num_targets, NULL);
+ perf.times = xmalloc (perf.iterations * sizeof (cairo_perf_ticks_t));
+
+ for (i = 0; i < perf.num_targets; i++) {
+ const cairo_boilerplate_target_t *target = perf.targets[i];
+
+ if (! target_is_measurable (target))
+ continue;
+
+ perf.target = target;
+ perf.test_number = 0;
+
+ for (j = 0; perf_cases[j].run; j++) {
+ const cairo_perf_case_t *perf_case = &perf_cases[j];
+
+ for (perf.size = perf_case->min_size;
+ perf.size <= perf_case->max_size;
+ perf.size *= 2)
+ {
+ void *closure;
+
+ surface = (target->create_surface) (NULL,
+ target->content,
+ perf.size, perf.size,
+ perf.size, perf.size,
+ CAIRO_BOILERPLATE_MODE_PERF,
+ 0,
+ &closure);
+ if (surface == NULL) {
+ fprintf (stderr,
+ "Error: Failed to create target surface: %s\n",
+ target->name);
+ continue;
+ }
+
+ cairo_perf_timer_set_synchronize (target->synchronize, closure);
+
+ perf.cr = cairo_create (surface);
+
+ perf_case->run (&perf, perf.cr, perf.size, perf.size);
+
+ if (cairo_status (perf.cr)) {
+ fprintf (stderr, "Error: Test left cairo in an error state: %s\n",
+ cairo_status_to_string (cairo_status (perf.cr)));
+ }
+
+ cairo_destroy (perf.cr);
+ cairo_surface_destroy (surface);
+
+ if (target->cleanup)
+ target->cleanup (closure);
+ }
+ }
+ }
+
+ cairo_perf_fini (&perf);
+
+ return 0;
+}
+
+const cairo_perf_case_t perf_cases[] = {
+ { paint, 64, 512},
+ { paint_with_alpha, 64, 512},
+ { fill, 64, 512},
+ { stroke, 64, 512},
+ { text, 64, 512},
+ { glyphs, 64, 512},
+ { mask, 64, 512},
+ { tessellate, 100, 100},
+ { subimage_copy, 16, 512},
+ { pattern_create_radial, 16, 16},
+ { zrusin, 415, 415},
+ { world_map, 800, 800},
+ { box_outline, 100, 100},
+ { mosaic, 800, 800 },
+ { long_lines, 100, 100},
+ { unaligned_clip, 100, 100},
+ { rectangles, 512, 512},
+ { rounded_rectangles, 512, 512},
+ { long_dashed_lines, 512, 512},
+ { composite_checker, 16, 512},
+ { twin, 800, 800},
+ { dragon, 1024, 1024 },
+ { pythagoras_tree, 768, 768 },
+ { intersections, 512, 512 },
+ { spiral, 512, 512 },
+ { NULL }
+};