1 /* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
2 /*
3 * Copyright © 2006 Mozilla Corporation
4 * Copyright © 2006 Red Hat, Inc.
5 *
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.
16 *
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.
24 *
25 * Authors: Vladimir Vukicevic <vladimir@pobox.com>
26 * Carl Worth <cworth@cworth.org>
27 */
28
29 #define _GNU_SOURCE 1 /* for sched_getaffinity() */
30
31 #include "cairo-perf.h"
32 #include "cairo-stats.h"
33
34 #include "cairo-boilerplate-getopt.h"
35
36 /* For basename */
37 #ifdef HAVE_LIBGEN_H
38 #include <libgen.h>
39 #endif
40
41 #if HAVE_FCFINI
42 #include <fontconfig/fontconfig.h>
43 #endif
44
45 #ifdef HAVE_SCHED_H
46 #define _WITH_CPU_SET_T
47 #include <sched.h>
48 #endif
49
50 #define CAIRO_PERF_ITERATIONS_DEFAULT 100
51 #define CAIRO_PERF_LOW_STD_DEV 0.03
52 #define CAIRO_PERF_STABLE_STD_DEV_COUNT 5
53 #define CAIRO_PERF_ITERATION_MS_DEFAULT 2000
54 #define CAIRO_PERF_ITERATION_MS_FAST 5
55
56 typedef struct _cairo_perf_case {
57 CAIRO_PERF_RUN_DECL (*run);
58 cairo_bool_t (*enabled) (cairo_perf_t *perf);
59 unsigned int min_size;
60 unsigned int max_size;
61 } cairo_perf_case_t;
62
63 const cairo_perf_case_t perf_cases[];
64
65 static const char *
_content_to_string(cairo_content_t content,cairo_bool_t similar)66 _content_to_string (cairo_content_t content,
67 cairo_bool_t similar)
68 {
69 switch (content|similar) {
70 case CAIRO_CONTENT_COLOR:
71 return "rgb";
72 case CAIRO_CONTENT_COLOR|1:
73 return "rgb&";
74 case CAIRO_CONTENT_ALPHA:
75 return "a";
76 case CAIRO_CONTENT_ALPHA|1:
77 return "a&";
78 case CAIRO_CONTENT_COLOR_ALPHA:
79 return "rgba";
80 case CAIRO_CONTENT_COLOR_ALPHA|1:
81 return "rgba&";
82 default:
83 return "<unknown_content>";
84 }
85 }
86
87 static cairo_bool_t
cairo_perf_has_similar(cairo_perf_t * perf)88 cairo_perf_has_similar (cairo_perf_t *perf)
89 {
90 cairo_surface_t *target;
91
92 if (getenv ("CAIRO_TEST_SIMILAR") == NULL)
93 return FALSE;
94
95 /* exclude the image backend */
96 target = cairo_get_target (perf->cr);
97 if (cairo_surface_get_type (target) == CAIRO_SURFACE_TYPE_IMAGE)
98 return FALSE;
99
100 return TRUE;
101 }
102
103 cairo_bool_t
cairo_perf_can_run(cairo_perf_t * perf,const char * name,cairo_bool_t * is_explicit)104 cairo_perf_can_run (cairo_perf_t *perf,
105 const char *name,
106 cairo_bool_t *is_explicit)
107 {
108 unsigned int i;
109
110 if (is_explicit)
111 *is_explicit = FALSE;
112
113 if (perf->num_names == 0)
114 return TRUE;
115
116 for (i = 0; i < perf->num_names; i++) {
117 if (strstr (name, perf->names[i])) {
118 if (is_explicit)
119 *is_explicit = FALSE;
120 return TRUE;
121 }
122 }
123
124 return FALSE;
125 }
126
127 static unsigned
cairo_perf_calibrate(cairo_perf_t * perf,cairo_perf_func_t perf_func)128 cairo_perf_calibrate (cairo_perf_t *perf,
129 cairo_perf_func_t perf_func)
130 {
131 cairo_time_t calibration, calibration_max;
132 unsigned loops, min_loops;
133
134 min_loops = 1;
135 calibration = perf_func (perf->cr, perf->size, perf->size, min_loops);
136
137 if (!perf->fast_and_sloppy) {
138 calibration_max = _cairo_time_from_s (perf->ms_per_iteration * 0.0001 / 4);
139 while (calibration < calibration_max) {
140 min_loops *= 2;
141 calibration = perf_func (perf->cr, perf->size, perf->size, min_loops);
142 }
143 }
144
145 /* XXX
146 * Compute the number of loops required for the timing
147 * interval to be perf->ms_per_iteration milliseconds. This
148 * helps to eliminate sampling variance due to timing and
149 * other systematic errors. However, it also hides
150 * synchronisation overhead as we attempt to process a large
151 * batch of identical operations in a single shot. This can be
152 * considered both good and bad... It would be good to perform
153 * a more rigorous analysis of the synchronisation overhead,
154 * that is to estimate the time for loop=0.
155 */
156 loops = _cairo_time_from_s (perf->ms_per_iteration * 0.001 * min_loops / calibration);
157 min_loops = perf->fast_and_sloppy ? 1 : 10;
158 if (loops < min_loops)
159 loops = min_loops;
160
161 return loops;
162 }
163
164 void
cairo_perf_run(cairo_perf_t * perf,const char * name,cairo_perf_func_t perf_func,cairo_count_func_t count_func)165 cairo_perf_run (cairo_perf_t *perf,
166 const char *name,
167 cairo_perf_func_t perf_func,
168 cairo_count_func_t count_func)
169 {
170 static cairo_bool_t first_run = TRUE;
171 unsigned int i, similar, similar_iters;
172 cairo_time_t *times;
173 cairo_stats_t stats = {0.0, 0.0};
174 int low_std_dev_count;
175
176 if (perf->list_only) {
177 printf ("%s\n", name);
178 return;
179 }
180
181 if (first_run) {
182 if (perf->raw) {
183 printf ("[ # ] %s.%-s %s %s %s ...\n",
184 "backend", "content", "test-size", "ticks-per-ms", "time(ticks)");
185 }
186
187 if (perf->summary) {
188 fprintf (perf->summary,
189 "[ # ] %8s.%-4s %28s %8s %8s %5s %5s %s %s\n",
190 "backend", "content", "test-size", "min(ticks)", "min(ms)", "median(ms)",
191 "stddev.", "iterations", "overhead");
192 }
193 first_run = FALSE;
194 }
195
196 times = perf->times;
197
198 if (getenv ("CAIRO_PERF_OUTPUT") != NULL) { /* check output */
199 char *filename;
200 cairo_status_t status;
201
202 xasprintf (&filename, "%s.%s.%s.%d.out.png",
203 name, perf->target->name,
204 _content_to_string (perf->target->content, 0),
205 perf->size);
206 cairo_save (perf->cr);
207 perf_func (perf->cr, perf->size, perf->size, 1);
208 cairo_restore (perf->cr);
209 status = cairo_surface_write_to_png (cairo_get_target (perf->cr), filename);
210 if (status) {
211 fprintf (stderr, "Failed to generate output check '%s': %s\n",
212 filename, cairo_status_to_string (status));
213 return;
214 }
215
216 free (filename);
217 }
218
219 if (cairo_perf_has_similar (perf))
220 similar_iters = 2;
221 else
222 similar_iters = 1;
223
224 for (similar = 0; similar < similar_iters; similar++) {
225 unsigned loops;
226
227 if (perf->summary) {
228 fprintf (perf->summary,
229 "[%3d] %8s.%-5s %26s.%-3d ",
230 perf->test_number, perf->target->name,
231 _content_to_string (perf->target->content, similar),
232 name, perf->size);
233 fflush (perf->summary);
234 }
235
236 /* We run one iteration in advance to warm caches and calibrate. */
237 cairo_perf_yield ();
238 if (similar)
239 cairo_push_group_with_content (perf->cr,
240 cairo_boilerplate_content (perf->target->content));
241 else
242 cairo_save (perf->cr);
243 perf_func (perf->cr, perf->size, perf->size, 1);
244 loops = cairo_perf_calibrate (perf, perf_func);
245 if (similar)
246 cairo_pattern_destroy (cairo_pop_group (perf->cr));
247 else
248 cairo_restore (perf->cr);
249
250 low_std_dev_count = 0;
251 for (i =0; i < perf->iterations; i++) {
252 cairo_perf_yield ();
253 if (similar)
254 cairo_push_group_with_content (perf->cr,
255 cairo_boilerplate_content (perf->target->content));
256 else
257 cairo_save (perf->cr);
258 times[i] = perf_func (perf->cr, perf->size, perf->size, loops) ;
259 if (similar)
260 cairo_pattern_destroy (cairo_pop_group (perf->cr));
261 else
262 cairo_restore (perf->cr);
263 if (perf->raw) {
264 if (i == 0)
265 printf ("[*] %s.%s %s.%d %g",
266 perf->target->name,
267 _content_to_string (perf->target->content, similar),
268 name, perf->size,
269 _cairo_time_to_double (_cairo_time_from_s (1.)) / 1000.);
270 printf (" %lld", (long long) (times[i] / (double) loops));
271 } else if (! perf->exact_iterations) {
272 if (i > 0) {
273 _cairo_stats_compute (&stats, times, i+1);
274
275 if (stats.std_dev <= CAIRO_PERF_LOW_STD_DEV) {
276 low_std_dev_count++;
277 if (low_std_dev_count >= CAIRO_PERF_STABLE_STD_DEV_COUNT)
278 break;
279 } else {
280 low_std_dev_count = 0;
281 }
282 }
283 }
284 }
285
286 if (perf->raw)
287 printf ("\n");
288
289 if (perf->summary) {
290 _cairo_stats_compute (&stats, times, i);
291 if (count_func != NULL) {
292 double count = count_func (perf->cr, perf->size, perf->size);
293 fprintf (perf->summary,
294 "%.3f [%10lld/%d] %#8.3f %#8.3f %#5.2f%% %3d: %.2f\n",
295 stats.min_ticks /(double) loops,
296 (long long) stats.min_ticks, loops,
297 _cairo_time_to_s (stats.min_ticks) * 1000.0 / loops,
298 _cairo_time_to_s (stats.median_ticks) * 1000.0 / loops,
299 stats.std_dev * 100.0, stats.iterations,
300 count / _cairo_time_to_s (stats.min_ticks));
301 } else {
302 fprintf (perf->summary,
303 "%.3f [%10lld/%d] %#8.3f %#8.3f %#5.2f%% %3d\n",
304 stats.min_ticks /(double) loops,
305 (long long) stats.min_ticks, loops,
306 _cairo_time_to_s (stats.min_ticks) * 1000.0 / loops,
307 _cairo_time_to_s (stats.median_ticks) * 1000.0 / loops,
308 stats.std_dev * 100.0, stats.iterations);
309 }
310 fflush (perf->summary);
311 }
312
313 perf->test_number++;
314 }
315 }
316
317 static void
usage(const char * argv0)318 usage (const char *argv0)
319 {
320 fprintf (stderr,
321 "Usage: %s [-flrv] [-i iterations] [test-names ...]\n"
322 "\n"
323 "Run the cairo performance test suite over the given tests (all by default)\n"
324 "The command-line arguments are interpreted as follows:\n"
325 "\n"
326 " -f fast; faster, less accurate\n"
327 " -i iterations; specify the number of iterations per test case\n"
328 " -l list only; just list selected test case names without executing\n"
329 " -r raw; display each time measurement instead of summary statistics\n"
330 " -v verbose; in raw mode also show the summaries\n"
331 "\n"
332 "If test names are given they are used as sub-string matches so a command\n"
333 "such as \"%s text\" can be used to run all text test cases.\n",
334 argv0, argv0);
335 }
336
337 static void
parse_options(cairo_perf_t * perf,int argc,char * argv[])338 parse_options (cairo_perf_t *perf,
339 int argc,
340 char *argv[])
341 {
342 int c;
343 const char *iters;
344 const char *ms = NULL;
345 char *end;
346 int verbose = 0;
347
348 if ((iters = getenv("CAIRO_PERF_ITERATIONS")) && *iters)
349 perf->iterations = strtol(iters, NULL, 0);
350 else
351 perf->iterations = CAIRO_PERF_ITERATIONS_DEFAULT;
352 perf->exact_iterations = 0;
353
354 perf->fast_and_sloppy = FALSE;
355 perf->ms_per_iteration = CAIRO_PERF_ITERATION_MS_DEFAULT;
356 if ((ms = getenv("CAIRO_PERF_ITERATION_MS")) && *ms) {
357 perf->ms_per_iteration = atof(ms);
358 }
359
360 perf->raw = FALSE;
361 perf->list_only = FALSE;
362 perf->names = NULL;
363 perf->num_names = 0;
364 perf->summary = stdout;
365
366 while (1) {
367 c = _cairo_getopt (argc, argv, "fi:lrv");
368 if (c == -1)
369 break;
370
371 switch (c) {
372 case 'f':
373 perf->fast_and_sloppy = TRUE;
374 if (ms == NULL)
375 perf->ms_per_iteration = CAIRO_PERF_ITERATION_MS_FAST;
376 break;
377 case 'i':
378 perf->exact_iterations = TRUE;
379 perf->iterations = strtoul (optarg, &end, 10);
380 if (*end != '\0') {
381 fprintf (stderr, "Invalid argument for -i (not an integer): %s\n",
382 optarg);
383 exit (1);
384 }
385 break;
386 case 'l':
387 perf->list_only = TRUE;
388 break;
389 case 'r':
390 perf->raw = TRUE;
391 perf->summary = NULL;
392 break;
393 case 'v':
394 verbose = 1;
395 break;
396 default:
397 fprintf (stderr, "Internal error: unhandled option: %c\n", c);
398 /* fall-through */
399 case '?':
400 usage (argv[0]);
401 exit (1);
402 }
403 }
404
405 if (verbose && perf->summary == NULL)
406 perf->summary = stderr;
407
408 if (optind < argc) {
409 perf->names = &argv[optind];
410 perf->num_names = argc - optind;
411 }
412 }
413
414 static int
check_cpu_affinity(void)415 check_cpu_affinity (void)
416 {
417 #ifdef HAVE_SCHED_GETAFFINITY
418
419 cpu_set_t affinity;
420 int i, cpu_count;
421
422 if (sched_getaffinity(0, sizeof(affinity), &affinity)) {
423 perror("sched_getaffinity");
424 return -1;
425 }
426
427 for(i = 0, cpu_count = 0; i < CPU_SETSIZE; ++i) {
428 if (CPU_ISSET(i, &affinity))
429 ++cpu_count;
430 }
431
432 if (cpu_count > 1) {
433 fputs(
434 "WARNING: cairo-perf has not been bound to a single CPU.\n",
435 stderr);
436 return -1;
437 }
438
439 return 0;
440 #else
441 fputs(
442 "WARNING: Cannot check CPU affinity for this platform.\n",
443 stderr);
444 return -1;
445 #endif
446 }
447
448 static void
cairo_perf_fini(cairo_perf_t * perf)449 cairo_perf_fini (cairo_perf_t *perf)
450 {
451 cairo_boilerplate_free_targets (perf->targets);
452 cairo_boilerplate_fini ();
453
454 free (perf->times);
455 cairo_debug_reset_static_data ();
456 #if HAVE_FCFINI
457 FcFini ();
458 #endif
459 }
460
461
462 int
main(int argc,char * argv[])463 main (int argc,
464 char *argv[])
465 {
466 int i, j;
467 cairo_perf_t perf;
468 cairo_surface_t *surface;
469
470 parse_options (&perf, argc, argv);
471
472 if (check_cpu_affinity()) {
473 fputs(
474 "NOTICE: cairo-perf and the X server should be bound to CPUs (either the same\n"
475 "or separate) on SMP systems. Not doing so causes random results when the X\n"
476 "server is moved to or from cairo-perf's CPU during the benchmarks:\n"
477 "\n"
478 " $ sudo taskset -cp 0 $(pidof X)\n"
479 " $ taskset -cp 1 $$\n"
480 "\n"
481 "See taskset(1) for information about changing CPU affinity.\n",
482 stderr);
483 }
484
485 perf.targets = cairo_boilerplate_get_targets (&perf.num_targets, NULL);
486 perf.times = xmalloc (perf.iterations * sizeof (cairo_time_t));
487
488 for (i = 0; i < perf.num_targets; i++) {
489 const cairo_boilerplate_target_t *target = perf.targets[i];
490
491 if (! target->is_measurable)
492 continue;
493
494 perf.target = target;
495 perf.test_number = 0;
496
497 for (j = 0; perf_cases[j].run; j++) {
498 const cairo_perf_case_t *perf_case = &perf_cases[j];
499
500 if (! perf_case->enabled (&perf))
501 continue;
502
503 for (perf.size = perf_case->min_size;
504 perf.size <= perf_case->max_size;
505 perf.size *= 2)
506 {
507 void *closure;
508
509 surface = (target->create_surface) (NULL,
510 target->content,
511 perf.size, perf.size,
512 perf.size, perf.size,
513 CAIRO_BOILERPLATE_MODE_PERF,
514 &closure);
515 if (surface == NULL) {
516 fprintf (stderr,
517 "Error: Failed to create target surface: %s\n",
518 target->name);
519 continue;
520 }
521
522 cairo_perf_timer_set_synchronize (target->synchronize, closure);
523
524 perf.cr = cairo_create (surface);
525
526 perf_case->run (&perf, perf.cr, perf.size, perf.size);
527
528 if (cairo_status (perf.cr)) {
529 fprintf (stderr, "Error: Test left cairo in an error state: %s\n",
530 cairo_status_to_string (cairo_status (perf.cr)));
531 }
532
533 cairo_destroy (perf.cr);
534 cairo_surface_destroy (surface);
535
536 if (target->cleanup)
537 target->cleanup (closure);
538 }
539 }
540 }
541
542 cairo_perf_fini (&perf);
543
544 return 0;
545 }
546
547 #define FUNC(f) f, f##_enabled
548 const cairo_perf_case_t perf_cases[] = {
549 { FUNC(pixel), 1, 1 },
550 { FUNC(a1_pixel), 1, 1 },
551 { FUNC(paint), 64, 512},
552 { FUNC(paint_with_alpha), 64, 512},
553 { FUNC(fill), 64, 512},
554 { FUNC(stroke), 64, 512},
555 { FUNC(text), 64, 512},
556 { FUNC(glyphs), 64, 512},
557 { FUNC(mask), 64, 512},
558 { FUNC(line), 32, 512},
559 { FUNC(a1_line), 32, 512},
560 { FUNC(curve), 32, 512},
561 { FUNC(a1_curve), 32, 512},
562 { FUNC(disjoint), 64, 512},
563 { FUNC(hatching), 64, 512},
564 { FUNC(tessellate), 100, 100},
565 { FUNC(subimage_copy), 16, 512},
566 { FUNC(hash_table), 16, 16},
567 { FUNC(pattern_create_radial), 16, 16},
568 { FUNC(zrusin), 415, 415},
569 { FUNC(world_map), 800, 800},
570 { FUNC(box_outline), 100, 100},
571 { FUNC(mosaic), 800, 800 },
572 { FUNC(long_lines), 100, 100},
573 { FUNC(unaligned_clip), 100, 100},
574 { FUNC(rectangles), 512, 512},
575 { FUNC(rounded_rectangles), 512, 512},
576 { FUNC(long_dashed_lines), 512, 512},
577 { FUNC(composite_checker), 16, 512},
578 { FUNC(twin), 800, 800},
579 { FUNC(dragon), 1024, 1024 },
580 { FUNC(sierpinski), 32, 1024 },
581 { FUNC(pythagoras_tree), 768, 768 },
582 { FUNC(intersections), 512, 512 },
583 { FUNC(many_strokes), 32, 512 },
584 { FUNC(wide_strokes), 32, 512 },
585 { FUNC(many_fills), 32, 512 },
586 { FUNC(wide_fills), 32, 512 },
587 { FUNC(many_curves), 32, 512 },
588 { FUNC(spiral), 512, 512 },
589 { FUNC(wave), 500, 500 },
590 { FUNC(fill_clip), 16, 512 },
591 { FUNC(tiger), 16, 1024 },
592 { NULL }
593 };
594