diff --git a/README.md b/README.md index 8d2a1f6..2d3a26a 100644 --- a/README.md +++ b/README.md @@ -14,30 +14,116 @@ curl https://raw.githubusercontent.com/SergiX44/php-benchmark-script/master/benc Or upload the file `bench.php` to your web document root and visit it. -# Example Output +#### Parameters + +You can change the difficulty multiplier by passing a `multiplier` parameter: + +```sh +php bench.php --multiplier=2 ``` --------------------------------------------------- -| PHP BENCHMARK SCRIPT v.1.0 by @SergiX44 | --------------------------------------------------- -PHP version................................. 8.2.1 -Platform.................................... Linux -Server:.................................. hostname -OPCache status:........................... enabled -OPCache JIT:.............................. enabled -PCRE JIT:................................. enabled -Started at:............... 17/01/2023 15:22:51.079 --------------------------------------------------- -math..................................... 0.1551 s -loops.................................... 0.0223 s -ifelse................................... 0.0305 s -switch................................... 0.0283 s -strings.................................. 0.4240 s -array.................................... 0.7684 s -regex.................................... 0.2929 s -is_{type}................................ 0.0611 s --------------------------------------------------- -Total time............................... 1.7828 s -Peak memory usage........................... 2 MiB + +### Additional tests + +You must have the `bench.php` file to your server. +You can download additional benchmarks (must be in the same directory as `bench.php`) using: + +```sh +# rand: random number generation +wget https://raw.githubusercontent.com/SergiX44/php-benchmark-script/master/rand.bench.php +``` + +Then you can run the benchmark using: + +```sh +php bench.php +``` + +If the file is in the same directory as `bench.php`, it will be automatically loaded. + +## Custom tests + +You can create your own tests by creating a file in the same directory as `bench.php` and the file must be +named `*.bench.php`. + +The file must return a closure, or an array of closures. Each closure should take a parameter `$multiplier` which is +how hard the test should be. The higher the `$multiplier`, the longer the test will take, by default it is `1`. +You should choose a reasonable number of iterations for your test (e.g. 1000) and multiply it by the `$multiplier`. + +Example: + +```php +// mytest.bench.php + +return function ($multiplier = 1) { + $iterations = 1000 * $multiplier; + for ($i = 0; $i < $iterations; ++$i) { + // do something + } +}; +``` + +Or with multiple tests: + +```php +// mytest.bench.php + +return [ + 'my_test' => function ($multiplier = 1) { + $iterations = 1000 * $multiplier; + for ($i = 0; $i < $iterations; ++$i) { + // do something + } + }, + 'another_test' => function ($multiplier = 1) { + $iterations = 1000 * $multiplier; + for ($i = 0; $i < $iterations; ++$i) { + // do something else + } + }, +]; +``` + +# Example Output + +```sh +------------------------------------------------------- +| PHP BENCHMARK SCRIPT v.2.0 by @SergiX44 | +------------------------------------------------------- +PHP............................................. 8.2.10 +Platform........................................ Darwin +Arch............................................. arm64 +Server........................................ hostname +Max memory usage.................................. 512M +OPCache status................................. enabled +OPCache JIT.................................... enabled +PCRE JIT....................................... enabled +XDebug extension.............................. disabled +Difficulty multiplier............................... 1x +Started at..................... 06/12/2023 13:45:37.453 +------------------------------------------------------- +math.......................................... 0.0935 s +loops......................................... 0.0121 s +ifelse........................................ 0.0173 s +switch........................................ 0.0172 s +string........................................ 0.1842 s +array......................................... 0.3212 s +regex......................................... 0.1769 s +is_{type}..................................... 0.0322 s +hash.......................................... 0.1202 s +json.......................................... 0.1586 s +-----------------Additional Benchmarks----------------- +io::file_read................................. 0.0129 s +io::file_write................................ 0.0715 s +io::file_zip.................................. 0.5335 s +io::file_unzip................................ 0.1571 s +rand::rand.................................... 0.0089 s +rand::mt_rand................................. 0.0089 s +rand::random_int.............................. 0.0679 s +rand::random_bytes............................ 0.2320 s +rand::openssl_random_pseudo_bytes............. 0.2953 s +------------------------------------------------------- +Total time.................................... 2.5214 s +Peak memory usage................................ 2 MiB ``` ## Authors diff --git a/bench.php b/bench.php index 6145164..b0f819f 100644 --- a/bench.php +++ b/bench.php @@ -1,17 +1,25 @@ 1.0, +]; -// Increase the multiplier if you want to benchmark longer -$multiplier = 1.0; +$args = get_args($defaultArgs); +$args = array_merge($defaultArgs, $args); /** @var array $benchmarks */ // the benchmarks! $benchmarks = [ - 'math' => function ($count = 200000) { - global $multiplier; + 'math' => function ($multiplier = 1, $count = 200000) { $x = 0; - for ($i = 0; $i < $count * $multiplier; $i++) { + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { $x += $i + $i; $x += $i * $i; $x += $i ** $i; @@ -53,24 +61,24 @@ tanh($i); } - return $x; + return $i; }, - 'loops' => function ($count = 20000000) { - global $multiplier; - for ($i = 0; $i < $count * $multiplier; ++$i) { - ; + 'loops' => function ($multiplier = 1, $count = 20000000) { + $count = $count * $multiplier; + for ($i = 0; $i < $count; ++$i) { + $i; } $i = 0; - while ($i < $count * $multiplier) { + while ($i < $count) { ++$i; } return $i; }, - 'ifelse' => function ($count = 10000000) { - global $multiplier; + 'ifelse' => function ($multiplier = 1, $count = 10000000) { $a = 0; $b = 0; - for ($i = 0; $i < $count * $multiplier; $i++) { + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { $k = $i % 4; if ($k === 0) { $i; @@ -79,16 +87,16 @@ } elseif ($k === 2) { $b = $i; } else { - + $i; } } return $a - $b; }, - 'switch' => function ($count = 10000000) { - global $multiplier; + 'switch' => function ($multiplier = 1, $count = 10000000) { $a = 0; $b = 0; - for ($i = 0; $i < $count * $multiplier; $i++) { + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { switch ($i % 4) { case 0: $i; @@ -105,10 +113,10 @@ } return $a - $b; }, - 'strings' => function ($count = 50000) { - global $multiplier; + 'string' => function ($multiplier = 1, $count = 50000) { $string = 'the quick brown fox jumps over the lazy dog '; - for ($i = 0; $i < $count * $multiplier; $i++) { + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { addslashes($string); bin2hex($string); chunk_split($string); @@ -142,10 +150,10 @@ } return $string; }, - 'array' => function ($count = 50000) { - global $multiplier; + 'array' => function ($multiplier = 1, $count = 50000) { $a = range(0, 100); - for ($i = 0; $i < $count * $multiplier; $i++) { + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { array_keys($a); array_values($a); array_flip($a); @@ -161,18 +169,19 @@ } return $a; }, - 'regex' => function ($count = 1000000) { - global $multiplier; + 'regex' => function ($multiplier = 1, $count = 1000000) { for ($i = 0; $i < $count * $multiplier; $i++) { - preg_match("#http[s]?://\w+[^\s\[\]\<]+#", 'this is a link to https://google.com which is a really popular site'); - preg_replace("#(^|\s)(http[s]?://\w+[^\s\[\]\<]+)#i", '\1\2', 'this is a link to https://google.com which is a really popular site'); + preg_match("#http[s]?://\w+[^\s\[\]\<]+#", + 'this is a link to https://google.com which is a really popular site'); + preg_replace("#(^|\s)(http[s]?://\w+[^\s\[\]\<]+)#i", '\1\2', + 'this is a link to https://google.com which is a really popular site'); } return $i; }, - 'is_{type}' => function ($count = 2500000) { - global $multiplier; + 'is_{type}' => function ($multiplier = 1, $count = 2500000) { $o = new stdClass(); - for ($i = 0; $i < $count * $multiplier; $i++) { + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { is_array([1]); is_array('1'); is_int(1); @@ -190,19 +199,83 @@ } return $o; }, + 'hash' => function ($multiplier = 1, $count = 10000) { + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { + md5($i); + sha1($i); + hash('sha256', $i); + hash('sha512', $i); + hash('ripemd160', $i); + hash('crc32', $i); + hash('crc32b', $i); + hash('adler32', $i); + hash('fnv132', $i); + hash('fnv164', $i); + hash('joaat', $i); + hash('haval128,3', $i); + hash('haval160,3', $i); + hash('haval192,3', $i); + hash('haval224,3', $i); + hash('haval256,3', $i); + hash('haval128,4', $i); + hash('haval160,4', $i); + hash('haval192,4', $i); + hash('haval224,4', $i); + hash('haval256,4', $i); + hash('haval128,5', $i); + hash('haval160,5', $i); + hash('haval192,5', $i); + hash('haval224,5', $i); + hash('haval256,5', $i); + } + return $i; + }, + 'json' => function ($multiplier = 1, $count = 100000) { + $data = [ + 'foo' => 'bar', + 'bar' => 'baz', + 'baz' => 'qux', + 'qux' => 'quux', + 'quux' => 'corge', + 'corge' => 'grault', + 'grault' => 'garply', + 'garply' => 'waldo', + 'waldo' => 'fred', + 'fred' => 'plugh', + 'plugh' => 'xyzzy', + 'xyzzy' => 'thud', + 'thud' => 'end', + ]; + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { + json_encode($data); + json_decode(json_encode($data)); + } + return $data; + }, ]; +$mtime = microtime(true); +// workaround for https://www.php.net/manual/en/datetime.createfromformat.php#128901 +if (fmod($mtime, 1) === .0000) { + $mtime += .0001; +} +$now = DateTime::createFromFormat('U.u', $mtime); -$isCli = php_sapi_name() === 'cli'; +$V = '2.0'; +$isCli = PHP_SAPI === 'cli'; $lf = $isCli ? PHP_EOL : '
'; -$w = 50; +$w = 55; +$multiplier = $args['multiplier']; +$additionalBenchmarks = loadAdditionalBenchmarks(); -$p = function ($str, $endStr = '', $pad = '.') use ($w, $lf) { +$p = function ($str, $endStr = '', $pad = '.', $mode = STR_PAD_RIGHT) use ($w, $lf) { if (!empty($endStr)) { $endStr = " $endStr"; } $length = max(0, $w - strlen($endStr)); - echo str_pad($str, $length, $pad).$endStr.$lf; + echo str_pad($str, $length, $pad, $mode) . $endStr . $lf; }; echo $isCli ? '' : '
';
@@ -211,6 +284,7 @@
 $p('', '', '-');
 $p('PHP', PHP_VERSION);
 $p('Platform', PHP_OS);
