[cairo-commit] perf/cairo-perf-diff perf/cairo-perf-diff.c
perf/cairo-perf-diff-files.c perf/.gitignore perf/Makefile.am
Carl Worth
cworth at kemper.freedesktop.org
Wed Oct 25 17:15:26 PDT 2006
perf/.gitignore | 2
perf/Makefile.am | 7 ++
perf/cairo-perf-diff | 106 +++++++++++++++++++++++++++++++++++++++++++
perf/cairo-perf-diff-files.c | 0
4 files changed, 112 insertions(+), 3 deletions(-)
New commits:
diff-tree a9f51c29733a5124e0d04d8dcc00d55ff3aeb05a (from c7b9f84744cd3fe4a6c9dead1e128d91f9cfc3cb)
Author: Carl Worth <cworth at cworth.org>
Date: Wed Oct 25 17:15:22 2006 -0700
Add new cairo-perf-diff for finding the performance difference between any 2 revisions.
The old cairo-perf-diff is now named cairo-perf-diff-files, but
the new one calls out to it and can still be used in an identical
way.
The new cairo-perf-diff can also be used to see what the performance
impact of a single commit is like so:
cairo-perf-diff HEAD
or between two commits:
cairo-perf-diff 1.2.4 HEAD
The script is careful to always run the latest cairo-perf program
even when testing old versions of the library. Also, the output
from any given performance run is cached so it gets less painful
to run as the cache gets primed (the cache is in .perf next to
.git).
The script is still a bit fragile in spots. In particular it depends
on cairo-perf being built in advance but doesn't do anythin to ensure
that happens.
diff --git a/perf/.gitignore b/perf/.gitignore
index 5881588..53d720c 100644
--- a/perf/.gitignore
+++ b/perf/.gitignore
@@ -1,5 +1,5 @@
cairo-perf
-cairo-perf-diff
+cairo-perf-diff-files
*.o
*.exe
*.manifest
diff --git a/perf/Makefile.am b/perf/Makefile.am
index 15917f9..addf547 100644
--- a/perf/Makefile.am
+++ b/perf/Makefile.am
@@ -29,7 +29,7 @@ cairo_perf_SOURCES += cairo-perf-posix.c
endif
cairo_perf_diff_SOURCES = \
- cairo-perf-diff.c
+ cairo-perf-diff-files.c
LDADD = $(top_builddir)/boilerplate/libcairoboilerplate.la \
$(top_builddir)/src/libcairo.la
@@ -40,5 +40,8 @@ $(top_builddir)/boilerplate/libcairoboil
$(top_builddir)/src/libcairo.la:
cd $(top_builddir)/src && $(MAKE) $(AM_MAKEFLAGS) libcairo.la
-perf: cairo-perf
+perf: cairo-perf FORCE
./cairo-perf
+
+FORCE:
+
diff --git a/perf/cairo-perf-diff b/perf/cairo-perf-diff
new file mode 100755
index 0000000..509a4e2
--- /dev/null
+++ b/perf/cairo-perf-diff
@@ -0,0 +1,106 @@
+#!/bin/sh
+set -e
+
+usage() {
+ argv0=$(basename $0)
+ echo "Usage:" >&2
+ echo "For comparing files created my cairo-perf:" >&2
+ echo "" >&2
+ echo " $argv0 old.perf new.perf" >&2
+ echo "" >&2
+ echo "For comparing (cached) performance of revisions:" >&2
+ echo "" >&2
+ echo " $argv0 <revision>" >&2
+ echo " $argv0 <rev1> <rev2>" >&2
+ echo "" >&2
+ echo "If given a single revision, compares its results to that of its" >&2
+ echo "(first-parent) predecessor. Otherwise compares the two given revisions." >&2
+ echo "The revisions can be any revision accepted by git. For example:" >&2
+ echo "" >&2
+ echo " $argv0 HEAD # Show impact of latest commit" >&2
+ echo " $argv0 1.2.0 1.2.4 # Compare performance of 1.2.0 to 1.2.4" >&2
+ echo "" >&2
+ echo "The performance results are cached in .perf next to the .git directory." >&2
+ exit 1
+}
+
+if [ $# -eq 1 ]; then
+ old="$1^"
+ new="$1"
+elif [ $# -eq 2 ]; then
+ old="$1"
+ new="$2"
+else
+ usage
+fi
+
+git_setup() {
+ SUBDIRECTORY_OK='Yes'
+ . git-sh-setup
+ CAIRO_DIR=$(dirname $GIT_DIR)
+ CAIRO_PERF_DIR=$CAIRO_DIR/.perf
+}
+
+rev2sha() {
+ rev=$1
+ git rev-parse --verify $rev || ( echo "Cannot reolve $rev to a revision" && exit 1 )
+}
+
+rev2perf() {
+ rev=$1
+ sha=$(rev2sha $rev)
+ echo "$CAIRO_PERF_DIR/$sha.perf"
+}
+
+# Usage: run_cairo_perf_if_not_cached <rev>
+run_cairo_perf_if_not_cached() {
+ rev=$1
+
+ owd=$(pwd)
+ sha=$(rev2sha $rev)
+ perf=$(rev2perf $rev)
+ if [ -e $perf ]; then
+ return 0
+ fi
+ if [ ! -d $CAIRO_PERF_DIR ]; then
+ echo "Creating new perf cache in $CAIRO_PERF_DIR"
+ mkdir $CAIRO_PERF_DIR
+ fi
+ cd $CAIRO_PERF_DIR
+
+ # XXX: OK, The symlink stuff here is really evil. What we really
+ # want is some sort of git-mirror as has been proposed before.
+
+ if [ ! -d build ]; then
+ mkdir build
+ GIT_DIR=build/.git git init-db
+ rm -rf build/.git/objects
+ ln -s $GIT_DIR/objects build/.git/objects
+ rm -rf build/.git/refs
+ ln -s $GIT_DIR/refs build/.git/refs
+ cp $GIT_DIR/HEAD build/.git/HEAD
+ (cd build; git reset --hard; CFLAGS="-O2" ./autogen.sh)
+ fi
+ cd build
+
+ # XXX: This is painful too. Maybe using "git-branch -f" would be easier here
+ git checkout -b tmp-cairo-perf-diff $sha >/dev/null 2>&1 || (git checkout tmp-cairo-perf-diff && git reset --hard $sha)
+ git reset --hard
+ make CFLAGS="-O2" || (rm config.cache && make CFLAGS="-O2")
+ LD_LIBRARY_PATH=$CAIRO_PERF_DIR/build/src/.libs $CAIRO_DIR/perf/.libs/cairo-perf > $perf
+ cd $owd
+}
+
+if [ ! -e $old ]; then
+ git_setup
+ run_cairo_perf_if_not_cached $old
+ old=$(rev2perf $old)
+fi
+
+if [ ! -e $new ]; then
+ git_setup
+ run_cairo_perf_if_not_cached $new
+ new=$(rev2perf $new)
+fi
+
+cairo-perf-diff-files $old $new
diff --git a/perf/cairo-perf-diff-files.c b/perf/cairo-perf-diff-files.c
new file mode 100644
index 0000000..eb2ef9f
--- /dev/null
+++ b/perf/cairo-perf-diff-files.c
@@ -0,0 +1,482 @@
+/*
+ * 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
+ * copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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: Carl Worth <cworth at cworth.org>
+ */
+
+/* We use _GNU_SOURCE for getline. If someone wants to avoid that
+ * dependence they could conditionally provide a custom implementation
+ * of getline instead. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <math.h>
+
+typedef struct _test_report {
+ int id;
+ char *backend;
+ char *content;
+ char *name;
+ int size;
+ double ticks;
+ double time;
+ double std_dev;
+ int iterations;
+} test_report_t;
+
+typedef struct _test_diff {
+ test_report_t *old;
+ test_report_t *new;
+ double speedup;
+} test_diff_t;
+
+typedef struct _cairo_perf_report {
+ const char *name;
+ test_report_t *tests;
+ int tests_size;
+ int tests_count;
+} cairo_perf_report_t;
+
+typedef enum {
+ TEST_REPORT_STATUS_SUCCESS,
+ TEST_REPORT_STATUS_COMMENT,
+ TEST_REPORT_STATUS_ERROR
+} test_report_status_t;
+
+/* Ad-hoc parsing, macros with a strong dependence on the calling
+ * context, and plenty of other ugliness is here. But at least it's
+ * not perl... */
+#define parse_error(...) fprintf(stderr, __VA_ARGS__); return TEST_REPORT_STATUS_ERROR;
+#define skip_char(c) \
+do { \
+ if (*s && *s == (c)) { \
+ s++; \
+ } else { \
+ parse_error ("expected '%c' but found '%c'", c, *s); \
+ } \
+} while (0)
+#define skip_space() while (*s && (*s == ' ' || *s == '\t')) s++;
+#define parse_int(result) \
+do { \
+ (result) = strtol (s, &end, 10); \
+ if (*s && end != s) { \
+ s = end; \
+ } else { \
+ parse_error("expected integer but found %s", s); \
+ } \
+} while (0)
+#define parse_double(result) \
+do { \
+ (result) = strtod (s, &end); \
+ if (*s && end != s) { \
+ s = end; \
+ } else { \
+ parse_error("expected floating-point value but found %s", s); \
+ } \
+} while (0)
+/* Here a string is simply a sequence of non-whitespace */
+#define parse_string(result) \
+do { \
+ for (end = s; *end; end++) \
+ if (isspace (*end)) \
+ break; \
+ (result) = strndup (s, end - s); \
+ if ((result) == NULL) { \
+ fprintf (stderr, "Out of memory.\n"); \
+ exit (1); \
+ } \
+ s = end; \
+} while (0)
+
+static test_report_status_t
+test_report_parse (test_report_t *report, char *line)
+{
+ char *end;
+ char *s = line;
+
+ /* The code here looks funny unless you understand that these are
+ * all macro calls, (and then the code just looks sick). */
+ if (*s == '\n')
+ return TEST_REPORT_STATUS_COMMENT;
+
+ skip_char ('[');
+ skip_space ();
+ if (*s == '#')
+ return TEST_REPORT_STATUS_COMMENT;
+ parse_int (report->id);
+ skip_char (']');
+
+ skip_space ();
+
+ parse_string (report->backend);
+ end = strrchr (report->backend, '-');
+ if (*end)
+ *end++ = '\0';
+ report->content = end;
+
+ skip_space ();
+
+ parse_string (report->name);
+ end = strrchr (report->name, '-');
+ if (*end)
+ *end++ = '\0';
+ report->size = atoi (end);
+
+ skip_space ();
+
+ parse_double (report->ticks);
+
+ skip_space ();
+
+ parse_double (report->time);
+
+ skip_space ();
+
+ parse_double (report->std_dev);
+ report->std_dev /= 100.0;
+ skip_char ('%');
+
+ skip_space ();
+
+ parse_int (report->iterations);
+
+ skip_space ();
+ skip_char ('\n');
+
+ return TEST_REPORT_STATUS_SUCCESS;
+}
+
+static void
+cairo_perf_report_load (cairo_perf_report_t *report, const char *filename)
+{
+ FILE *file;
+ test_report_status_t status;
+ int line_number = 0;
+ char *line = NULL;
+ size_t line_size = 0;
+
+ report->name = filename;
+ report->tests = NULL;
+ report->tests_size = 0;
+ report->tests_count = 0;
+
+ file = fopen (filename, "r");
+ if (file == NULL) {
+ fprintf (stderr, "Failed to open %s: %s\n",
+ filename, strerror (errno));
+ exit (1);
+ }
+
+ while (1) {
+ if (report->tests_count == report->tests_size) {
+ report->tests_size = report->tests_size ? 2 * report->tests_size : 16;
+ report->tests = realloc (report->tests,
+ report->tests_size * sizeof (test_report_t));
+ if (report->tests == NULL) {
+ fprintf (stderr, "Out of memory.\n");
+ exit (1);
+ }
+ }
+
+ line_number++;
+ if (getline (&line, &line_size, file) == -1)
+ break;
+
+ status = test_report_parse (&report->tests[report->tests_count], line);
+ if (status == TEST_REPORT_STATUS_ERROR)
+ fprintf (stderr, "Ignoring unrecognized line %d of %s:\n%s",
+ line_number, filename, line);
+ if (status == TEST_REPORT_STATUS_SUCCESS)
+ report->tests_count++;
+ /* Do nothing on TEST_REPORT_STATUS_COMMENT */
+ }
+
+ if (line)
+ free (line);
+}
+
+static int
+test_diff_cmp (const void *a, const void *b)
+{
+ const test_diff_t *a_diff = a;
+ const test_diff_t *b_diff = b;
+ double a_change, b_change;
+
+ a_change = a_diff->speedup;
+ b_change = b_diff->speedup;
+
+ /* First make all speedups come before all slowdowns. */
+ if (a_change > 1.0 && b_change < 1.0)
+ return -1;
+ if (a_change < 1.0 && b_change > 1.0)
+ return 1;
+
+ /* Then, within each, sort by magnitude of speed change */
+ if (a_change < 1.0)
+ a_change = 1.0 / a_change;
+
+ if (b_change < 1.0)
+ b_change = 1.0 / b_change;
+
+ /* Reverse sort so larger changes come first */
+ if (a_change > b_change)
+ return -1;
+ if (a_change < b_change)
+ return 1;
+ return 0;
+}
+
+static int
+test_report_cmp_backend_then_name (const void *a, const void *b)
+{
+ const test_report_t *a_test = a;
+ const test_report_t *b_test = b;
+ int cmp;
+
+ cmp = strcmp (a_test->backend, b_test->backend);
+ if (cmp)
+ return cmp;
+
+ cmp = strcmp (a_test->content, b_test->content);
+ if (cmp)
+ return cmp;
+
+ cmp = strcmp (a_test->name, b_test->name);
+ if (cmp)
+ return cmp;
+
+ if (a_test->size < b_test->size)
+ return -1;
+ if (a_test->size > b_test->size)
+ return 1;
+ return 0;
+}
+
+static void
+cairo_perf_report_sort_by_backend_then_name (cairo_perf_report_t *report)
+{
+ qsort (report->tests, report->tests_count, sizeof (test_report_t),
+ test_report_cmp_backend_then_name);
+}
+
+#define CHANGE_BAR_WIDTH 70
+static void
+print_change_bar (double change, double max_change)
+{
+ int units_per_cell = (int) ceil (max_change / CHANGE_BAR_WIDTH);
+
+ /* For a 1.0x speedup we want a zero-size bar to show "no
+ * change". */
+ change -= 1.0;
+
+ while (change > units_per_cell) {
+ printf("â");
+ change -= units_per_cell;
+ }
+
+ change /= units_per_cell;
+
+ if (change > 7.5/8.0)
+ printf("â");
+ else if (change > 6.5/8.0)
+ printf("â");
+ else if (change > 5.5/8.0)
+ printf("â");
+ else if (change > 4.5/8.0)
+ printf("â");
+ else if (change > 3.5/8.0)
+ printf("â");
+ else if (change > 2.5/8.0)
+ printf("â");
+ else if (change > 1.5/8.0)
+ printf("â");
+ else if (change > 0.5/8.0)
+ printf("â");
+
+ printf ("\n");
+}
+
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+static void
+cairo_perf_report_diff (cairo_perf_report_t *old,
+ cairo_perf_report_t *new,
+ double min_change)
+{
+ int i, i_old, i_new;
+ test_report_t *o, *n;
+ double o_min, o_max, n_min, n_max;
+ int cmp;
+ test_diff_t *diff, *diffs;
+ int num_diffs = 0;
+ int printed_speedup = 0, printed_slowdown = 0;
+ double change, max_change;
+
+ diffs = malloc (MAX (old->tests_count, new->tests_count) * sizeof (test_diff_t));
+ if (diffs == NULL) {
+ fprintf (stderr, "Out of memory.\n");
+ exit (1);
+ }
+
+ cairo_perf_report_sort_by_backend_then_name (old);
+ cairo_perf_report_sort_by_backend_then_name (new);
+
+ i_old = 0;
+ i_new = 0;
+ while (i_old < old->tests_count && i_new < new->tests_count) {
+ o = &old->tests[i_old];
+ n = &new->tests[i_new];
+ cmp = test_report_cmp_backend_then_name (o, n);
+ if (cmp < 0) {
+ fprintf (stderr, "Only in old: %s %s\n", o->backend, o->name);
+ i_old++;
+ continue;
+ }
+ if (cmp > 0) {
+ fprintf (stderr, "Only in new: %s %s\n", n->backend, n->name);
+ i_new++;
+ continue;
+ }
+
+ /* Discard as uninteresting a change which doesn't separate
+ * the means by a few standard deviations in each direction,
+ * (that is, require the bulk of each curve to be
+ * non-overlapping). */
+ o_min = o->ticks * (1 - 3 * o->std_dev);
+ o_max = o->ticks * (1 + 3 * o->std_dev);
+ n_min = n->ticks * (1 - 3 * n->std_dev);
+ n_max = n->ticks * (1 + 3 * n->std_dev);
+ if (n_min > o_max ||
+ n_max < o_min)
+ {
+ diffs[num_diffs].old = o;
+ diffs[num_diffs].new = n;
+ diffs[num_diffs].speedup = o->ticks / n->ticks;
+ num_diffs++;
+ }
+
+ i_old++;
+ i_new++;
+ }
+
+ qsort (diffs, num_diffs, sizeof (test_diff_t), test_diff_cmp);
+
+ max_change = 1.0;
+ for (i = 0; i < num_diffs; i++) {
+ change = diffs[i].speedup;
+ if (change < 1.0)
+ change = 1.0 / change;
+ if (change > max_change)
+ max_change = change;
+ }
+
+ for (i = 0; i < num_diffs; i++) {
+ diff = &diffs[i];
+
+ change = diff->speedup;
+ if (change < 1.0)
+ change = 1.0 / change;
+
+ /* Discard as uninteresting a change which is less than the
+ * minimum change required, (default may be overriden on
+ * command-line). */
+ if (change - 1.0 < min_change)
+ continue;
+
+ if (diff->speedup > 1.0 && ! printed_speedup) {
+ printf ("Speedups\n"
+ "========\n");
+ printed_speedup = 1;
+ }
+ if (diff->speedup < 1.0 && ! printed_slowdown) {
+ printf ("Slowdowns\n"
+ "=========\n");
+ printed_slowdown = 1;
+ }
+
+ printf ("%5s-%-4s %26s-%-3d %6.2f %4.2f%% -> %6.2f %4.2f%%: %5.2fx ",
+ diff->old->backend, diff->old->content,
+ diff->old->name, diff->old->size,
+ diff->old->time, diff->old->std_dev * 100,
+ diff->new->time, diff->new->std_dev * 100,
+ change);
+
+ if (diff->speedup > 1.0)
+ printf ("speedup\n");
+ else
+ printf ("slowdown\n");
+
+ print_change_bar (change, max_change);
+ }
+
+ free (diffs);
+}
+
+static void
+usage (const char *argv0)
+{
+ fprintf (stderr, "Usage: %s file1 file2 [minimum_significant_change[%%]]\n", argv0);
+ fprintf (stderr,
+ "Computes significant performance differences for cairo performance reports.\n"
+ "Each file should be the output of the cairo-perf program (or \"make perf\").\n"
+ "The third argument is used to supress all changes below some threshold.\n"
+ "The default value of 5%% ignores any speeedup or slowdown of 5%% or less,\n"
+ "A value of 0 will cause all output to be reported.\n");
+}
+
+int
+main (int argc, const char *argv[])
+{
+ const char *old_filename, *new_filename;
+ cairo_perf_report_t old, new;
+ double min_change;
+ char *end;
+
+ if (argc < 3) {
+ usage (argv[0]);
+ return 1;
+ }
+
+ old_filename = argv[1];
+ new_filename = argv[2];
+
+ min_change = 0.05;
+ if (argc >= 4) {
+ min_change = strtod (argv[3], &end);
+ if (*end && *end == '%')
+ min_change = min_change / 100.0;
+ }
+
+ cairo_perf_report_load (&old, old_filename);
+ cairo_perf_report_load (&new, new_filename);
+
+ cairo_perf_report_diff (&old, &new, min_change);
+
+ return 0;
+}
+
diff --git a/perf/cairo-perf-diff.c b/perf/cairo-perf-diff.c
deleted file mode 100644
index eb2ef9f..0000000
--- a/perf/cairo-perf-diff.c
+++ /dev/null
@@ -1,482 +0,0 @@
-/*
- * 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
- * copyright holders not be used in advertising or publicity
- * pertaining to distribution of the software without specific,
- * written prior permission. The copyright holders make no
- * representations about the suitability of this software for any
- * purpose. It is provided "as is" without express or implied
- * warranty.
- *
- * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
- * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
- * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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: Carl Worth <cworth at cworth.org>
- */
-
-/* We use _GNU_SOURCE for getline. If someone wants to avoid that
- * dependence they could conditionally provide a custom implementation
- * of getline instead. */
-#ifndef _GNU_SOURCE
-# define _GNU_SOURCE
-#endif
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <ctype.h>
-#include <math.h>
-
-typedef struct _test_report {
- int id;
- char *backend;
- char *content;
- char *name;
- int size;
- double ticks;
- double time;
- double std_dev;
- int iterations;
-} test_report_t;
-
-typedef struct _test_diff {
- test_report_t *old;
- test_report_t *new;
- double speedup;
-} test_diff_t;
-
-typedef struct _cairo_perf_report {
- const char *name;
- test_report_t *tests;
- int tests_size;
- int tests_count;
-} cairo_perf_report_t;
-
-typedef enum {
- TEST_REPORT_STATUS_SUCCESS,
- TEST_REPORT_STATUS_COMMENT,
- TEST_REPORT_STATUS_ERROR
-} test_report_status_t;
-
-/* Ad-hoc parsing, macros with a strong dependence on the calling
- * context, and plenty of other ugliness is here. But at least it's
- * not perl... */
-#define parse_error(...) fprintf(stderr, __VA_ARGS__); return TEST_REPORT_STATUS_ERROR;
-#define skip_char(c) \
-do { \
- if (*s && *s == (c)) { \
- s++; \
- } else { \
- parse_error ("expected '%c' but found '%c'", c, *s); \
- } \
-} while (0)
-#define skip_space() while (*s && (*s == ' ' || *s == '\t')) s++;
-#define parse_int(result) \
-do { \
- (result) = strtol (s, &end, 10); \
- if (*s && end != s) { \
- s = end; \
- } else { \
- parse_error("expected integer but found %s", s); \
- } \
-} while (0)
-#define parse_double(result) \
-do { \
- (result) = strtod (s, &end); \
- if (*s && end != s) { \
- s = end; \
- } else { \
- parse_error("expected floating-point value but found %s", s); \
- } \
-} while (0)
-/* Here a string is simply a sequence of non-whitespace */
-#define parse_string(result) \
-do { \
- for (end = s; *end; end++) \
- if (isspace (*end)) \
- break; \
- (result) = strndup (s, end - s); \
- if ((result) == NULL) { \
- fprintf (stderr, "Out of memory.\n"); \
- exit (1); \
- } \
- s = end; \
-} while (0)
-
-static test_report_status_t
-test_report_parse (test_report_t *report, char *line)
-{
- char *end;
- char *s = line;
-
- /* The code here looks funny unless you understand that these are
- * all macro calls, (and then the code just looks sick). */
- if (*s == '\n')
- return TEST_REPORT_STATUS_COMMENT;
-
- skip_char ('[');
- skip_space ();
- if (*s == '#')
- return TEST_REPORT_STATUS_COMMENT;
- parse_int (report->id);
- skip_char (']');
-
- skip_space ();
-
- parse_string (report->backend);
- end = strrchr (report->backend, '-');
- if (*end)
- *end++ = '\0';
- report->content = end;
-
- skip_space ();
-
- parse_string (report->name);
- end = strrchr (report->name, '-');
- if (*end)
- *end++ = '\0';
- report->size = atoi (end);
-
- skip_space ();
-
- parse_double (report->ticks);
-
- skip_space ();
-
- parse_double (report->time);
-
- skip_space ();
-
- parse_double (report->std_dev);
- report->std_dev /= 100.0;
- skip_char ('%');
-
- skip_space ();
-
- parse_int (report->iterations);
-
- skip_space ();
- skip_char ('\n');
-
- return TEST_REPORT_STATUS_SUCCESS;
-}
-
-static void
-cairo_perf_report_load (cairo_perf_report_t *report, const char *filename)
-{
- FILE *file;
- test_report_status_t status;
- int line_number = 0;
- char *line = NULL;
- size_t line_size = 0;
-
- report->name = filename;
- report->tests = NULL;
- report->tests_size = 0;
- report->tests_count = 0;
-
- file = fopen (filename, "r");
- if (file == NULL) {
- fprintf (stderr, "Failed to open %s: %s\n",
- filename, strerror (errno));
- exit (1);
- }
-
- while (1) {
- if (report->tests_count == report->tests_size) {
- report->tests_size = report->tests_size ? 2 * report->tests_size : 16;
- report->tests = realloc (report->tests,
- report->tests_size * sizeof (test_report_t));
- if (report->tests == NULL) {
- fprintf (stderr, "Out of memory.\n");
- exit (1);
- }
- }
-
- line_number++;
- if (getline (&line, &line_size, file) == -1)
- break;
-
- status = test_report_parse (&report->tests[report->tests_count], line);
- if (status == TEST_REPORT_STATUS_ERROR)
- fprintf (stderr, "Ignoring unrecognized line %d of %s:\n%s",
- line_number, filename, line);
- if (status == TEST_REPORT_STATUS_SUCCESS)
- report->tests_count++;
- /* Do nothing on TEST_REPORT_STATUS_COMMENT */
- }
-
- if (line)
- free (line);
-}
-
-static int
-test_diff_cmp (const void *a, const void *b)
-{
- const test_diff_t *a_diff = a;
- const test_diff_t *b_diff = b;
- double a_change, b_change;
-
- a_change = a_diff->speedup;
- b_change = b_diff->speedup;
-
- /* First make all speedups come before all slowdowns. */
- if (a_change > 1.0 && b_change < 1.0)
- return -1;
- if (a_change < 1.0 && b_change > 1.0)
- return 1;
-
- /* Then, within each, sort by magnitude of speed change */
- if (a_change < 1.0)
- a_change = 1.0 / a_change;
-
- if (b_change < 1.0)
- b_change = 1.0 / b_change;
-
- /* Reverse sort so larger changes come first */
- if (a_change > b_change)
- return -1;
- if (a_change < b_change)
- return 1;
- return 0;
-}
-
-static int
-test_report_cmp_backend_then_name (const void *a, const void *b)
-{
- const test_report_t *a_test = a;
- const test_report_t *b_test = b;
- int cmp;
-
- cmp = strcmp (a_test->backend, b_test->backend);
- if (cmp)
- return cmp;
-
- cmp = strcmp (a_test->content, b_test->content);
- if (cmp)
- return cmp;
-
- cmp = strcmp (a_test->name, b_test->name);
- if (cmp)
- return cmp;
-
- if (a_test->size < b_test->size)
- return -1;
- if (a_test->size > b_test->size)
- return 1;
- return 0;
-}
-
-static void
-cairo_perf_report_sort_by_backend_then_name (cairo_perf_report_t *report)
-{
- qsort (report->tests, report->tests_count, sizeof (test_report_t),
- test_report_cmp_backend_then_name);
-}
-
-#define CHANGE_BAR_WIDTH 70
-static void
-print_change_bar (double change, double max_change)
-{
- int units_per_cell = (int) ceil (max_change / CHANGE_BAR_WIDTH);
-
- /* For a 1.0x speedup we want a zero-size bar to show "no
- * change". */
- change -= 1.0;
-
- while (change > units_per_cell) {
- printf("â");
- change -= units_per_cell;
- }
-
- change /= units_per_cell;
-
- if (change > 7.5/8.0)
- printf("â");
- else if (change > 6.5/8.0)
- printf("â");
- else if (change > 5.5/8.0)
- printf("â");
- else if (change > 4.5/8.0)
- printf("â");
- else if (change > 3.5/8.0)
- printf("â");
- else if (change > 2.5/8.0)
- printf("â");
- else if (change > 1.5/8.0)
- printf("â");
- else if (change > 0.5/8.0)
- printf("â");
-
- printf ("\n");
-}
-
-#define MAX(a,b) ((a) > (b) ? (a) : (b))
-static void
-cairo_perf_report_diff (cairo_perf_report_t *old,
- cairo_perf_report_t *new,
- double min_change)
-{
- int i, i_old, i_new;
- test_report_t *o, *n;
- double o_min, o_max, n_min, n_max;
- int cmp;
- test_diff_t *diff, *diffs;
- int num_diffs = 0;
- int printed_speedup = 0, printed_slowdown = 0;
- double change, max_change;
-
- diffs = malloc (MAX (old->tests_count, new->tests_count) * sizeof (test_diff_t));
- if (diffs == NULL) {
- fprintf (stderr, "Out of memory.\n");
- exit (1);
- }
-
- cairo_perf_report_sort_by_backend_then_name (old);
- cairo_perf_report_sort_by_backend_then_name (new);
-
- i_old = 0;
- i_new = 0;
- while (i_old < old->tests_count && i_new < new->tests_count) {
- o = &old->tests[i_old];
- n = &new->tests[i_new];
- cmp = test_report_cmp_backend_then_name (o, n);
- if (cmp < 0) {
- fprintf (stderr, "Only in old: %s %s\n", o->backend, o->name);
- i_old++;
- continue;
- }
- if (cmp > 0) {
- fprintf (stderr, "Only in new: %s %s\n", n->backend, n->name);
- i_new++;
- continue;
- }
-
- /* Discard as uninteresting a change which doesn't separate
- * the means by a few standard deviations in each direction,
- * (that is, require the bulk of each curve to be
- * non-overlapping). */
- o_min = o->ticks * (1 - 3 * o->std_dev);
- o_max = o->ticks * (1 + 3 * o->std_dev);
- n_min = n->ticks * (1 - 3 * n->std_dev);
- n_max = n->ticks * (1 + 3 * n->std_dev);
- if (n_min > o_max ||
- n_max < o_min)
- {
- diffs[num_diffs].old = o;
- diffs[num_diffs].new = n;
- diffs[num_diffs].speedup = o->ticks / n->ticks;
- num_diffs++;
- }
-
- i_old++;
- i_new++;
- }
-
- qsort (diffs, num_diffs, sizeof (test_diff_t), test_diff_cmp);
-
- max_change = 1.0;
- for (i = 0; i < num_diffs; i++) {
- change = diffs[i].speedup;
- if (change < 1.0)
- change = 1.0 / change;
- if (change > max_change)
- max_change = change;
- }
-
- for (i = 0; i < num_diffs; i++) {
- diff = &diffs[i];
-
- change = diff->speedup;
- if (change < 1.0)
- change = 1.0 / change;
-
- /* Discard as uninteresting a change which is less than the
- * minimum change required, (default may be overriden on
- * command-line). */
- if (change - 1.0 < min_change)
- continue;
-
- if (diff->speedup > 1.0 && ! printed_speedup) {
- printf ("Speedups\n"
- "========\n");
- printed_speedup = 1;
- }
- if (diff->speedup < 1.0 && ! printed_slowdown) {
- printf ("Slowdowns\n"
- "=========\n");
- printed_slowdown = 1;
- }
-
- printf ("%5s-%-4s %26s-%-3d %6.2f %4.2f%% -> %6.2f %4.2f%%: %5.2fx ",
- diff->old->backend, diff->old->content,
- diff->old->name, diff->old->size,
- diff->old->time, diff->old->std_dev * 100,
- diff->new->time, diff->new->std_dev * 100,
- change);
-
- if (diff->speedup > 1.0)
- printf ("speedup\n");
- else
- printf ("slowdown\n");
-
- print_change_bar (change, max_change);
- }
-
- free (diffs);
-}
-
-static void
-usage (const char *argv0)
-{
- fprintf (stderr, "Usage: %s file1 file2 [minimum_significant_change[%%]]\n", argv0);
- fprintf (stderr,
- "Computes significant performance differences for cairo performance reports.\n"
- "Each file should be the output of the cairo-perf program (or \"make perf\").\n"
- "The third argument is used to supress all changes below some threshold.\n"
- "The default value of 5%% ignores any speeedup or slowdown of 5%% or less,\n"
- "A value of 0 will cause all output to be reported.\n");
-}
-
-int
-main (int argc, const char *argv[])
-{
- const char *old_filename, *new_filename;
- cairo_perf_report_t old, new;
- double min_change;
- char *end;
-
- if (argc < 3) {
- usage (argv[0]);
- return 1;
- }
-
- old_filename = argv[1];
- new_filename = argv[2];
-
- min_change = 0.05;
- if (argc >= 4) {
- min_change = strtod (argv[3], &end);
- if (*end && *end == '%')
- min_change = min_change / 100.0;
- }
-
- cairo_perf_report_load (&old, old_filename);
- cairo_perf_report_load (&new, new_filename);
-
- cairo_perf_report_diff (&old, &new, min_change);
-
- return 0;
-}
-
More information about the cairo-commit
mailing list