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
#include "config.h"
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
#include <sched.h>
47
#endif
48

            
49
#define CAIRO_PERF_ITERATIONS_DEFAULT		100
50
#define CAIRO_PERF_LOW_STD_DEV			0.03
51
#define CAIRO_PERF_STABLE_STD_DEV_COUNT		5
52
#define CAIRO_PERF_ITERATION_MS_DEFAULT		2000
53
#define CAIRO_PERF_ITERATION_MS_FAST		5
54

            
55
typedef struct _cairo_perf_case {
56
    CAIRO_PERF_RUN_DECL (*run);
57
    cairo_bool_t (*enabled) (cairo_perf_t *perf);
58
    unsigned int min_size;
59
    unsigned int max_size;
60
} cairo_perf_case_t;
61

            
62
const cairo_perf_case_t perf_cases[];
63

            
64
static const char *
65
_content_to_string (cairo_content_t content,
66
		    cairo_bool_t    similar)
67
{
68
    switch (content|similar) {
69
    case CAIRO_CONTENT_COLOR:
70
	return "rgb";
71
    case CAIRO_CONTENT_COLOR|1:
72
	return "rgb&";
73
    case CAIRO_CONTENT_ALPHA:
74
	return "a";
75
    case CAIRO_CONTENT_ALPHA|1:
76
	return "a&";
77
    case CAIRO_CONTENT_COLOR_ALPHA:
78
	return "rgba";
79
    case CAIRO_CONTENT_COLOR_ALPHA|1:
80
	return "rgba&";
81
    default:
82
	return "<unknown_content>";
83
    }
84
}
85

            
86
static cairo_bool_t
87
cairo_perf_has_similar (cairo_perf_t *perf)
88
{
89
    cairo_surface_t *target;
90

            
91
    if (getenv ("CAIRO_TEST_SIMILAR") == NULL)
92
	return FALSE;
93

            
94
    /* exclude the image backend */
95
    target = cairo_get_target (perf->cr);
96
    if (cairo_surface_get_type (target) == CAIRO_SURFACE_TYPE_IMAGE)
97
	return FALSE;
98

            
99
    return TRUE;
100
}
101

            
102
cairo_bool_t
103
cairo_perf_can_run (cairo_perf_t *perf,
104
		    const char	 *name,
105
		    cairo_bool_t *is_explicit)
106
{
107
    unsigned int i;
108

            
109
    if (is_explicit)
110
	*is_explicit = FALSE;
111

            
112
    if (perf->num_names == 0)
113
	return TRUE;
114

            
115
    for (i = 0; i < perf->num_names; i++) {
116
	if (strstr (name, perf->names[i])) {
117
	    if (is_explicit)
118
		*is_explicit = FALSE;
119
	    return TRUE;
120
	}
121
    }
122

            
123
    return FALSE;
124
}
125

            
126
static unsigned
127
cairo_perf_calibrate (cairo_perf_t	*perf,
128
		      cairo_perf_func_t  perf_func)
129
{
130
    cairo_time_t calibration, calibration_max;
131
    unsigned loops, min_loops;
132

            
133
    min_loops = 1;
134
    calibration = perf_func (perf->cr, perf->size, perf->size, min_loops);
135

            
136
    if (!perf->fast_and_sloppy) {
137
	calibration_max = _cairo_time_from_s (perf->ms_per_iteration * 0.0001 / 4);
138
	while (calibration < calibration_max) {
139
	    min_loops *= 2;
140
	    calibration = perf_func (perf->cr, perf->size, perf->size, min_loops);
141
	}
142
    }
143

            
144
    /* XXX
145
     * Compute the number of loops required for the timing
146
     * interval to be perf->ms_per_iteration milliseconds. This
147
     * helps to eliminate sampling variance due to timing and
148
     * other systematic errors.  However, it also hides
149
     * synchronisation overhead as we attempt to process a large
150
     * batch of identical operations in a single shot. This can be
151
     * considered both good and bad... It would be good to perform
152
     * a more rigorous analysis of the synchronisation overhead,
153
     * that is to estimate the time for loop=0.
154
     */
155
    loops = _cairo_time_from_s (perf->ms_per_iteration * 0.001 * min_loops / calibration);
156
    min_loops = perf->fast_and_sloppy ? 1 : 10;
157
    if (loops < min_loops)
158
	loops = min_loops;
159

            
160
    return loops;
161
}
162

            
163
void
164
cairo_perf_run (cairo_perf_t	   *perf,
165
		const char	   *name,
166
		cairo_perf_func_t   perf_func,
167
		cairo_count_func_t  count_func)