+$p('Arch', php_uname('m'));
 if ($isCli) {
     $p('Server', gethostname());
 } else {
@@ -218,26 +292,34 @@
     $addr = @$_SERVER['SERVER_ADDR'] ?: 'null';
     $p('Server', "{$name}@{$addr}");
 }
+$p('Max memory usage', ini_get('memory_limit'));
 $opStatus = function_exists('opcache_get_status') ? opcache_get_status() : false;
 $p('OPCache status', is_array($opStatus) && @$opStatus['opcache_enabled'] ? 'enabled' : 'disabled');
 $p('OPCache JIT', is_array($opStatus) && @$opStatus['jit']['enabled'] ? 'enabled' : 'disabled/unavailable');
 $p('PCRE JIT', ini_get('pcre.jit') ? 'enabled' : 'disabled');
 $p('XDebug extension', extension_loaded('xdebug') ? 'enabled' : 'disabled');
-$p('Benchmark Multiplier', $multiplier);
-$p('Started at', DateTime::createFromFormat('U.u', microtime(true))->format('d/m/Y H:i:s.v'));
-$p('', '', '-');
+$p('Difficulty multiplier', "{$multiplier}x");
+$p('Started at', $now->format('d/m/Y H:i:s.v'));
+$p('', '', '-', STR_PAD_BOTH);
 
 $stopwatch = new StopWatch();
 
 foreach ($benchmarks as $name => $benchmark) {
-    $stopwatch->start();
-    $benchmark();
-    $time = $stopwatch->stop();
-    $p($name, number_format($time, 4).' s');
+    $time = runBenchmark($stopwatch, $name, $benchmark, $multiplier);
+    $p($name, $time);
 }
