[perf] Benchmark traces

Add a variant of cairo-perf that measures the time to replay traces.
This commit is contained in:
Chris Wilson 2009-06-02 13:08:25 +01:00
parent 403f780b29
commit 7ed050fd43
7 changed files with 605 additions and 3 deletions

View file

@ -145,3 +145,18 @@ xunlink (const char *pathname)
exit (1);
}
}
char *
xstrdup (const char *str)
{
if (str == NULL)
return NULL;
str = strdup (str);
if (str == NULL) {
fprintf (stderr, "Error: Out of memory. Exiting.\n");
exit (1);
}
return (char *) str;
}

View file

@ -48,4 +48,8 @@ xasprintf (char **strp, const char *fmt, ...) CAIRO_BOILERPLATE_PRINTF_FORMAT(2,
void
xunlink (const char *path);
#define xstrdup cairo_boilerplate_xstrdup
char *
xstrdup (const char *str);
#endif

1
perf/.gitignore vendored
View file

@ -1,6 +1,7 @@
TAGS
tags
cairo-perf
cairo-perf-trace
cairo-perf-compare-backends
cairo-perf-diff-files
cairo-perf-graph-files

View file

@ -4,12 +4,14 @@ AM_CPPFLAGS = \
-I$(srcdir) \
-I$(top_srcdir)/boilerplate \
-I$(top_srcdir)/src \
-I$(top_srcdir)/util/cairo-script \
-I$(top_builddir)/src \
$(CAIRO_CFLAGS)
AM_LDFLAGS = $(CAIRO_LDFLAGS)
EXTRA_PROGRAMS += cairo-perf \
cairo-perf-trace \
cairo-perf-diff-files \
cairo-perf-compare-backends \
cairo-perf-graph-files
@ -70,6 +72,21 @@ libcairoperf_la_SOURCES = \
cairo-stats.c \
cairo-stats.h
cairo_perf_trace_SOURCES = \
cairo-perf-trace.c
if CAIRO_HAS_WIN32_SURFACE
cairo_perf_trace_SOURCES += cairo-perf-win32.c
else
if CAIRO_HAS_OS2_SURFACE
cairo_perf_trace_SOURCES += cairo-perf-os2.c
else
cairo_perf_trace_SOURCES += cairo-perf-posix.c
endif
endif
cairo_perf_trace_LDADD = \
$(top_builddir)/util/cairo-script/libcairo-script-interpreter.la \
$(LDADD)
cairo_perf_diff_files_SOURCES = \
cairo-perf-diff-files.c

View file

@ -179,6 +179,37 @@ added:
above, three tests would be performed at sizes of 16x16, 32x32 and
64x64.
How to benchmark traces
-----------------------
Using cairo-trace you can record the exact sequence of graphic operations
made by an application and replay them later. These traces can then be
used to benchmark the various backends and patches.
To record a trace:
$ cairo-trace --no-mark-dirty --no-callers $APPLICATION [$ARGV]
--no-mark-dirty is useful for applications that are paranoid about
surfaces being modified by external plugins outside of their control, the
prime example here is firefox.
--no-callers disables the symbolic caller lookup and so speeds tracing
(dramatically for large c++ programs) and similarly speeds up the replay
as the files are much smaller.
The output file will be called $APPLICATION.$PID.trace, the actual path
written to will be displayed on the terminal.
Then to use cairo-perf-trace:
$ ./cairo-perf-trace $APPLICATION.$PID.trace
Alternatively you can put the trace into perf/traces, or set
CAIRO_TRACE_DIR to point to your trace directory, and the trace will be
included in the performance tests.
If you record an interesting trace, please consider sharing it by compressing
it, LZMA preferred, and posting a link to cairo@cairographics.org.
How to run cairo-perf-diff on WINDOWS
-------------------------------------
This section explains the specifics of running cairo-perf-diff under

View file

@ -122,9 +122,13 @@ test_diff_print (test_diff_t *diff,
double test_time;
double change;
printf ("(%s, size: %d)\n",
diff->tests[0]->name,
diff->tests[0]->size);
if (diff->tests[0]->size != 0) {
printf ("(%s, size: %d)\n",
diff->tests[0]->name,
diff->tests[0]->size);
} else {
printf ("(%s\n", diff->tests[0]->name);
}
for (i = 0; i < diff->num_tests; i++) {
test_time = diff->tests[i]->stats.min_ticks;

530
perf/cairo-perf-trace.c Normal file
View file

@ -0,0 +1,530 @@
/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
/*
* Copyright © 2006 Mozilla Corporation
* Copyright © 2006 Red Hat, Inc.
* Copyright © 2009 Chris Wilson
*
* 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>
* Chris Wilson <chris@chris-wilson.co.uk>
*/
#define _GNU_SOURCE 1 /* for sched_getaffinity() */
#include "cairo-perf.h"
#include "cairo-stats.h"
#include "cairo-boilerplate-getopt.h"
#include <cairo-script-interpreter.h>
/* For basename */
#ifdef HAVE_LIBGEN_H
#include <libgen.h>
#endif
#include <sys/types.h>
#include <dirent.h>
#if HAVE_FCFINI
#include <fontconfig/fontconfig.h>
#endif
#ifdef HAVE_SCHED_H
#include <sched.h>
#endif
#define CAIRO_PERF_ITERATIONS_DEFAULT 10
#define CAIRO_PERF_LOW_STD_DEV 0.3
#define CAIRO_PERF_STABLE_STD_DEV_COUNT 3
/* 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 (cairo_boilerplate_target_t *target)
{
if (target->content != CAIRO_CONTENT_COLOR_ALPHA)
return FALSE;
switch (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)
{
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_MAJOR > 1 || (CAIRO_VERSION_MAJOR == 1 && CAIRO_VERSION_MINOR > 2)
case CAIRO_SURFACE_TYPE_OS2:
#endif
return TRUE;
case CAIRO_SURFACE_TYPE_PDF:
case CAIRO_SURFACE_TYPE_PS:
case CAIRO_SURFACE_TYPE_SVG:
default:
return FALSE;
}
}
cairo_bool_t
cairo_perf_can_run (cairo_perf_t *perf,
const char *name)
{
unsigned int i;
char *copy, *dot;
if (perf->num_names == 0)
return TRUE;
copy = xstrdup (name);
dot = strchr (copy, '.');
if (dot != NULL)
*dot = '\0';
for (i = 0; i < perf->num_names; i++)
if (strstr (copy, perf->names[i]))
break;
free (copy);
return i != perf->num_names;
}
static void
clear_surface (cairo_surface_t *surface)
{
cairo_t *cr = cairo_create (surface);
cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
cairo_paint (cr);
cairo_destroy (cr);
}
static cairo_surface_t *
_similar_surface_create (void *closure,
cairo_content_t content,
double width, double height)
{
return cairo_surface_create_similar (closure, content, width, height);
}
static void
execute (cairo_perf_t *perf,
cairo_script_interpreter_t *csi,
cairo_surface_t *target,
const char *trace)
{
static cairo_bool_t first_run = TRUE;
unsigned int i;
cairo_perf_ticks_t *times;
cairo_stats_t stats = {0.0, 0.0};
int low_std_dev_count;
char *trace_cpy, *name, *dot;
const cairo_script_interpreter_hooks_t hooks = {
.closure = target,
.surface_create = _similar_surface_create
};
trace_cpy = xstrdup (trace);
name = basename (trace_cpy);
dot = strchr (name, '.');
if (dot)
*dot = '\0';
if (perf->list_only) {
printf ("%s\n", name);
free (trace_cpy);
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 %28s %8s %8s %5s %5s %s\n",
"backend", "test", "min(ticks)", "min(ms)", "median(ms)",
"stddev.", "iterations");
}
first_run = FALSE;
}
times = perf->times;
if (perf->summary) {
fprintf (perf->summary,
"[%3d] %8s %26s ",
perf->test_number,
perf->target->name,
name);
fflush (perf->summary);
}
cairo_script_interpreter_install_hooks (csi, &hooks);
low_std_dev_count = 0;
for (i =0; i < perf->iterations; i++) {
cairo_perf_yield ();
cairo_perf_timer_start ();
cairo_script_interpreter_run (csi, trace);
clear_surface (target); /* queue a write to the sync'ed surface */
cairo_perf_timer_stop ();
times[i] = cairo_perf_timer_elapsed ();
if (perf->raw) {
if (i == 0)
printf ("[*] %s.%s %s.%d %g",
perf->target->name,
"rgba",
name,
0,
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++;
free (trace_cpy);
}
static void
usage (const char *argv0)
{
fprintf (stderr,
"Usage: %s [-l] [-r] [-v] [-i iterations] [test-names ... | traces ...]\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-trace firefox\" can be used to run all firefox traces.\n"
"Alternatively, you can specify a list of filenames to execute.\n",
argv0, argv0);
}
static void
parse_options (cairo_perf_t *perf, int argc, char *argv[])
{
int c;
const char *iters;
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->raw = FALSE;
perf->list_only = FALSE;
perf->names = NULL;
perf->num_names = 0;
perf->summary = stdout;
while (1) {
c = _cairo_getopt (argc, argv, "i:lrv");
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 '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);
#if 0 /* XXX */
cairo_debug_reset_static_data ();
#if HAVE_FCFINI
FcFini ();
#endif
#endif
}
static cairo_bool_t
have_trace_filenames (cairo_perf_t *perf)
{
unsigned int i;
if (perf->num_names == 0)
return FALSE;
for (i = 0; i < perf->num_names; i++)
if (access (perf->names[i], R_OK) == 0)
return TRUE;
return FALSE;
}
static void
cairo_perf_trace (cairo_perf_t *perf,
cairo_boilerplate_target_t *target,
cairo_script_interpreter_t *csi,
const char *trace)
{
cairo_surface_t *surface;
void *closure;
surface = (target->create_surface) (NULL,
CAIRO_CONTENT_COLOR_ALPHA,
1, 1,
1, 1,
CAIRO_BOILERPLATE_MODE_PERF,
0,
&closure);
if (surface == NULL) {
fprintf (stderr,
"Error: Failed to create target surface: %s\n",
target->name);
return;
}
cairo_perf_timer_set_synchronize (target->synchronize, closure);
execute (perf, csi, surface, trace);
cairo_surface_destroy (surface);
if (target->cleanup)
target->cleanup (closure);
}
int
main (int argc, char *argv[])
{
cairo_perf_t perf;
cairo_script_interpreter_t *csi;
const char *trace_dir = "traces";
cairo_bool_t names_are_traces;
unsigned int n;
int i;
parse_options (&perf, argc, argv);
if (! perf.list_only && 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\n",
stderr);
}
if (getenv ("CAIRO_TRACE_DIR") != NULL)
trace_dir = getenv ("CAIRO_TRACE_DIR");
perf.targets = cairo_boilerplate_get_targets (&perf.num_targets, NULL);
perf.times = xmalloc (perf.iterations * sizeof (cairo_perf_ticks_t));
csi = cairo_script_interpreter_create ();
/* do we have a list of filenames? */
names_are_traces = have_trace_filenames (&perf);
for (i = 0; i < perf.num_targets; i++) {
cairo_boilerplate_target_t *target = perf.targets[i];
if (! perf.list_only && ! target_is_measurable (target))
continue;
perf.target = target;
perf.test_number = 0;
if (names_are_traces) {
for (n = 0; n < perf.num_names; n++) {
if (access (perf.names[n], R_OK) == 0)
cairo_perf_trace (&perf, target, csi, perf.names[n]);
}
} else {
DIR *dir;
struct dirent *de;
dir = opendir (trace_dir);
if (dir == NULL) {
fprintf (stderr,
"Error: Failed to open trace directory '%s'.\n"
"Have you cloned the trace repository?\n"
" git clone git://anongit.freedesktop.org/~ickle/traces\n"
" cd traces && make\n"
"Or set the env.var CAIRO_TRACE_DIR to point to your traces?\n",
trace_dir);
return 1;
}
while ((de = readdir (dir)) != NULL) {
char *trace;
const char *dot;
dot = strrchr (de->d_name, '.');
if (dot == NULL)
continue;
if (strcmp (dot, ".trace"))
continue;
if (! cairo_perf_can_run (&perf, de->d_name))
continue;
xasprintf (&trace, "%s/%s", trace_dir, de->d_name);
cairo_perf_trace (&perf, target, csi, trace);
free (trace);
}
closedir (dir);
}
if (perf.list_only)
break;
}
cairo_script_interpreter_destroy (csi);
cairo_perf_fini (&perf);
return 0;
}