2 * Copyright © 2006 Red Hat, Inc.
4 * Permission to use, copy, modify, distribute, and sell this software
5 * and its documentation for any purpose is hereby granted without
6 * fee, provided that the above copyright notice appear in all copies
7 * and that both that copyright notice and this permission notice
8 * appear in supporting documentation, and that the name of the
9 * copyright holders not be used in advertising or publicity
10 * pertaining to distribution of the software without specific,
11 * written prior permission. The copyright holders make no
12 * representations about the suitability of this software for any
13 * purpose. It is provided "as is" without express or implied
16 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
17 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
18 * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
21 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
22 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
25 * Authors: Carl Worth <cworth@cworth.org>
28 #include "cairo-perf.h"
38 typedef struct _cairo_perf_report_options {
41 int print_change_bars;
43 } cairo_perf_report_options_t;
45 typedef struct _cairo_perf_diff_files_args {
46 const char **filenames;
48 cairo_perf_report_options_t options;
49 } cairo_perf_diff_files_args_t;
52 test_diff_cmp_speedup_before_slowdown (const void *a,
55 const test_diff_t *a_diff = a;
56 const test_diff_t *b_diff = b;
58 /* First make all speedups come before all slowdowns. */
59 if (a_diff->change > 0 && b_diff->change < 0)
61 if (a_diff->change < 0 && b_diff->change > 0)
64 if (a_diff->change == b_diff->change)
67 /* Large speedups come first. */
68 if (a_diff->change > 0) {
69 if (a_diff->change > b_diff->change)
75 /* Large slowdowns come last. */
76 if (a_diff->change < 0) {
77 if (a_diff->change < b_diff->change)
87 test_diff_cmp (const void *a,
90 const test_diff_t *a_diff = a;
91 const test_diff_t *b_diff = b;
93 /* Reverse sort by magnitude of change so larger changes come
95 if (a_diff->change > b_diff->change)
98 if (a_diff->change < b_diff->change)
104 #define CHANGE_BAR_WIDTH 70
106 print_change_bar (double change,
110 int units_per_cell = ceil (max_change / CHANGE_BAR_WIDTH);
111 static char const *ascii_boxes[8] = {
112 "****","***" ,"***", "**",
115 static char const *utf_boxes[8] = {
119 char const **boxes = use_utf ? utf_boxes : ascii_boxes;
121 /* For a 1.0x speedup we want a zero-size bar to show "no
125 while (change > units_per_cell) {
126 printf ("%s", boxes[0]);
127 change -= units_per_cell;
130 change /= units_per_cell;
132 if (change > 7.5/8.0)
133 printf ("%s", boxes[0]);
134 else if (change > 6.5/8.0)
135 printf ("%s", boxes[1]);
136 else if (change > 5.5/8.0)
137 printf ("%s", boxes[2]);
138 else if (change > 4.5/8.0)
139 printf ("%s", boxes[3]);
140 else if (change > 3.5/8.0)
141 printf ("%s", boxes[4]);
142 else if (change > 2.5/8.0)
143 printf ("%s", boxes[5]);
144 else if (change > 1.5/8.0)
145 printf ("%s", boxes[6]);
146 else if (change > 0.5/8.0)
147 printf ("%s", boxes[7]);
153 test_diff_print_binary (test_diff_t *diff,
155 cairo_perf_report_options_t *options)
157 if (diff->tests[0]->size)
158 printf ("%5s-%-4s %26s-%-3d",
159 diff->tests[0]->backend, diff->tests[0]->content,
160 diff->tests[0]->name, diff->tests[0]->size);
162 printf ("%5s %26s", diff->tests[0]->backend, diff->tests[0]->name);
164 printf (" %6.2f (%.2f %4.2f%%) -> %6.2f (%.2f %4.2f%%): %5.2fx ",
165 diff->tests[0]->stats.min_ticks / diff->tests[0]->stats.ticks_per_ms,
166 diff->tests[0]->stats.median_ticks / diff->tests[0]->stats.ticks_per_ms,
167 diff->tests[0]->stats.std_dev * 100,
168 diff->tests[1]->stats.min_ticks / diff->tests[1]->stats.ticks_per_ms,
169 diff->tests[1]->stats.median_ticks / diff->tests[1]->stats.ticks_per_ms,
170 diff->tests[1]->stats.std_dev * 100,
171 fabs (diff->change));
173 if (diff->change > 1.0)
174 printf ("speedup\n");
176 printf ("slowdown\n");
178 if (options->print_change_bars)
179 print_change_bar (fabs (diff->change), max_change,
184 test_diff_print_multi (test_diff_t *diff,
186 cairo_perf_report_options_t *options)
192 if (diff->tests[0]->size) {
193 printf ("%s (backend: %s-%s, size: %d)\n",
194 diff->tests[0]->name,
195 diff->tests[0]->backend,
196 diff->tests[0]->content,
197 diff->tests[0]->size);
199 printf ("%s (backend: %s)\n",
200 diff->tests[0]->name,
201 diff->tests[0]->backend);
204 for (i = 0; i < diff->num_tests; i++) {
205 test_time = diff->tests[i]->stats.min_ticks;
206 if (! options->use_ticks)
207 test_time /= diff->tests[i]->stats.ticks_per_ms;
208 change = diff->max / test_time;
209 printf ("[%d] %6.2f: %5.2fx ",
210 diff->tests[i]->fileno,
211 diff->tests[i]->stats.min_ticks / diff->tests[i]->stats.ticks_per_ms,
214 if (options->print_change_bars)
215 print_change_bar (change, max_change, options->use_utf);
223 #define MAX(a,b) ((a) > (b) ? (a) : (b))
225 cairo_perf_reports_compare (cairo_perf_report_t *reports,
227 cairo_perf_report_options_t *options)
230 test_report_t **tests, *min_test;
231 test_diff_t *diff, *diffs;
232 int num_diffs, max_diffs;
236 cairo_bool_t printed_speedup = FALSE;
237 cairo_bool_t printed_slowdown = FALSE;
239 assert (num_reports >= 2);
241 tests = xmalloc (num_reports * sizeof (test_report_t *));
243 max_diffs = reports[0].tests_count;
244 for (i = 0; i < num_reports; i++) {
245 tests[i] = reports[i].tests;
246 if (reports[i].tests_count > max_diffs)
247 max_diffs = reports[i].tests_count;
250 diff = diffs = xmalloc (max_diffs * sizeof (test_diff_t));
254 /* We expect iterations values of 0 when multiple raw reports
255 * for the same test have been condensed into the stats of the
256 * first. So we just skip these later reports that have no
259 for (i = 0; i < num_reports; i++) {
260 while (tests[i]->name && tests[i]->stats.iterations == 0)
266 if (seen_non_null < 2)
269 /* Find the minimum of all current tests, (we have to do this
270 * in case some reports don't have a particular test). */
271 for (i = 0; i < num_reports; i++) {
272 if (tests[i]->name) {
277 for (++i; i < num_reports; i++) {
278 if (tests[i]->name &&
279 test_report_cmp_backend_then_name (tests[i], min_test) < 0)
285 /* For each report that has the current test, record it into
286 * the diff structure. */
288 diff->tests = xmalloc (num_reports * sizeof (test_diff_t));
289 for (i = 0; i < num_reports; i++) {
290 if (tests[i]->name &&
291 test_report_cmp_backend_then_name (tests[i], min_test) == 0)
293 test_time = tests[i]->stats.min_ticks;
294 if (! options->use_ticks)
295 test_time /= tests[i]->stats.ticks_per_ms;
296 if (diff->num_tests == 0) {
297 diff->min = test_time;
298 diff->max = test_time;
300 if (test_time < diff->min)
301 diff->min = test_time;
302 if (test_time > diff->max)
303 diff->max = test_time;
305 diff->tests[diff->num_tests++] = tests[i];
309 diff->change = diff->max / diff->min;
311 if (num_reports == 2) {
312 double old_time, new_time;
313 if (diff->num_tests == 1) {
314 printf ("Only in %s: %s %s\n",
315 diff->tests[0]->configuration,
316 diff->tests[0]->backend,
317 diff->tests[0]->name);
320 old_time = diff->tests[0]->stats.min_ticks;
321 new_time = diff->tests[1]->stats.min_ticks;
322 if (! options->use_ticks) {
323 old_time /= diff->tests[0]->stats.ticks_per_ms;
324 new_time /= diff->tests[1]->stats.ticks_per_ms;
326 diff->change = old_time / new_time;
327 if (diff->change < 1.0)
328 diff->change = - 1.0 / diff->change;
337 if (num_reports == 2)
338 qsort (diffs, num_diffs, sizeof (test_diff_t),
339 test_diff_cmp_speedup_before_slowdown);
341 qsort (diffs, num_diffs, sizeof (test_diff_t), test_diff_cmp);
344 for (i = 0; i < num_diffs; i++) {
345 if (fabs (diffs[i].change) > max_change)
346 max_change = fabs (diffs[i].change);
349 if (num_reports == 2)
352 diffs->tests[0]->configuration,
353 diffs->tests[1]->configuration);
355 for (i = 0; i < num_diffs; i++) {
358 /* Discard as uninteresting a change which is less than the
359 * minimum change required, (default may be overriden on
361 if (fabs (diff->change) - 1.0 < options->min_change)
364 if (num_reports == 2) {
365 if (diff->change > 1.0 && ! printed_speedup) {
368 printed_speedup = TRUE;
370 if (diff->change < 1.0 && ! printed_slowdown) {
371 printf ("Slowdowns\n"
373 printed_slowdown = TRUE;
375 test_diff_print_binary (diff, max_change, options);
377 test_diff_print_multi (diff, max_change, options);
382 for (i = 0; i < num_diffs; i++)
383 free (diffs[i].tests);
389 usage (const char *argv0)
391 char const *basename = strrchr(argv0, '/');
392 basename = basename ? basename+1 : argv0;
394 "Usage: %s [options] file1 file2 [...]\n\n",
397 "Computes significant performance differences for cairo performance reports.\n"
398 "Each file should be the output of the cairo-perf program (or \"make perf\").\n"
399 "The following options are available:\n"
401 "--no-utf Use ascii stars instead of utf-8 change bars.\n"
402 " Four stars are printed per factor of speedup.\n"
404 "--no-bars Don't display change bars at all.\n\n"
406 "--use-ms Use milliseconds to calculate differences.\n"
407 " (instead of ticks which are hardware dependent)\n"
409 "--min-change threshold[%%]\n"
410 " Suppress all changes below the given threshold.\n"
411 " The default threshold of 0.05 or 5%% ignores any\n"
412 " speedup or slowdown of 1.05 or less. A threshold\n"
413 " of 0 will cause all output to be reported.\n"
419 parse_args (int argc,
421 cairo_perf_diff_files_args_t *args)
425 for (i = 1; i < argc; i++) {
426 if (strcmp (argv[i], "--no-utf") == 0) {
427 args->options.use_utf = 0;
429 else if (strcmp (argv[i], "--no-bars") == 0) {
430 args->options.print_change_bars = 0;
432 else if (strcmp (argv[i], "--use-ms") == 0) {
435 else if (strcmp (argv[i], "--use-ticks") == 0) {
436 args->options.use_ticks = 1;
438 else if (strcmp (argv[i], "--min-change") == 0) {
443 args->options.min_change = strtod (argv[i], &end);
446 args->options.min_change /= 100;
453 args->num_filenames++;
454 args->filenames = xrealloc (args->filenames,
455 args->num_filenames * sizeof (char *));
456 args->filenames[args->num_filenames - 1] = argv[i];
465 cairo_perf_diff_files_args_t args = {
466 NULL, /* filenames */
467 0, /* num_filenames */
469 0.05, /* min change */
471 1, /* display change bars? */
474 cairo_perf_report_t *reports;
478 parse_args (argc, argv, &args);
480 if (args.num_filenames < 2)
483 reports = xmalloc (args.num_filenames * sizeof (cairo_perf_report_t));
485 for (i = 0; i < args.num_filenames; i++ ) {
486 cairo_perf_report_load (&reports[i], args.filenames[i], i, NULL);
487 printf ("[%d] %s\n", i, args.filenames[i]);
491 cairo_perf_reports_compare (reports, args.num_filenames, &args.options);
493 /* Pointless memory cleanup, (would be a great place for talloc) */
494 free (args.filenames);
495 for (i = 0; i < args.num_filenames; i++) {
496 for (t = reports[i].tests; t->name; t++) {
501 free (reports[i].tests);
502 free (reports[i].configuration);