168
{
169
    static cairo_bool_t first_run = TRUE;
170
    unsigned int i, similar, similar_iters;
171
    cairo_time_t *times;
172
    cairo_stats_t stats = {0.0, 0.0};
173
    int low_std_dev_count;
174

            
175
    if (perf->list_only) {
176
	printf ("%s\n", name);
177
	return;
178
    }
179

            
180
    if (first_run) {
181
	if (perf->raw) {
182
	    printf ("[ # ] %s.%-s %s %s %s ...\n",
183
		    "backend", "content", "test-size", "ticks-per-ms", "time(ticks)");
184
	}
185

            
186
	if (perf->summary) {
187
	    fprintf (perf->summary,
188
		     "[ # ] %8s.%-4s %28s %8s %8s %5s %5s %s %s\n",
189
		     "backend", "content", "test-size", "min(ticks)", "min(ms)", "median(ms)",
190
		     "stddev.", "iterations", "overhead");
191
	}
192
	first_run = FALSE;
193
    }
194

            
195
    times = perf->times;
196

            
197
    if (getenv ("CAIRO_PERF_OUTPUT") != NULL) { /* check output */
198
	char *filename;
199
	cairo_status_t status;
200

            
201
	xasprintf (&filename, "%s.%s.%s.%d.out.png",
202
		   name, perf->target->name,
203
		   _content_to_string (perf->target->content, 0),
204
		   perf->size);
205
	cairo_save (perf->cr);
206
	perf_func (perf->cr, perf->size, perf->size, 1);
207
	cairo_restore (perf->cr);
208
	status = cairo_surface_write_to_png (cairo_get_target (perf->cr), filename);
209
	if (status) {
210
	    fprintf (stderr, "Failed to generate output check '%s': %s\n",
211
		     filename, cairo_status_to_string (status));
212
	    return;
213
	}
214

            
215
	free (filename);
216
    }
217

            
218
    if (cairo_perf_has_similar (perf))
219
	similar_iters = 2;
220
    else
221
	similar_iters = 1;
222

            
223
    for (similar = 0; similar < similar_iters; similar++) {
224
	unsigned loops;
225

            
226
	if (perf->summary) {
227
	    fprintf (perf->summary,
228
		     "[%3d] %8s.%-5s %26s.%-3d ",
229
		     perf->test_number, perf->target->name,
230
		     _content_to_string (perf->target->content, similar),
231
		     name, perf->size);
232
	    fflush (perf->summary);
233
	}
234

            
235
	/* We run one iteration in advance to warm caches and calibrate. */
236
	cairo_perf_yield ();
237
	if (similar)
238
	    cairo_push_group_with_content (perf->cr,
239
					   cairo_boilerplate_content (perf->target->content));
240
	else
241
	    cairo_save (perf->cr);
242
	perf_func (perf->cr, perf->size, perf->size, 1);
243
	loops = cairo_perf_calibrate (perf, perf_func);
244
	if (similar)
245
	    cairo_pattern_destroy (cairo_pop_group (perf->cr));
246
	else
247
	    cairo_restore (perf->cr);
248

            
249
	low_std_dev_count = 0;
250
	for (i =0; i < perf->iterations; i++) {
251
	    cairo_perf_yield ();
252
	    if (similar)
253
		cairo_push_group_with_content (perf->cr,
254
					       cairo_boilerplate_content (perf->target->content));
255
	    else
256
		cairo_save (perf->cr);
257
	    times[i] = perf_func (perf->cr, perf->size, perf->size, loops) ;
258
	    if (similar)
259
		cairo_pattern_destroy (cairo_pop_group (perf->cr));
260
	    else
261
		cairo_restore (perf->cr);
262
	    if (perf->raw) {
263
		if (i == 0)
264
		    printf ("[*] %s.%s %s.%d %g",
265
			    perf->target->name,
266
			    _content_to_string (perf->target->content, similar),
267
			    name, perf->size,
268
			    _cairo_time_to_double (_cairo_time_from_s (1.)) / 1000.);
269
		printf (" %lld", (long long) (times[i] / (double) loops));
270
	    } else if (! perf->exact_iterations) {
271
		if (i > 0) {
272
		    _cairo_stats_compute (&stats, times, i+1);
273

            
274
		    if (stats.std_dev <= CAIRO_PERF_LOW_STD_DEV) {
275
			low_std_dev_count++;
276
			if (low_std_dev_count >= CAIRO_PERF_STABLE_STD_DEV_COUNT)
277
			    break;
278
		    } else {
279
			low_std_dev_count = 0;
280
		    }
281
		}
282
	    }
283
	}
284

            
285
	if (perf->raw)
286
	    printf ("\n");
287

            
288
	if (perf->summary) {
289
	    _cairo_stats_compute (&stats, times, i);
290
	    if (count_func != NULL) {
291
		double count = count_func (perf->cr, perf->size, perf->size);
292
		fprintf (perf->summary,
293
			 "%.3f [%10lld/%d] %#8.3f %#8.3f %#5.2f%% %3d: %.2f\n",
294
			 stats.min_ticks /(double) loops,
295
			 (long long) stats.min_ticks, loops,
296
			 _cairo_time_to_s (stats.min_ticks) * 1000.0 / loops,
297
			 _cairo_time_to_s (stats.median_ticks) * 1000.0 / loops,
298
			 stats.std_dev * 100.0, stats.iterations,
299
			 count / _cairo_time_to_s (stats.min_ticks));
300
	    } else {
301
		fprintf (perf->summary,
302
			 "%.3f [%10lld/%d] %#8.3f %#8.3f %#5.2f%% %3d\n",
303
			 stats.min_ticks /(double) loops,
304
			 (long long) stats.min_ticks, loops,
305
			 _cairo_time_to_s (stats.min_ticks) * 1000.0 / loops,
306
			 _cairo_time_to_s (stats.median_ticks) * 1000.0 / loops,
307
			 stats.std_dev * 100.0, stats.iterations);
308
	    }
309
	    fflush (perf->summary);
310
	}
311

            
312
	perf->test_number++;
313
    }
314
}
315

            
316
static void
317
usage (const char *argv0)
318
{
319
    fprintf (stderr,
320
"Usage: %s [-flrv] [-i iterations] [test-names ...]\n"
321
"\n"
322
"Run the cairo performance test suite over the given tests (all by default)\n"
323
"The command-line arguments are interpreted as follows:\n"
324
"\n"
325
"  -f	fast; faster, less accurate\n"
326
"  -i	iterations; specify the number of iterations per test case\n"
327
"  -l	list only; just list selected test case names without executing\n"
328
"  -r	raw; display each time measurement instead of summary statistics\n"
329
"  -v	verbose; in raw mode also show the summaries\n"
330
"\n"
331
"If test names are given they are used as sub-string matches so a command\n"
332
"such as \"%s text\" can be used to run all text test cases.\n",
333
	     argv0, argv0);
334
}
335

            
336
static void
337
parse_options (cairo_perf_t *perf,
338
	       int	     argc,
339
	       char	    *argv[])