+
+if (!empty($additionalBenchmarks)) {
+    $p('Additional Benchmarks', '', '-', STR_PAD_BOTH);
+    foreach ($additionalBenchmarks as $name => $benchmark) {
+        $time = runBenchmark($stopwatch, $name, $benchmark, $multiplier);
+        $p($name, $time);
+    }
+}
+
 $p('', '', '-');
-$p('Total time', number_format($stopwatch->totalTime, 4).' s');
-$p('Peak memory usage', round(memory_get_peak_usage(true) / 1024 / 1024, 2).' MiB');
+$p('Total time', number_format($stopwatch->totalTime, 4) . ' s');
+$p('Peak memory usage', round(memory_get_peak_usage(true) / 1024 / 1024, 2) . ' MiB');
 
 echo $isCli ? '' : '
'; @@ -278,4 +360,77 @@ private function t() { return function_exists('hrtime') ? hrtime(true) / 1e9 : microtime(true); } +} + +function get_args($expectedArgs) +{ + $args = []; + + if (PHP_SAPI === 'cli') { + $cleanedArgs = array_map(function ($arg) { + return strpos($arg, '--') !== 0 ? null : str_replace('--', '', $arg); + }, $GLOBALS['argv']); + + parse_str(implode('&', array_filter($cleanedArgs)), $args); + } else { + parse_str($_SERVER['QUERY_STRING'], $args); + } + + $args = array_intersect_key($args, array_flip(array_keys($expectedArgs))); + + // cast the type to the original type if needed + foreach ($expectedArgs as $key => $value) { + if (isset($args[$key])) { + settype($args[$key], gettype($value)); + } + } + + return $args; +} + +function loadAdditionalBenchmarks() +{ + $benchmarks = []; + $benchFiles = glob(__DIR__ . '/./*.bench.php'); + foreach ($benchFiles as $benchFile) { + $benchName = basename($benchFile, '.bench.php'); + $newBenchmark = require $benchFile; + if (is_callable($newBenchmark)) { + $benchmarks[$benchName] = $newBenchmark; + continue; + } + + if (is_array($newBenchmark)) { + $newBenchmark = array_filter($newBenchmark, 'is_callable'); + $newBenchmark = array_combine(array_map(function ($name) use ($benchName) { + return "{$benchName}::{$name}"; + }, array_keys($newBenchmark)), $newBenchmark); + + $benchmarks = array_merge($benchmarks, $newBenchmark); + continue; + } + + throw new RuntimeException("Invalid benchmark file: {$benchFile}"); + } + + return $benchmarks; +} + +function runBenchmark($stopwatch, $name, $benchmark, $multiplier = 1) +{ + $r = null; + try { + $stopwatch->start(); + $r = $benchmark($multiplier); + } catch (Exception $e) { + return 'ERROR: ' . $e->getMessage(); + } finally { + $time = $stopwatch->stop(); + } + + if ($r === INF) { + return 'SKIPPED'; + } + + return number_format($time, 4) . ' s'; } \ No newline at end of file diff --git a/io.bench.php b/io.bench.php new file mode 100644 index 0000000..a7694bb --- /dev/null +++ b/io.bench.php @@ -0,0 +1,54 @@ + function($multiplier = 1, $count = 1000) { + file_put_contents('test.txt', "test"); + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { + file_get_contents('test.txt'); + } + unlink('test.txt'); + return $i; + }, + 'file_write' => function($multiplier = 1, $count = 1000) { + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { + file_put_contents('test.txt', "test $i"); + } + unlink('test.txt'); + return $i; + }, + 'file_zip' => function($multiplier = 1, $count = 1000) { + file_put_contents('test.txt', "test"); + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { + $zip = new ZipArchive(); + $zip->open('test.zip', ZipArchive::CREATE); + $zip->addFile('test.txt'); + $zip->close(); + } + unlink('test.txt'); + unlink('test.zip'); + return $i; + }, + 'file_unzip' => function($multiplier = 1, $count = 1000) { + file_put_contents('test.txt', "test"); + $zip = new ZipArchive(); + $zip->open('test.zip', ZipArchive::CREATE); + $zip->addFile('test.txt'); + $zip->close(); + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { + $zip = new ZipArchive(); + $zip->open('test.zip'); + $zip->extractTo('test'); + $zip->close(); + } + unlink('test.txt'); + unlink('test.zip'); + unlink('test/test.txt'); + rmdir('test'); + return $i; + }, + +]; \ No newline at end of file diff --git a/rand.bench.php b/rand.bench.php new file mode 100644 index 0000000..70aded0 --- /dev/null +++ b/rand.bench.php @@ -0,0 +1,52 @@ + */ +return [ + 'rand' => function($multiplier = 1, $count = 1000000) { + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { + rand(0, $i); + } + return $i; + }, + 'mt_rand' => function($multiplier = 1, $count = 1000000) { + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { + mt_rand(0, $i); + } + return $i; + }, + 'random_int' => function($multiplier = 1, $count = 1000000) { + if (!function_exists('random_int')) { + return INF; + } + + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { + random_int(0, $i); + } + return $i; + }, + 'random_bytes' => function($multiplier = 1, $count = 1000000) { + if (!function_exists('random_bytes')) { + return INF; + } + + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { + random_bytes(32); + } + return $i; + }, + 'openssl_random_pseudo_bytes' => function($multiplier = 1, $count = 1000000) { + if (!function_exists('openssl_random_pseudo_bytes')) { + return INF; + } + + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { + openssl_random_pseudo_bytes(32); + } + return $i; + }, +];