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