340
{
341
    int c;
342
    const char *iters;
343
    const char *ms = NULL;
344
    char *end;
345
    int verbose = 0;
346

            
347
    if ((iters = getenv("CAIRO_PERF_ITERATIONS")) && *iters)
348
	perf->iterations = strtol(iters, NULL, 0);
349
    else
350
	perf->iterations = CAIRO_PERF_ITERATIONS_DEFAULT;
351
    perf->exact_iterations = 0;
352

            
353
    perf->fast_and_sloppy = FALSE;
354
    perf->ms_per_iteration = CAIRO_PERF_ITERATION_MS_DEFAULT;
355
    if ((ms = getenv("CAIRO_PERF_ITERATION_MS")) && *ms) {
356
	perf->ms_per_iteration = atof(ms);
357
    }
358

            
359
    perf->raw = FALSE;
360
    perf->list_only = FALSE;
361
    perf->names = NULL;
362
    perf->num_names = 0;
363
    perf->summary = stdout;
364

            
365
    while (1) {
366
	c = _cairo_getopt (argc, argv, "fi:lrv");
367
	if (c == -1)
368
	    break;
369

            
370
	switch (c) {
371
	case 'f':
372
	    perf->fast_and_sloppy = TRUE;
373
	    if (ms == NULL)
374
		perf->ms_per_iteration = CAIRO_PERF_ITERATION_MS_FAST;
375
	    break;
376
	case 'i':
377
	    perf->exact_iterations = TRUE;
378
	    perf->iterations = strtoul (optarg, &end, 10);
379
	    if (*end != '\0') {
380
		fprintf (stderr, "Invalid argument for -i (not an integer): %s\n",
381
			 optarg);
382
		exit (1);
383
	    }
384
	    break;
385
	case 'l':
386
	    perf->list_only = TRUE;
387
	    break;
388
	case 'r':
389
	    perf->raw = TRUE;
390
	    perf->summary = NULL;
391
	    break;
392
	case 'v':
393
	    verbose = 1;
394
	    break;
395
	default:
396
	    fprintf (stderr, "Internal error: unhandled option: %c\n", c);
397
	    /* fall-through */
398
	case '?':
399
	    usage (argv[0]);
400
	    exit (1);
401
	}
402
    }
403

            
404
    if (verbose && perf->summary == NULL)
405
	perf->summary = stderr;
406

            
407
    if (optind < argc) {
408
	perf->names = &argv[optind];
409
	perf->num_names = argc - optind;
410
    }
411
}
412

            
413
static int 
414
check_cpu_affinity (void)
415
{
416
#ifdef HAVE_SCHED_GETAFFINITY
417

            
418
    cpu_set_t affinity;
419
    int i, cpu_count;
420

            
421
    if (sched_getaffinity(0, sizeof(affinity), &affinity)) {
422
	perror("sched_getaffinity");
423
	return -1;
424
    }
425

            
426
    for(i = 0, cpu_count = 0; i < CPU_SETSIZE; ++i) {
427
	if (CPU_ISSET(i, &affinity))
428
	    ++cpu_count;
429
    }
430

            
431
    if (cpu_count > 1) {
432
	fputs(
433
	    "WARNING: cairo-perf has not been bound to a single CPU.\n",
434
	    stderr);
435
	return -1;
436
    }
437

            
438
    return 0;
439
#else
440
    fputs(
441
	"WARNING: Cannot check CPU affinity for this platform.\n",
442
	stderr);
443
    return -1;
444
#endif
445
}
446

            
447
static void
448
cairo_perf_fini (cairo_perf_t *perf)
449
{
450
    cairo_boilerplate_free_targets (perf->targets);
451
    cairo_boilerplate_fini ();
452

            
453
    free (perf->times);
454
    cairo_debug_reset_static_data ();
455
#if HAVE_FCFINI
456
    FcFini ();
457
#endif
458
}
459

            
460

            
461
int
462
main (int   argc,
463
      char *argv[])
