Created
March 5, 2026 15:17
-
-
Save marxin/f266bd9cea0fa91beaea05ca94466bb1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| final class WPBenchmarkRunner | |
| { | |
| /** @var float max wall-clock runtime (seconds) before early stop */ | |
| private float $maximum_execution_time; | |
| /** @var int return code when max runtime is reached */ | |
| private int $max_time_reached_return_code; | |
| /** @var float benchmark start timestamp */ | |
| private float $start_time = 0.0; | |
| public function __construct(float $maximumExecutionTime = 300.0, int $maxTimeReachedReturnCode = -1) | |
| { | |
| $this->maximum_execution_time = $maximumExecutionTime; | |
| $this->max_time_reached_return_code = $maxTimeReachedReturnCode; | |
| } | |
| public function run(string $function, ?int $parameter = null) | |
| { | |
| if (!method_exists($this, $function)) { | |
| throw new InvalidArgumentException("Unknown benchmark function: {$function}"); | |
| } | |
| $this->start_time = microtime(true); | |
| if ($parameter === null) { | |
| return $this->{$function}(); | |
| } | |
| return $this->{$function}($parameter); | |
| } | |
| private function hasExceededExecutionTime(): bool | |
| { | |
| return (microtime(true) - $this->start_time) > $this->maximum_execution_time; | |
| } | |
| private function random_string(int $len = 10): string | |
| { | |
| $avail_chars = '1234567890ABCDEFGHJIKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; | |
| $char_len = strlen($avail_chars); | |
| $rand_string = ''; | |
| for ($sid_n = 0; $sid_n < $len; $sid_n++) { | |
| $rand_string .= substr($avail_chars, rand(0, $char_len - 1), 1); | |
| } | |
| return $rand_string; | |
| } | |
| public function test_cpu_randbytes(int $s_max = 80000) | |
| { | |
| $a = null; | |
| for ($i = 0; $i < 150; $i++) { | |
| $a .= $this->random_string(10240); | |
| } | |
| $ahex = bin2hex($a); | |
| $ahex_capital = ''; | |
| $ahex_max_index = max(strlen($ahex) - 1, 0); | |
| $j_max = (int) ($s_max / 10000); | |
| $i_max = (int) ($s_max / max($j_max, 1)); | |
| for ($j = 0; $j < $j_max; $j++) { | |
| for ($i = 0; $i < $i_max; $i++) { | |
| $ahex_capital .= strtoupper($ahex[rand(0, $ahex_max_index)]); | |
| md5($ahex_capital); | |
| if (strlen($ahex_capital) > 10240) { | |
| $ahex_capital = ''; | |
| } | |
| } | |
| if ($this->hasExceededExecutionTime()) { | |
| return $this->max_time_reached_return_code; | |
| } | |
| } | |
| return true; | |
| } | |
| public function test_cpu_regex(int $i_max = 10, int $j_max = 30, int $s_length = 40480) | |
| { | |
| $data = []; | |
| $mbEregReplaceAvailable = function_exists('mb_eregi_replace'); | |
| $mbStrlenAvailable = function_exists('mb_strlen'); | |
| for ($i = 0; $i < $i_max; $i++) { | |
| $replace_value = $this->random_string(2); | |
| $s = $replace_value . ', '; | |
| while (($mbStrlenAvailable ? mb_strlen($s) : strlen($s)) < $s_length) { | |
| $s .= $this->random_string(10) . ', '; | |
| } | |
| for ($j = 0; $j < $j_max; $j++) { | |
| $replace_with = $this->random_string(2); | |
| if ($mbEregReplaceAvailable) { | |
| $s = mb_eregi_replace($replace_value, $replace_with, $s); | |
| $s = mb_eregi_replace($replace_with, $replace_value, $s); | |
| $s = mb_eregi_replace($replace_value, $replace_with, $s); | |
| } else { | |
| $pattern1 = '/' . preg_quote($replace_value, '/') . '/i'; | |
| $pattern2 = '/' . preg_quote($replace_with, '/') . '/i'; | |
| $s = preg_replace($pattern1, $replace_with, $s) ?? $s; | |
| $s = preg_replace($pattern2, $replace_value, $s) ?? $s; | |
| $s = preg_replace($pattern1, $replace_with, $s) ?? $s; | |
| } | |
| } | |
| $data[] = $s; | |
| } | |
| if ($i_max === $j_max && $i_max === 1) { | |
| return true; | |
| } | |
| $data_splitted_2 = []; | |
| $data_splitted = []; | |
| foreach ($data as $big_string) { | |
| $data_splitted[] = explode(',', $big_string); | |
| array_merge($data_splitted_2, preg_split('/[\s,]+/', $big_string) ?: []); | |
| } | |
| foreach ($data_splitted as $rk => $r_array) { | |
| sort($data_splitted[$rk]); | |
| } | |
| if ($this->hasExceededExecutionTime()) { | |
| return $this->max_time_reached_return_code; | |
| } | |
| foreach ($data_splitted_2 as $rk => $rv) { | |
| $data_splitted_2[$rk] = md5(md5($rv)); | |
| } | |
| if ($this->hasExceededExecutionTime()) { | |
| return $this->max_time_reached_return_code; | |
| } | |
| sort($data_splitted_2); | |
| if ($this->hasExceededExecutionTime()) { | |
| return $this->max_time_reached_return_code; | |
| } | |
| foreach ($data_splitted as $rk => $r_array) { | |
| foreach ($r_array as $sk => $sv) { | |
| $data_splitted[$rk][$sk] = md5($sv); | |
| } | |
| } | |
| unset($data); | |
| return true; | |
| } | |
| private function wpbenchmark_fibonacci_recursive(int $n): int | |
| { | |
| if ($n <= 1) { | |
| return $n; | |
| } | |
| if ($n % 5 === 0 && $this->hasExceededExecutionTime()) { | |
| return $this->max_time_reached_return_code; | |
| } | |
| $left = $this->wpbenchmark_fibonacci_recursive($n - 1); | |
| if ($left === $this->max_time_reached_return_code) { | |
| return $this->max_time_reached_return_code; | |
| } | |
| $right = $this->wpbenchmark_fibonacci_recursive($n - 2); | |
| if ($right === $this->max_time_reached_return_code) { | |
| return $this->max_time_reached_return_code; | |
| } | |
| return $left + $right; | |
| } | |
| private function wpbenchmark_fibonacci_iterative(int $n): bool | |
| { | |
| $fib = []; | |
| $fib[0] = 0; | |
| $fib[1] = 1; | |
| for ($i = 2; $i <= $n; $i++) { | |
| $fib[$i] = $fib[$i - 1] + $fib[$i - 2]; | |
| } | |
| return true; | |
| } | |
| public function test_fibo_recursive() | |
| { | |
| $result = $this->wpbenchmark_fibonacci_recursive(35); | |
| if ($result === $this->max_time_reached_return_code) { | |
| return $this->max_time_reached_return_code; | |
| } | |
| return true; | |
| } | |
| public function test_fibo_iterative() | |
| { | |
| for ($j = 0; $j < 1; $j++) { | |
| for ($i = 1; $i < 10000; $i++) { | |
| $this->wpbenchmark_fibonacci_iterative(5000); | |
| } | |
| if ($this->hasExceededExecutionTime()) { | |
| return $this->max_time_reached_return_code; | |
| } | |
| } | |
| return true; | |
| } | |
| public function test_cpu_floating_operations(int $iterations = 200000) | |
| { | |
| for ($j = 0; $j < 15; $j++) { | |
| $result = 0.0; | |
| for ($i = 0; $i < $iterations; $i++) { | |
| $result += sin($i) * cos($i) / sqrt($i + 1); | |
| } | |
| } | |
| return true; | |
| } | |
| } | |
| $benchmarkDefinitions = [ | |
| // 'test_cpu_regex' => [ | |
| // 'name' => 'Operations with large text data', | |
| // 'description' => '', | |
| // 'default_parameter' => null, | |
| // 'accepts_parameter' => true, | |
| // ], | |
| 'test_cpu_randbytes' => [ | |
| 'name' => 'Random binary data operations', | |
| 'description' => 'Test CPU and memory with random binary data generation and memory prefill', | |
| 'default_parameter' => 80000, | |
| 'accepts_parameter' => true, | |
| ], | |
| 'test_fibo_recursive' => [ | |
| 'name' => 'Recursive mathematical calculations', | |
| 'description' => '', | |
| 'default_parameter' => null, | |
| 'accepts_parameter' => false, | |
| ], | |
| 'test_fibo_iterative' => [ | |
| 'name' => 'Iterative mathematical calculations', | |
| 'description' => '', | |
| 'default_parameter' => null, | |
| 'accepts_parameter' => false, | |
| ], | |
| 'test_cpu_floating_operations' => [ | |
| 'name' => 'Floating point operations', | |
| 'description' => '', | |
| 'default_parameter' => 800000, | |
| 'accepts_parameter' => true, | |
| ], | |
| ]; | |
| $selected = $argv[1] ?? 'all'; | |
| $customParameter = null; | |
| $maxExecutionTimeArg = null; | |
| if (is_numeric($selected)) { | |
| // Backward compatibility: old style args were [iterations] [maxExecutionTime]. | |
| $selected = 'test_cpu_floating_operations'; | |
| $customParameter = (int) $argv[1]; | |
| $maxExecutionTimeArg = $argv[2] ?? null; | |
| } else { | |
| if ($selected === 'all') { | |
| $maxExecutionTimeArg = $argv[2] ?? null; | |
| } elseif (isset($benchmarkDefinitions[$selected]) && $benchmarkDefinitions[$selected]['accepts_parameter']) { | |
| $customParameter = isset($argv[2]) ? (int) $argv[2] : null; | |
| $maxExecutionTimeArg = $argv[3] ?? null; | |
| } else { | |
| $maxExecutionTimeArg = $argv[2] ?? null; | |
| } | |
| } | |
| $maxExecutionTime = isset($maxExecutionTimeArg) ? (float) $maxExecutionTimeArg : 300.0; | |
| if ($customParameter !== null && $customParameter <= 0) { | |
| fwrite(STDERR, "Parameter must be a positive integer.\n"); | |
| exit(2); | |
| } | |
| if ($maxExecutionTime <= 0) { | |
| fwrite(STDERR, "Max execution time must be a positive number.\n"); | |
| exit(2); | |
| } | |
| $runner = new WPBenchmarkRunner($maxExecutionTime); | |
| $toRun = []; | |
| if ($selected === 'all') { | |
| $toRun = array_keys($benchmarkDefinitions); | |
| } elseif (isset($benchmarkDefinitions[$selected])) { | |
| $toRun[] = $selected; | |
| } else { | |
| fwrite(STDERR, "Unknown benchmark '{$selected}'. Available benchmarks:\n"); | |
| foreach ($benchmarkDefinitions as $function => $meta) { | |
| fwrite(STDERR, " - {$function}: {$meta['name']}\n"); | |
| } | |
| exit(2); | |
| } | |
| $labels = array_merge($toRun, ['total']); | |
| $labelWidth = max(array_map('strlen', $labels)) + 5; | |
| $totalElapsed = 0.0; | |
| foreach ($toRun as $function) { | |
| $meta = $benchmarkDefinitions[$function]; | |
| $parameter = $customParameter ?? $meta['default_parameter']; | |
| $benchmarkStart = microtime(true); | |
| $result = $runner->run($function, $parameter); | |
| $elapsed = microtime(true) - $benchmarkStart; | |
| $totalElapsed += $elapsed; | |
| if ($result !== true) { | |
| // Keep output minimal and predictable even when benchmark exits early. | |
| } | |
| echo str_pad($function, $labelWidth, '.', STR_PAD_RIGHT) . number_format($elapsed, 2, '.', '') . " s\n"; | |
| } | |
| echo str_pad('total', $labelWidth, '.', STR_PAD_RIGHT) . number_format($totalElapsed, 2, '.', '') . " s\n"; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment