diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 69b6e24..6d847b2 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -25,17 +25,23 @@ jobs: extensions: opcache coverage: none + - uses: shogo82148/actions-setup-mysql@v1 + with: + distribution: "mariadb" + mysql-version: "11.4" + root-password: "root" + - name: Run (1) - run: php -d opcache.enable=1 -d opcache.enable_cli=1 -d pcre.jit=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=32M bench.php + run: php -d opcache.enable=1 -d opcache.enable_cli=1 -d pcre.jit=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=32M bench.php --mysql_user=root --mysql_password=root - name: Run (2) - run: php -d opcache.enable=1 -d opcache.enable_cli=1 -d pcre.jit=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=32M bench.php + run: php -d opcache.enable=1 -d opcache.enable_cli=1 -d pcre.jit=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=32M bench.php --mysql_user=root --mysql_password=root - name: Run (3) - run: php -d opcache.enable=1 -d opcache.enable_cli=1 -d pcre.jit=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=32M bench.php + run: php -d opcache.enable=1 -d opcache.enable_cli=1 -d pcre.jit=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=32M bench.php --mysql_user=root --mysql_password=root - name: Run (4) - run: php -d opcache.enable=1 -d opcache.enable_cli=1 -d pcre.jit=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=32M bench.php + run: php -d opcache.enable=1 -d opcache.enable_cli=1 -d pcre.jit=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=32M bench.php --mysql_user=root --mysql_password=root - name: Run (5) - run: php -d opcache.enable=1 -d opcache.enable_cli=1 -d pcre.jit=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=32M bench.php + run: php -d opcache.enable=1 -d opcache.enable_cli=1 -d pcre.jit=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=32M bench.php --mysql_user=root --mysql_password=root diff --git a/bench.php b/bench.php index b0f819f..3661ed6 100644 --- a/bench.php +++ b/bench.php @@ -8,10 +8,15 @@ $defaultArgs = [ // Increase the multiplier if you want to benchmark longer 'multiplier' => 1.0, + 'mysql_host' => '127.0.0.1', + 'mysql_user' => null, + 'mysql_password' => null, + 'mysql_port' => 3306, ]; -$args = get_args($defaultArgs); -$args = array_merge($defaultArgs, $args); +$args = array_merge($defaultArgs, get_args($defaultArgs)); +$setupHooks = []; +$cleanupHooks = []; /** @var array $benchmarks */ // the benchmarks! @@ -269,58 +274,68 @@ $w = 55; $multiplier = $args['multiplier']; $additionalBenchmarks = loadAdditionalBenchmarks(); - -$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, $mode) . $endStr . $lf; -}; +$extraLines = []; +$currentBenchmark = null; echo $isCli ? '' : '
';
-$p('', '', '-');
+printLine('', '', '-');
 printf('|%s|%s', str_pad(sprintf("PHP BENCHMARK SCRIPT v.%s by @SergiX44", $V), $w - 2, ' ', STR_PAD_BOTH), $lf);
-$p('', '', '-');
-$p('PHP', PHP_VERSION);
-$p('Platform', PHP_OS);
-$p('Arch', php_uname('m'));
+printLine('', '', '-');
+printLine('PHP', PHP_VERSION);
+printLine('Platform', PHP_OS);
+printLine('Arch', php_uname('m'));
 if ($isCli) {
-    $p('Server', gethostname());
+    printLine('Server', gethostname());
 } else {
     $name = @$_SERVER['SERVER_NAME'] ?: 'null';
     $addr = @$_SERVER['SERVER_ADDR'] ?: 'null';
-    $p('Server', "{$name}@{$addr}");
+    printLine('Server', "{$name}@{$addr}");
 }
-$p('Max memory usage', ini_get('memory_limit'));
+printLine('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('Difficulty multiplier', "{$multiplier}x");
-$p('Started at', $now->format('d/m/Y H:i:s.v'));
-$p('', '', '-', STR_PAD_BOTH);
+printLine('OPCache status', is_array($opStatus) && @$opStatus['opcache_enabled'] ? 'enabled' : 'disabled');
+printLine('OPCache JIT', is_array($opStatus) && @$opStatus['jit']['enabled'] ? 'enabled' : 'disabled/unavailable');
+printLine('PCRE JIT', ini_get('pcre.jit') ? 'enabled' : 'disabled');
+printLine('XDebug extension', extension_loaded('xdebug') ? 'enabled' : 'disabled');
+printLine('Difficulty multiplier', "{$multiplier}x");
+printLine('Started at', $now->format('d/m/Y H:i:s.v'));
+printLine('', '', '-', STR_PAD_BOTH);
+
+foreach ($setupHooks as $hook) {
+    $hook($args);
+}
 
 $stopwatch = new StopWatch();
 
 foreach ($benchmarks as $name => $benchmark) {
-    $time = runBenchmark($stopwatch, $name, $benchmark, $multiplier);
-    $p($name, $time);
+    $currentBenchmark = $name;
+    $time = runBenchmark($stopwatch, $benchmark, $multiplier);
+    printLine($name, $time);
 }
 
 if (!empty($additionalBenchmarks)) {
-    $p('Additional Benchmarks', '', '-', STR_PAD_BOTH);
+    printLine('Additional Benchmarks', '', '-', STR_PAD_BOTH);
     foreach ($additionalBenchmarks as $name => $benchmark) {
-        $time = runBenchmark($stopwatch, $name, $benchmark, $multiplier);
-        $p($name, $time);
+        $currentBenchmark = $name;
+        $time = runBenchmark($stopwatch, $benchmark, $multiplier);
+        printLine($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');
+foreach ($cleanupHooks as $hook) {
+    $hook($args);
+}
+
+if  (!empty($extraLines)) {
+    printLine('Extra', '', '-', STR_PAD_BOTH);
+    foreach ($extraLines as $line) {
+        printLine($line[0], $line[1]);
+    }
+}
 
+printLine('', '', '-');
+printLine('Total time', number_format($stopwatch->totalTime, 4) . ' s');
+printLine('Peak memory usage', round(memory_get_peak_usage(true) / 1024 / 1024, 2) . ' MiB');
 echo $isCli ? '' : '
'; @@ -339,7 +354,7 @@ class StopWatch */ public function start() { - return $this->start = $this->t(); + return $this->start = self::time(); } /** @@ -347,7 +362,7 @@ public function start() */ public function stop() { - $time = $this->t() - $this->start; + $time = self::time() - $this->start; $this->totalTime += $time; return $time; @@ -356,7 +371,7 @@ public function stop() /** * @return float */ - private function t() + public static function time() { return function_exists('hrtime') ? hrtime(true) / 1e9 : microtime(true); } @@ -380,7 +395,7 @@ function get_args($expectedArgs) // cast the type to the original type if needed foreach ($expectedArgs as $key => $value) { - if (isset($args[$key])) { + if (isset($args[$key]) && $value !== null) { settype($args[$key], gettype($value)); } } @@ -416,7 +431,13 @@ function loadAdditionalBenchmarks() return $benchmarks; } -function runBenchmark($stopwatch, $name, $benchmark, $multiplier = 1) +function extraStat($name, $value) +{ + global $extraLines, $currentBenchmark; + $extraLines[] = ["$currentBenchmark::$name", $value]; +} + +function runBenchmark($stopwatch, $benchmark, $multiplier = 1) { $r = null; try { @@ -433,4 +454,26 @@ function runBenchmark($stopwatch, $name, $benchmark, $multiplier = 1) } return number_format($time, 4) . ' s'; +} + +function printLine($str, $endStr = '', $pad = '.', $mode = STR_PAD_RIGHT) { + global $lf, $w; + + if (!empty($endStr)) { + $endStr = " $endStr"; + } + $length = max(0, $w - strlen($endStr)); + echo str_pad($str, $length, $pad, $mode) . $endStr . $lf; +} + +function setup(callable $hook) +{ + global $setupHooks; + $setupHooks[] = $hook; +} + +function teardown(callable $hook) +{ + global $cleanupHooks; + $cleanupHooks[] = $hook; } \ No newline at end of file diff --git a/mysql.bench.php b/mysql.bench.php new file mode 100644 index 0000000..9cf47b4 --- /dev/null +++ b/mysql.bench.php @@ -0,0 +1,229 @@ +connect_error) { + printf("Mysql Connect Error (%s) %s\n", $mysqli->connect_errno, $mysqli->connect_error); + return; + } + + // drop database + $mysqli->query("DROP DATABASE IF EXISTS `bench_test`"); + // create database + $mysqli->query("CREATE DATABASE IF NOT EXISTS `bench_test`"); + $mysqli->select_db('bench_test'); + $mysqli->query("CREATE TABLE IF NOT EXISTS `bench_test`.`test` (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255))"); + + for ($i = 0; $i < $initialRowCount; $i++) { + $values[] = "('test$i')"; + } + $mysqli->query("INSERT INTO `bench_test`.`test` (name) VALUES " . implode(',', $values)); +}); + +teardown(function () use (&$mysqli) { + if ($mysqli === null) { + return; + } + + $mysqli->query("DROP DATABASE IF EXISTS `bench_test`"); + $mysqli->close(); +}); + + +return [ + 'ping' => function () use (&$mysqli) { + if ($mysqli === null) { + return INF; + } + $mysqli->ping(); + return 1; + }, + 'select_version' => function ($multiplier = 1, $count = 1000) use (&$mysqli) { + if ($mysqli === null) { + return INF; + } + + $count = $count * $multiplier; + $time = StopWatch::time(); + for ($i = 0; $i < $count; $i++) { + $mysqli->query("SELECT VERSION()"); + } + + extraStat('q/s', round($count / (StopWatch::time() - $time))); + + return $i; + }, + 'select_all' => function ($multiplier = 1, $count = 1000) use (&$mysqli) { + if ($mysqli === null) { + return INF; + } + + $count = $count * $multiplier; + $time = StopWatch::time(); + for ($i = 0; $i < $count; $i++) { + $mysqli->query("SELECT * FROM `bench_test`.`test`"); + } + extraStat('q/s', round($count / (StopWatch::time() - $time))); + return $i; + }, + 'select_cursor' => function ($multiplier = 1, $count = 1000) use (&$mysqli) { + if ($mysqli === null) { + return INF; + } + + $count = $count * $multiplier; + for ($i = 0; $i < $count; $i++) { + $result = $mysqli->query("SELECT * FROM `bench_test`.`test`"); + while ($row = $result->fetch_assoc()) { + } + $result->close(); + } + return $i; + }, + 'seq_insert' => function ($multiplier = 1, $count = 1000) use (&$mysqli) { + if ($mysqli === null) { + return INF; + } + + $count = $count * $multiplier; + $time = StopWatch::time(); + for ($i = 0; $i < $count; $i++) { + $mysqli->query("INSERT INTO `bench_test`.`test` (name) VALUES ('test')"); + } + extraStat('q/s', round($count / (StopWatch::time() - $time))); + return $i; + }, + 'bulk_insert' => function ($multiplier = 1, $count = 100000) use (&$mysqli) { + if ($mysqli === null) { + return INF; + } + + $count = $count * $multiplier; + $values = []; + for ($i = 0; $i < $count; $i++) { + $values[] = "('test$i')"; + } + $mysqli->query("INSERT INTO `bench_test`.`test` (name) VALUES " . implode(',', $values)); + return $i; + }, + 'update' => function ($multiplier = 1, $count = 1000) use (&$mysqli) { + if ($mysqli === null) { + return INF; + } + + $count = $count * $multiplier; + $time = StopWatch::time(); + for ($i = 0; $i < $count; $i++) { + $mysqli->query("UPDATE `bench_test`.`test` SET name = 'test' WHERE id = '$i'"); + } + extraStat('q/s', round($count / (StopWatch::time() - $time))); + return $i; + }, + 'update_with_index' => function ($multiplier = 1, $count = 1000) use (&$mysqli) { + if ($mysqli === null) { + return INF; + } + + $mysqli->query("CREATE INDEX idx ON `bench_test`.`test` (id)"); + + $count = $count * $multiplier; + $time = StopWatch::time(); + for ($i = 0; $i < $count; $i++) { + $mysqli->query("UPDATE `bench_test`.`test` SET name = 'test' WHERE id = '$i'"); + } + extraStat('q/s', round($count / (StopWatch::time() - $time))); + + $mysqli->query("DROP INDEX idx ON `bench_test`.`test`"); + return $i; + }, + 'transaction_insert' => function ($multiplier = 1, $count = 1000) use (&$mysqli) { + if ($mysqli === null) { + return INF; + } + + $count = $count * $multiplier; + $time = StopWatch::time(); + for ($i = 0; $i < $count; $i++) { + $mysqli->begin_transaction(); + $mysqli->query("INSERT INTO `bench_test`.`test` (name) VALUES ('test')"); + $mysqli->commit(); + } + extraStat('t/s', round($count / (StopWatch::time() - $time))); + return $i; + }, + 'aes_encrypt' => function ($multiplier = 1, $count = 1000) use (&$mysqli) { + if ($mysqli === null) { + return INF; + } + + $stmt = $mysqli->prepare("SELECT AES_ENCRYPT(?, 'key')"); + $stmt->bind_param('s', $data); + + $data = str_repeat('a', 16); + $count = $count * $multiplier; + $time = StopWatch::time(); + for ($i = 0; $i < $count; $i++) { + $stmt->execute(); + $stmt->get_result()->fetch_assoc(); + } + extraStat('q/s', round($count / (StopWatch::time() - $time))); + $stmt->close(); + return $i; + }, + 'aes_decrypt' => function ($multiplier = 1, $count = 1000) use (&$mysqli) { + if ($mysqli === null) { + return INF; + } + + $stmt = $mysqli->prepare("SELECT AES_DECRYPT(?, 'key')"); + $stmt->bind_param('s', $data); + + $data = str_repeat('a', 16); + $count = $count * $multiplier; + $time = StopWatch::time(); + for ($i = 0; $i < $count; $i++) { + $stmt->execute(); + $stmt->get_result()->fetch_assoc(); + } + extraStat('q/s', round($count / (StopWatch::time() - $time))); + $stmt->close(); + return $i; + }, + 'indexes' => function ($multiplier = 1, $count = 1000) use (&$mysqli) { + if ($mysqli === null) { + return INF; + } + + $mysqli->query("CREATE INDEX idx_name ON `bench_test`.`test` (name)"); + $mysqli->query("DROP INDEX idx_name ON `bench_test`.`test`"); + return 1; + }, + 'delete' => function ($multiplier = 1, $count = 1000) use (&$mysqli) { + if ($mysqli === null) { + return INF; + } + + $count = $count * $multiplier; + $time = StopWatch::time(); + for ($i = 0; $i < $count; $i++) { + $mysqli->query("DELETE FROM `bench_test`.`test` WHERE id = '$i'"); + } + extraStat('q/s', round($count / (StopWatch::time() - $time))); + return $i; + }, +]; \ No newline at end of file