d6b52c446468ffa716c3cbedcfecb909589095d7
[framework/graphics/cairo.git] / perf / cairo-perf-micro.c
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-version.h" /* for the real version */
32
33 #include "cairo-perf.h"
34 #include "cairo-stats.h"
35
36 #include "cairo-boilerplate-getopt.h"
37
38 /* For basename */
39 #ifdef HAVE_LIBGEN_H
40 #include <libgen.h>
41 #endif
42
43 #if HAVE_FCFINI
44 #include <fontconfig/fontconfig.h>
45 #endif
46
47 #ifdef HAVE_SCHED_H
48 #include <sched.h>
49 #endif
50
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
56
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;
62 } cairo_perf_case_t;
63
64 const cairo_perf_case_t perf_cases[];
65
66 static const char *
67 _content_to_string (cairo_content_t content,
68                     cairo_bool_t    similar)
69 {
70     switch (content|similar) {
71     case CAIRO_CONTENT_COLOR:
72         return "rgb";
73     case CAIRO_CONTENT_COLOR|1:
74         return "rgb&";
75     case CAIRO_CONTENT_ALPHA:
76         return "a";
77     case CAIRO_CONTENT_ALPHA|1:
78         return "a&";
79     case CAIRO_CONTENT_COLOR_ALPHA:
80         return "rgba";
81     case CAIRO_CONTENT_COLOR_ALPHA|1:
82         return "rgba&";
83     default:
84         return "<unknown_content>";
85     }
86 }
87
88 static cairo_bool_t
89 cairo_perf_has_similar (cairo_perf_t *perf)
90 {
91     cairo_surface_t *target;
92
93     if (getenv ("CAIRO_TEST_SIMILAR") == NULL)
94         return FALSE;
95
96     /* exclude the image backend */
97     target = cairo_get_target (perf->cr);
98     if (cairo_surface_get_type (target) == CAIRO_SURFACE_TYPE_IMAGE)
99         return FALSE;
100
101     return TRUE;
102 }
103
104 cairo_bool_t
105 cairo_perf_can_run (cairo_perf_t *perf,
106                     const char   *name,
107                     cairo_bool_t *is_explicit)
108 {
109     unsigned int i;
110
111     if (is_explicit)
112         *is_explicit = FALSE;
113
114     if (perf->num_names == 0)
115         return TRUE;
116
117     for (i = 0; i < perf->num_names; i++) {
118         if (strstr (name, perf->names[i])) {
119             if (is_explicit)
120                 *is_explicit = FALSE;
121             return TRUE;
122         }
123     }
124
125     return FALSE;
126 }
127
128 static unsigned
129 cairo_perf_calibrate (cairo_perf_t      *perf,
130                       cairo_perf_func_t  perf_func)
131 {
132     cairo_time_t calibration, calibration_max;
133     unsigned loops, min_loops;
134
135     min_loops = 1;
136     calibration = perf_func (perf->cr, perf->size, perf->size, min_loops);
137
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) {
141             min_loops *= 2;
142             calibration = perf_func (perf->cr, perf->size, perf->size, min_loops);
143         }
144     }
145
146     /* XXX
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.
156      */
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)
160         loops = min_loops;
161
162     return loops;
163 }
164
165 void
166 cairo_perf_run (cairo_perf_t       *perf,
167                 const char         *name,
168                 cairo_perf_func_t   perf_func,
169                 cairo_count_func_t  count_func)
170 {
171     static cairo_bool_t first_run = TRUE;
172     unsigned int i, similar, similar_iters;
173     cairo_time_t *times;
174     cairo_stats_t stats = {0.0, 0.0};
175     int low_std_dev_count;
176
177     if (perf->list_only) {
178         printf ("%s\n", name);
179         return;
180     }
181
182     if (first_run) {
183         if (perf->raw) {
184             printf ("[ # ] %s.%-s %s %s %s ...\n",
185                     "backend", "content", "test-size", "ticks-per-ms", "time(ticks)");
186         }
187
188         if (perf->summary) {
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");
193         }
194         first_run = FALSE;
195     }
196
197     times = perf->times;
198
199     if (getenv ("CAIRO_PERF_OUTPUT") != NULL) { /* check output */
200         char *filename;
201         cairo_status_t status;
202
203         xasprintf (&filename, "%s.%s.%s.%d.out.png",
204                    name, perf->target->name,
205                    _content_to_string (perf->target->content, 0),
206                    perf->size);
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);
211         if (status) {
212             fprintf (stderr, "Failed to generate output check '%s': %s\n",
213                      filename, cairo_status_to_string (status));
214             return;
215         }
216
217         free (filename);
218     }
219
220     if (cairo_perf_has_similar (perf))
221         similar_iters = 2;
222     else
223         similar_iters = 1;
224
225     for (similar = 0; similar < similar_iters; similar++) {
226         unsigned loops;
227
228         if (perf->summary) {
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),
233                      name, perf->size);
234             fflush (perf->summary);
235         }
236
237         /* We run one iteration in advance to warm caches and calibrate. */
238         cairo_perf_yield ();
239         if (similar)
240             cairo_push_group_with_content (perf->cr,
241                                            cairo_boilerplate_content (perf->target->content));
242         else
243             cairo_save (perf->cr);
244         perf_func (perf->cr, perf->size, perf->size, 1);
245         loops = cairo_perf_calibrate (perf, perf_func);
246         if (similar)
247             cairo_pattern_destroy (cairo_pop_group (perf->cr));
248         else
249             cairo_restore (perf->cr);
250
251         low_std_dev_count = 0;
252         for (i =0; i < perf->iterations; i++) {
253             cairo_perf_yield ();
254             if (similar)
255                 cairo_push_group_with_content (perf->cr,
256                                                cairo_boilerplate_content (perf->target->content));
257             else
258                 cairo_save (perf->cr);
259             times[i] = perf_func (perf->cr, perf->size, perf->size, loops) ;
260             if (similar)
261                 cairo_pattern_destroy (cairo_pop_group (perf->cr));
262             else
263                 cairo_restore (perf->cr);
264             if (perf->raw) {
265                 if (i == 0)
266                     printf ("[*] %s.%s %s.%d %g",
267                             perf->target->name,
268                             _content_to_string (perf->target->content, similar),
269                             name, perf->size,
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) {
273                 if (i > 0) {
274                     _cairo_stats_compute (&stats, times, i+1);
275
276                     if (stats.std_dev <= CAIRO_PERF_LOW_STD_DEV) {
277                         low_std_dev_count++;
278                         if (low_std_dev_count >= CAIRO_PERF_STABLE_STD_DEV_COUNT)
279                             break;
280                     } else {
281                         low_std_dev_count = 0;
282                     }
283                 }
284             }
285         }
286
287         if (perf->raw)
288             printf ("\n");
289
290         if (perf->summary) {
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));
302             } else {
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);
310             }
311             fflush (perf->summary);
312         }
313
314         perf->test_number++;
315     }
316 }
317
318 static void
319 usage (const char *argv0)
320 {
321     fprintf (stderr,
322 "Usage: %s [-flrv] [-i iterations] [test-names ...]\n"
323 "\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"
326 "\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"
332 "\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",
335              argv0, argv0);
336 }
337
338 static void
339 parse_options (cairo_perf_t *perf,
340                int           argc,
341                char         *argv[])
342 {
343     int c;
344     const char *iters;
345     const char *ms = NULL;
346     char *end;
347     int verbose = 0;
348
349     if ((iters = getenv("CAIRO_PERF_ITERATIONS")) && *iters)
350         perf->iterations = strtol(iters, NULL, 0);
351     else
352         perf->iterations = CAIRO_PERF_ITERATIONS_DEFAULT;
353     perf->exact_iterations = 0;
354
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);
359     }
360
361     perf->raw = FALSE;
362     perf->list_only = FALSE;
363     perf->names = NULL;
364     perf->num_names = 0;
365     perf->summary = stdout;
366
367     while (1) {
368         c = _cairo_getopt (argc, argv, "fi:lrv");
369         if (c == -1)
370             break;
371
372         switch (c) {
373         case 'f':
374             perf->fast_and_sloppy = TRUE;
375             if (ms == NULL)
376                 perf->ms_per_iteration = CAIRO_PERF_ITERATION_MS_FAST;
377             break;
378         case 'i':
379             perf->exact_iterations = TRUE;
380             perf->iterations = strtoul (optarg, &end, 10);
381             if (*end != '\0') {
382                 fprintf (stderr, "Invalid argument for -i (not an integer): %s\n",
383                          optarg);
384                 exit (1);
385             }
386             break;
387         case 'l':
388             perf->list_only = TRUE;
389             break;
390         case 'r':
391             perf->raw = TRUE;
392             perf->summary = NULL;
393             break;
394         case 'v':
395             verbose = 1;
396             break;
397         default:
398             fprintf (stderr, "Internal error: unhandled option: %c\n", c);
399             /* fall-through */
400         case '?':
401             usage (argv[0]);
402             exit (1);
403         }
404     }
405
406     if (verbose && perf->summary == NULL)
407         perf->summary = stderr;
408
409     if (optind < argc) {
410         perf->names = &argv[optind];
411         perf->num_names = argc - optind;
412     }
413 }
414
415 static int 
416 check_cpu_affinity (void)
417 {
418 #ifdef HAVE_SCHED_GETAFFINITY
419
420     cpu_set_t affinity;
421     int i, cpu_count;
422
423     if (sched_getaffinity(0, sizeof(affinity), &affinity)) {
424         perror("sched_getaffinity");
425         return -1;
426     }
427
428     for(i = 0, cpu_count = 0; i < CPU_SETSIZE; ++i) {
429         if (CPU_ISSET(i, &affinity))
430             ++cpu_count;
431     }
432
433     if (cpu_count > 1) {
434         fputs(
435             "WARNING: cairo-perf has not been bound to a single CPU.\n",
436             stderr);
437         return -1;
438     }
439
440     return 0;
441 #else
442     fputs(
443         "WARNING: Cannot check CPU affinity for this platform.\n",
444         stderr);
445     return -1;
446 #endif
447 }
448
449 static void
450 cairo_perf_fini (cairo_perf_t *perf)
451 {
452     cairo_boilerplate_free_targets (perf->targets);
453     cairo_boilerplate_fini ();
454
455     free (perf->times);
456     cairo_debug_reset_static_data ();
457 #if HAVE_FCFINI
458     FcFini ();
459 #endif
460 }
461
462
463 int
464 main (int   argc,
465       char *argv[])
466 {
467     int i, j;
468     cairo_perf_t perf;
469     cairo_surface_t *surface;
470
471     parse_options (&perf, argc, argv);
472
473     if (check_cpu_affinity()) {
474         fputs(
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"
478             "\n"
479             "    $ sudo taskset -cp 0 $(pidof X)\n"
480             "    $ taskset -cp 1 $$\n"
481             "\n"
482             "See taskset(1) for information about changing CPU affinity.\n",
483             stderr);
484     }
485
486     perf.targets = cairo_boilerplate_get_targets (&perf.num_targets, NULL);
487     perf.times = xmalloc (perf.iterations * sizeof (cairo_time_t));
488
489     for (i = 0; i < perf.num_targets; i++) {
490         const cairo_boilerplate_target_t *target = perf.targets[i];
491
492         if (! target->is_measurable)
493             continue;
494
495         perf.target = target;
496         perf.test_number = 0;
497
498         for (j = 0; perf_cases[j].run; j++) {
499             const cairo_perf_case_t *perf_case = &perf_cases[j];
500
501             if (! perf_case->enabled (&perf))
502                 continue;
503
504             for (perf.size = perf_case->min_size;
505                  perf.size <= perf_case->max_size;
506                  perf.size *= 2)
507             {
508                 void *closure;
509
510                 surface = (target->create_surface) (NULL,
511                                                     target->content,
512                                                     perf.size, perf.size,
513                                                     perf.size, perf.size,
514                                                     CAIRO_BOILERPLATE_MODE_PERF,
515                                                     &closure);
516                 if (surface == NULL) {
517                     fprintf (stderr,
518                              "Error: Failed to create target surface: %s\n",
519                              target->name);
520                     continue;
521                 }
522
523                 cairo_perf_timer_set_synchronize (target->synchronize, closure);
524
525                 perf.cr = cairo_create (surface);
526
527                 perf_case->run (&perf, perf.cr, perf.size, perf.size);
528
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)));
532                 }
533
534                 cairo_destroy (perf.cr);
535                 cairo_surface_destroy (surface);
536
537                 if (target->cleanup)
538                     target->cleanup (closure);
539             }
540         }
541     }
542
543     cairo_perf_fini (&perf);
544
545     return 0;
546 }
547
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 },
593     { NULL }
594 };