464
{
465
    int i, j;
466
    cairo_perf_t perf;
467
    cairo_surface_t *surface;
468

            
469
    parse_options (&perf, argc, argv);
470

            
471
    if (check_cpu_affinity()) {
472
	fputs(
473
	    "NOTICE: cairo-perf and the X server should be bound to CPUs (either the same\n"
474
	    "or separate) on SMP systems. Not doing so causes random results when the X\n"
475
	    "server is moved to or from cairo-perf's CPU during the benchmarks:\n"
476
	    "\n"
477
	    "    $ sudo taskset -cp 0 $(pidof X)\n"
478
	    "    $ taskset -cp 1 $$\n"
479
	    "\n"
480
	    "See taskset(1) for information about changing CPU affinity.\n",
481
	    stderr);
482
    }
483

            
484
    perf.targets = cairo_boilerplate_get_targets (&perf.num_targets, NULL);
485
    perf.times = xmalloc (perf.iterations * sizeof (cairo_time_t));
486

            
487
    for (i = 0; i < perf.num_targets; i++) {
488
	const cairo_boilerplate_target_t *target = perf.targets[i];
489

            
490
	if (! target->is_measurable)
491
	    continue;
492

            
493
	perf.target = target;
494
	perf.test_number = 0;
495

            
496
	for (j = 0; perf_cases[j].run; j++) {
497
	    const cairo_perf_case_t *perf_case = &perf_cases[j];
498

            
499
	    if (! perf_case->enabled (&perf))
500
		continue;
501

            
502
	    for (perf.size = perf_case->min_size;
503
		 perf.size <= perf_case->max_size;
504
		 perf.size *= 2)
505
	    {
506
		void *closure;
507

            
508
		surface = (target->create_surface) (NULL,
509
						    target->content,
510
						    perf.size, perf.size,
511
						    perf.size, perf.size,
512
						    CAIRO_BOILERPLATE_MODE_PERF,
513
						    &closure);
514
		if (surface == NULL) {
515
		    fprintf (stderr,
516
			     "Error: Failed to create target surface: %s\n",
517
			     target->name);
518
		    continue;
519
		}
520

            
521
		cairo_perf_timer_set_synchronize (target->synchronize, closure);
522

            
523
		perf.cr = cairo_create (surface);
524

            
525
		perf_case->run (&perf, perf.cr, perf.size, perf.size);
526

            
527
		if (cairo_status (perf.cr)) {
528
		    fprintf (stderr, "Error: Test left cairo in an error state: %s\n",
529
			     cairo_status_to_string (cairo_status (perf.cr)));
530
		}
531

            
532
		cairo_destroy (perf.cr);
533
		cairo_surface_destroy (surface);
534

            
535
		if (target->cleanup)
536
		    target->cleanup (closure);
537
	    }
538
	}
539
    }
540

            
541
    cairo_perf_fini (&perf);
542

            
543
    return 0;
544
}
545

            
546
#define FUNC(f) f, f##_enabled
547
const cairo_perf_case_t perf_cases[] = {
548
    { FUNC(pixel),  1, 1 },
549
    { FUNC(a1_pixel),  1, 1 },
550
    { FUNC(paint),  64, 512},
551
    { FUNC(paint_with_alpha),  64, 512},
552
    { FUNC(fill),   64, 512},
553
    { FUNC(stroke), 64, 512},
554
    { FUNC(text),   64, 512},
555
    { FUNC(glyphs), 64, 512},
556
    { FUNC(mask),   64, 512},
557
    { FUNC(line),  32, 512},
558
    { FUNC(a1_line),  32, 512},
559
    { FUNC(curve),  32, 512},
560
    { FUNC(a1_curve),  32, 512},
561
    { FUNC(disjoint),   64, 512},
562
    { FUNC(hatching),   64, 512},
563
    { FUNC(tessellate), 100, 100},
564
    { FUNC(subimage_copy), 16, 512},
565
    { FUNC(hash_table), 16, 16},
566
    { FUNC(pattern_create_radial), 16, 16},
567
    { FUNC(zrusin), 415, 415},
568
    { FUNC(world_map), 800, 800},
569
    { FUNC(box_outline), 100, 100},
570
    { FUNC(mosaic), 800, 800 },
571
    { FUNC(long_lines), 100, 100},
572
    { FUNC(unaligned_clip), 100, 100},
573
    { FUNC(rectangles), 512, 512},
574
    { FUNC(rounded_rectangles), 512, 512},
575
    { FUNC(long_dashed_lines), 512, 512},
576
    { FUNC(composite_checker), 16, 512},
577
    { FUNC(twin), 800, 800},
578
    { FUNC(dragon), 1024, 1024 },
579
    { FUNC(sierpinski), 32, 1024 },
580
    { FUNC(pythagoras_tree), 768, 768 },
581
    { FUNC(intersections), 512, 512 },
582
    { FUNC(many_strokes), 32, 512 },
583
    { FUNC(wide_strokes), 32, 512 },
584
    { FUNC(many_fills), 32, 512 },
585
    { FUNC(wide_fills), 32, 512 },
586
    { FUNC(many_curves), 32, 512 },
587
    { FUNC(spiral), 512, 512 },
588
    { FUNC(wave), 500, 500 },
589
    { FUNC(fill_clip), 16, 512 },
590
    { FUNC(tiger), 16, 1024 },
591
    { NULL }
592
};