1 /* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
3 * Copyright © 2006 Mozilla Corporation
4 * Copyright © 2006 Red Hat, Inc.
6 * Permission to use, copy, modify, distribute, and sell this software
7 * and its documentation for any purpose is hereby granted without
8 * fee, provided that the above copyright notice appear in all copies
9 * and that both that copyright notice and this permission notice
10 * appear in supporting documentation, and that the name of
11 * the authors not be used in advertising or publicity pertaining to
12 * distribution of the software without specific, written prior
13 * permission. The authors make no representations about the
14 * suitability of this software for any purpose. It is provided "as
15 * is" without express or implied warranty.
17 * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
18 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
19 * FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL,
20 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
21 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
22 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
23 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25 * Authors: Vladimir Vukicevic <vladimir@pobox.com>
26 * Carl Worth <cworth@cworth.org>
29 #define _GNU_SOURCE 1 /* for sched_getaffinity() */
31 #include "../cairo-version.h" /* for the real version */
33 #include "cairo-perf.h"
34 #include "cairo-stats.h"
36 #include "cairo-boilerplate-getopt.h"
44 #include <fontconfig/fontconfig.h>
51 #define CAIRO_PERF_ITERATIONS_DEFAULT 100
52 #define CAIRO_PERF_LOW_STD_DEV 0.03
53 #define CAIRO_PERF_STABLE_STD_DEV_COUNT 5
54 #define CAIRO_PERF_ITERATION_MS_DEFAULT 2000
55 #define CAIRO_PERF_ITERATION_MS_FAST 5
57 typedef struct _cairo_perf_case {
58 CAIRO_PERF_RUN_DECL (*run);
59 cairo_bool_t (*enabled) (cairo_perf_t *perf);
60 unsigned int min_size;
61 unsigned int max_size;
64 const cairo_perf_case_t perf_cases[];
67 _content_to_string (cairo_content_t content,
70 switch (content|similar) {
71 case CAIRO_CONTENT_COLOR:
73 case CAIRO_CONTENT_COLOR|1:
75 case CAIRO_CONTENT_ALPHA:
77 case CAIRO_CONTENT_ALPHA|1:
79 case CAIRO_CONTENT_COLOR_ALPHA:
81 case CAIRO_CONTENT_COLOR_ALPHA|1:
84 return "<unknown_content>";
89 cairo_perf_has_similar (cairo_perf_t *perf)
91 cairo_surface_t *target;
93 if (getenv ("CAIRO_TEST_SIMILAR") == NULL)
96 /* exclude the image backend */
97 target = cairo_get_target (perf->cr);
98 if (cairo_surface_get_type (target) == CAIRO_SURFACE_TYPE_IMAGE)
105 cairo_perf_can_run (cairo_perf_t *perf,
107 cairo_bool_t *is_explicit)
112 *is_explicit = FALSE;
114 if (perf->num_names == 0)
117 for (i = 0; i < perf->num_names; i++) {
118 if (strstr (name, perf->names[i])) {
120 *is_explicit = FALSE;
129 cairo_perf_calibrate (cairo_perf_t *perf,
130 cairo_perf_func_t perf_func)
132 cairo_time_t calibration, calibration_max;
133 unsigned loops, min_loops;
136 calibration = perf_func (perf->cr, perf->size, perf->size, min_loops);
138 if (!perf->fast_and_sloppy) {
139 calibration_max = _cairo_time_from_s (perf->ms_per_iteration * 0.0001 / 4);
140 while (calibration < calibration_max) {
142 calibration = perf_func (perf->cr, perf->size, perf->size, min_loops);
147 * Compute the number of loops required for the timing
148 * interval to be perf->ms_per_iteration milliseconds. This
149 * helps to eliminate sampling variance due to timing and
150 * other systematic errors. However, it also hides
151 * synchronisation overhead as we attempt to process a large
152 * batch of identical operations in a single shot. This can be
153 * considered both good and bad... It would be good to perform
154 * a more rigorous analysis of the synchronisation overhead,
155 * that is to estimate the time for loop=0.
157 loops = _cairo_time_from_s (perf->ms_per_iteration * 0.001 * min_loops / calibration);
158 min_loops = perf->fast_and_sloppy ? 1 : 10;
159 if (loops < min_loops)
166 cairo_perf_run (cairo_perf_t *perf,
168 cairo_perf_func_t perf_func,
169 cairo_count_func_t count_func)
171 static cairo_bool_t first_run = TRUE;
172 unsigned int i, similar, similar_iters;
174 cairo_stats_t stats = {0.0, 0.0};
175 int low_std_dev_count;
177 if (perf->list_only) {
178 printf ("%s\n", name);
184 printf ("[ # ] %s.%-s %s %s %s ...\n",
185 "backend", "content", "test-size", "ticks-per-ms", "time(ticks)");
189 fprintf (perf->summary,
190 "[ # ] %8s.%-4s %28s %8s %8s %5s %5s %s %s\n",
191 "backend", "content", "test-size", "min(ticks)", "min(ms)", "median(ms)",
192 "stddev.", "iterations", "overhead");
199 if (getenv ("CAIRO_PERF_OUTPUT") != NULL) { /* check output */
201 cairo_status_t status;
203 xasprintf (&filename, "%s.%s.%s.%d.out.png",
204 name, perf->target->name,
205 _content_to_string (perf->target->content, 0),
207 cairo_save (perf->cr);
208 perf_func (perf->cr, perf->size, perf->size, 1);
209 cairo_restore (perf->cr);
210 status = cairo_surface_write_to_png (cairo_get_target (perf->cr), filename);
212 fprintf (stderr, "Failed to generate output check '%s': %s\n",
213 filename, cairo_status_to_string (status));
220 if (cairo_perf_has_similar (perf))
225 for (similar = 0; similar < similar_iters; similar++) {
229 fprintf (perf->summary,
230 "[%3d] %8s.%-5s %26s.%-3d ",
231 perf->test_number, perf->target->name,
232 _content_to_string (perf->target->content, similar),
234 fflush (perf->summary);
237 /* We run one iteration in advance to warm caches and calibrate. */
240 cairo_push_group_with_content (perf->cr,
241 cairo_boilerplate_content (perf->target->content));
243 cairo_save (perf->cr);
244 perf_func (perf->cr, perf->size, perf->size, 1);
245 loops = cairo_perf_calibrate (perf, perf_func);
247 cairo_pattern_destroy (cairo_pop_group (perf->cr));
249 cairo_restore (perf->cr);
251 low_std_dev_count = 0;
252 for (i =0; i < perf->iterations; i++) {
255 cairo_push_group_with_content (perf->cr,
256 cairo_boilerplate_content (perf->target->content));
258 cairo_save (perf->cr);
259 times[i] = perf_func (perf->cr, perf->size, perf->size, loops) ;
261 cairo_pattern_destroy (cairo_pop_group (perf->cr));
263 cairo_restore (perf->cr);
266 printf ("[*] %s.%s %s.%d %g",
268 _content_to_string (perf->target->content, similar),
270 _cairo_time_to_double (_cairo_time_from_s (1.)) / 1000.);
271 printf (" %lld", (long long) (times[i] / (double) loops));
272 } else if (! perf->exact_iterations) {
274 _cairo_stats_compute (&stats, times, i+1);
276 if (stats.std_dev <= CAIRO_PERF_LOW_STD_DEV) {
278 if (low_std_dev_count >= CAIRO_PERF_STABLE_STD_DEV_COUNT)
281 low_std_dev_count = 0;
291 _cairo_stats_compute (&stats, times, i);
292 if (count_func != NULL) {
293 double count = count_func (perf->cr, perf->size, perf->size);
294 fprintf (perf->summary,
295 "%.3f [%10lld/%d] %#8.3f %#8.3f %#5.2f%% %3d: %.2f\n",
296 stats.min_ticks /(double) loops,
297 (long long) stats.min_ticks, loops,
298 _cairo_time_to_s (stats.min_ticks) * 1000.0 / loops,
299 _cairo_time_to_s (stats.median_ticks) * 1000.0 / loops,
300 stats.std_dev * 100.0, stats.iterations,
301 count / _cairo_time_to_s (stats.min_ticks));
303 fprintf (perf->summary,
304 "%.3f [%10lld/%d] %#8.3f %#8.3f %#5.2f%% %3d\n",
305 stats.min_ticks /(double) loops,
306 (long long) stats.min_ticks, loops,
307 _cairo_time_to_s (stats.min_ticks) * 1000.0 / loops,
308 _cairo_time_to_s (stats.median_ticks) * 1000.0 / loops,
309 stats.std_dev * 100.0, stats.iterations);
311 fflush (perf->summary);
319 usage (const char *argv0)
322 "Usage: %s [-flrv] [-i iterations] [test-names ...]\n"
324 "Run the cairo performance test suite over the given tests (all by default)\n"
325 "The command-line arguments are interpreted as follows:\n"
327 " -f fast; faster, less accurate\n"
328 " -i iterations; specify the number of iterations per test case\n"
329 " -l list only; just list selected test case names without executing\n"
330 " -r raw; display each time measurement instead of summary statistics\n"
331 " -v verbose; in raw mode also show the summaries\n"
333 "If test names are given they are used as sub-string matches so a command\n"
334 "such as \"%s text\" can be used to run all text test cases.\n",
339 parse_options (cairo_perf_t *perf,
345 const char *ms = NULL;
349 if ((iters = getenv("CAIRO_PERF_ITERATIONS")) && *iters)
350 perf->iterations = strtol(iters, NULL, 0);
352 perf->iterations = CAIRO_PERF_ITERATIONS_DEFAULT;
353 perf->exact_iterations = 0;
355 perf->fast_and_sloppy = FALSE;
356 perf->ms_per_iteration = CAIRO_PERF_ITERATION_MS_DEFAULT;
357 if ((ms = getenv("CAIRO_PERF_ITERATION_MS")) && *ms) {
358 perf->ms_per_iteration = atof(ms);
362 perf->list_only = FALSE;
365 perf->summary = stdout;
368 c = _cairo_getopt (argc, argv, "fi:lrv");
374 perf->fast_and_sloppy = TRUE;
376 perf->ms_per_iteration = CAIRO_PERF_ITERATION_MS_FAST;
379 perf->exact_iterations = TRUE;
380 perf->iterations = strtoul (optarg, &end, 10);
382 fprintf (stderr, "Invalid argument for -i (not an integer): %s\n",
388 perf->list_only = TRUE;
392 perf->summary = NULL;
398 fprintf (stderr, "Internal error: unhandled option: %c\n", c);
406 if (verbose && perf->summary == NULL)
407 perf->summary = stderr;
410 perf->names = &argv[optind];
411 perf->num_names = argc - optind;
416 check_cpu_affinity (void)
418 #ifdef HAVE_SCHED_GETAFFINITY
423 if (sched_getaffinity(0, sizeof(affinity), &affinity)) {
424 perror("sched_getaffinity");
428 for(i = 0, cpu_count = 0; i < CPU_SETSIZE; ++i) {
429 if (CPU_ISSET(i, &affinity))
435 "WARNING: cairo-perf has not been bound to a single CPU.\n",
443 "WARNING: Cannot check CPU affinity for this platform.\n",
450 cairo_perf_fini (cairo_perf_t *perf)
452 cairo_boilerplate_free_targets (perf->targets);
453 cairo_boilerplate_fini ();
456 cairo_debug_reset_static_data ();
469 cairo_surface_t *surface;
471 parse_options (&perf, argc, argv);
473 if (check_cpu_affinity()) {
475 "NOTICE: cairo-perf and the X server should be bound to CPUs (either the same\n"
476 "or separate) on SMP systems. Not doing so causes random results when the X\n"
477 "server is moved to or from cairo-perf's CPU during the benchmarks:\n"
479 " $ sudo taskset -cp 0 $(pidof X)\n"
480 " $ taskset -cp 1 $$\n"
482 "See taskset(1) for information about changing CPU affinity.\n",
486 perf.targets = cairo_boilerplate_get_targets (&perf.num_targets, NULL);
487 perf.times = xmalloc (perf.iterations * sizeof (cairo_time_t));
489 for (i = 0; i < perf.num_targets; i++) {
490 const cairo_boilerplate_target_t *target = perf.targets[i];
492 if (! target->is_measurable)
495 perf.target = target;
496 perf.test_number = 0;
498 for (j = 0; perf_cases[j].run; j++) {
499 const cairo_perf_case_t *perf_case = &perf_cases[j];
501 if (! perf_case->enabled (&perf))
504 for (perf.size = perf_case->min_size;
505 perf.size <= perf_case->max_size;
510 surface = (target->create_surface) (NULL,
512 perf.size, perf.size,
513 perf.size, perf.size,
514 CAIRO_BOILERPLATE_MODE_PERF,
516 if (surface == NULL) {
518 "Error: Failed to create target surface: %s\n",
523 cairo_perf_timer_set_synchronize (target->synchronize, closure);
525 perf.cr = cairo_create (surface);
527 perf_case->run (&perf, perf.cr, perf.size, perf.size);
529 if (cairo_status (perf.cr)) {
530 fprintf (stderr, "Error: Test left cairo in an error state: %s\n",
531 cairo_status_to_string (cairo_status (perf.cr)));
534 cairo_destroy (perf.cr);
535 cairo_surface_destroy (surface);
538 target->cleanup (closure);
543 cairo_perf_fini (&perf);
548 #define FUNC(f) f, f##_enabled
549 const cairo_perf_case_t perf_cases[] = {
550 { FUNC(pixel), 1, 1 },
551 { FUNC(a1_pixel), 1, 1 },
552 { FUNC(paint), 64, 512},
553 { FUNC(paint_with_alpha), 64, 512},
554 { FUNC(fill), 64, 512},
555 { FUNC(stroke), 64, 512},
556 { FUNC(text), 64, 512},
557 { FUNC(glyphs), 64, 512},
558 { FUNC(mask), 64, 512},
559 { FUNC(line), 32, 512},
560 { FUNC(a1_line), 32, 512},
561 { FUNC(curve), 32, 512},
562 { FUNC(a1_curve), 32, 512},
563 { FUNC(disjoint), 64, 512},
564 { FUNC(hatching), 64, 512},
565 { FUNC(tessellate), 100, 100},
566 { FUNC(subimage_copy), 16, 512},
567 { FUNC(hash_table), 16, 16},
568 { FUNC(pattern_create_radial), 16, 16},
569 { FUNC(zrusin), 415, 415},
570 { FUNC(world_map), 800, 800},
571 { FUNC(box_outline), 100, 100},
572 { FUNC(mosaic), 800, 800 },
573 { FUNC(long_lines), 100, 100},
574 { FUNC(unaligned_clip), 100, 100},
575 { FUNC(rectangles), 512, 512},
576 { FUNC(rounded_rectangles), 512, 512},
577 { FUNC(long_dashed_lines), 512, 512},
578 { FUNC(composite_checker), 16, 512},
579 { FUNC(twin), 800, 800},
580 { FUNC(dragon), 1024, 1024 },
581 { FUNC(sierpinski), 32, 1024 },
582 { FUNC(pythagoras_tree), 768, 768 },
583 { FUNC(intersections), 512, 512 },
584 { FUNC(many_strokes), 32, 512 },
585 { FUNC(wide_strokes), 32, 512 },
586 { FUNC(many_fills), 32, 512 },
587 { FUNC(wide_fills), 32, 512 },
588 { FUNC(many_curves), 32, 512 },
589 { FUNC(spiral), 512, 512 },
590 { FUNC(wave), 500, 500 },
591 { FUNC(fill_clip), 16, 512 },
592 { FUNC(tiger), 16, 1024 },