xref: /php-src/benchmark/benchmark.php (revision c16ad918)
1<?php
2
3require_once __DIR__ . '/shared.php';
4
5$storeResult = ($argv[1] ?? 'false') === 'true';
6$phpCgi = $argv[2] ?? dirname(PHP_BINARY) . '/php-cgi';
7if (!file_exists($phpCgi)) {
8    fwrite(STDERR, "php-cgi not found\n");
9    exit(1);
10}
11
12function main() {
13    global $storeResult;
14
15    $profilesDir = __DIR__ . '/profiles';
16    if (!is_dir($profilesDir)) {
17        mkdir($profilesDir, 0755, true);
18    }
19
20    $data = [];
21    if (false !== $branch = getenv('GITHUB_REF_NAME')) {
22        $data['branch'] = $branch;
23    }
24    $data['Zend/bench.php'] = runBench(false);
25    $data['Zend/bench.php JIT'] = runBench(true);
26    $data['Symfony Demo 2.2.3'] = runSymfonyDemo(false);
27    $data['Symfony Demo 2.2.3 JIT'] = runSymfonyDemo(true);
28    $data['Wordpress 6.2'] = runWordpress(false);
29    $data['Wordpress 6.2 JIT'] = runWordpress(true);
30    $result = json_encode($data, JSON_PRETTY_PRINT) . "\n";
31
32    fwrite(STDOUT, $result);
33
34    if ($storeResult) {
35        storeResult($result);
36    }
37}
38
39function storeResult(string $result) {
40    $repo = __DIR__ . '/repos/data';
41    cloneRepo($repo, 'git@github.com:php/benchmarking-data.git');
42
43    $commitHash = getPhpSrcCommitHash();
44    $dir = $repo . '/' . substr($commitHash, 0, 2) . '/' . $commitHash;
45    $summaryFile = $dir . '/summary.json';
46    if (!is_dir($dir)) {
47        mkdir($dir, 0755, true);
48    }
49    file_put_contents($summaryFile, $result);
50}
51
52function getPhpSrcCommitHash(): string {
53    $result = runCommand(['git', 'log', '--pretty=format:%H', '-n', '1'], dirname(__DIR__));
54    return $result->stdout;
55}
56
57function runBench(bool $jit): array {
58    return runValgrindPhpCgiCommand('bench', [dirname(__DIR__) . '/Zend/bench.php'], jit: $jit);
59}
60
61function runSymfonyDemo(bool $jit): array {
62    $dir = __DIR__ . '/repos/symfony-demo-2.2.3';
63    cloneRepo($dir, 'https://github.com/php/benchmarking-symfony-demo-2.2.3.git');
64    runPhpCommand([$dir . '/bin/console', 'cache:clear']);
65    runPhpCommand([$dir . '/bin/console', 'cache:warmup']);
66    return runValgrindPhpCgiCommand('symfony-demo', [$dir . '/public/index.php'], cwd: $dir, jit: $jit, warmup: 50, repeat: 50);
67}
68
69function runWordpress(bool $jit): array {
70    $dir = __DIR__ . '/repos/wordpress-6.2';
71    cloneRepo($dir, 'https://github.com/php/benchmarking-wordpress-6.2.git');
72
73    /* FIXME: It might be better to use a stable version of PHP for this command because we can't
74     * easily alter the phar file */
75    runPhpCommand([
76        '-d error_reporting=0',
77        'wp-cli.phar',
78        'core',
79        'install',
80        '--url=wordpress.local',
81        '--title="Wordpress"',
82        '--admin_user=wordpress',
83        '--admin_password=wordpress',
84        '--admin_email=benchmark@php.net',
85    ], $dir);
86
87    // Warmup
88    runPhpCommand([$dir . '/index.php'], $dir);
89    return runValgrindPhpCgiCommand('wordpress', [$dir . '/index.php'], cwd: $dir, jit: $jit, warmup: 50, repeat: 50);
90}
91
92function runPhpCommand(array $args, ?string $cwd = null): ProcessResult {
93    return runCommand([PHP_BINARY, ...$args], $cwd);
94}
95
96function runValgrindPhpCgiCommand(
97    string $name,
98    array $args,
99    ?string $cwd = null,
100    bool $jit = false,
101    int $warmup = 0,
102    int $repeat = 1,
103): array {
104    global $phpCgi;
105
106    $profileOut = __DIR__ . "/profiles/callgrind.out.$name";
107    if ($jit) {
108        $profileOut .= '.jit';
109    }
110
111    $process = runCommand([
112        'valgrind',
113        '--tool=callgrind',
114        '--dump-instr=yes',
115        "--callgrind-out-file=$profileOut",
116        '--',
117        $phpCgi,
118        '-T' . ($warmup ? $warmup . ',' : '') . $repeat,
119        '-d max_execution_time=0',
120        '-d opcache.enable=1',
121        '-d opcache.jit=' . ($jit ? 'tracing' : 'disable'),
122        '-d opcache.jit_buffer_size=128M',
123        '-d opcache.validate_timestamps=0',
124        ...$args,
125    ]);
126    $instructions = extractInstructionsFromValgrindOutput($process->stderr);
127    if ($repeat > 1) {
128        $instructions = gmp_strval(gmp_div_q($instructions, $repeat));
129    }
130    return ['instructions' => $instructions];
131}
132
133function extractInstructionsFromValgrindOutput(string $output): string {
134    preg_match("(==[0-9]+== Events    : Ir\n==[0-9]+== Collected : (?<instructions>[0-9]+))", $output, $matches);
135    return $matches['instructions'] ?? throw new \Exception('Unexpected valgrind output');
136}
137
138main();
139