-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: command()
in Common function
#8667
base: develop
Are you sure you want to change the base?
Conversation
command()
in Common function
Latest in 427f2a6 Benchmark Test Time Memory
command_new() 11.8162 2 MB
command() 12.5120 0 Bytes <?php
namespace App\Controllers;
use CodeIgniter\Debug\Iterator;
class Home extends BaseController
{
public function index(): string
{
$iterator = new Iterator();
$iterator->add('command_new()', static function () {
for ($i = 0; $i <= 10; $i++) {
command_new('env');
}
});
$iterator->add('command()', static function () {
for ($i=0; $i <= 10; $i++) {
command('env');
}
});
return $iterator->run(40000);
}
} |
Benchmarking on macOS shows things are getting very slow.
<?php
namespace App\Controllers;
use CodeIgniter\Debug\Iterator;
class Home extends BaseController
{
public function index(): string
{
$iterator = new Iterator();
$iterator->add('command_new()', static function () {
command_new('env');
});
$iterator->add('command()', static function () {
command('env');
});
return $iterator->run(10);
}
} |
I have already benchmark on 2 PC windows, new better then old. @kenjis I create test simulate multi command running by |
Running on macOS with M2, shows similar results with @kenjis <?php
namespace App\Controllers;
use CodeIgniter\Debug\Iterator;
class Home extends BaseController
{
public function index(): string
{
$outputs = [];
$template = 'On %s runs:';
foreach ([10, 1000, 2000, 4000, 40000] as $run) {
$outputs[] = sprintf($template, number_format($run));
$iterator = new Iterator();
$iterator->add('command_new()', static function () {
command_new('env');
});
$iterator->add('command()', static function () {
command('env');
});
$outputs[] = $iterator->run($run);
$outputs[] = '<br/>';
}
return implode("\n", $outputs);
}
} On 10 runs:
On 1,000 runs:
On 2,000 runs:
On 4,000 runs:
On 40,000 runs:
I removed the for loop inside the closure as the iterator itself loops the closure already. |
Running with the loop inside the closure: <?php
namespace App\Controllers;
use CodeIgniter\Debug\Iterator;
class Home extends BaseController
{
public function index(): string
{
$outputs = [];
$template = 'On %s runs:';
foreach ([10, 1000, 2000, 4000, 40000] as $run) {
$outputs[] = sprintf($template, number_format($run));
$iterator = new Iterator();
$iterator->add('command_new()', static function () {
for ($i = 0; $i <= 10; $i++) {
command_new('env');
}
});
$iterator->add('command()', static function () {
for ($i = 0; $i <= 10; $i++) {
command('env');
}
});
$outputs[] = $iterator->run($run);
$outputs[] = '<br/>';
}
return implode("\n", $outputs);
}
} On 10 runs:
On 1,000 runs:
On 2,000 runs:
On 4,000 runs:
On 40,000 runs:
|
When the iteration is very small, command() is much, much faster. Why? |
If I quit almost all applications except terminal, and disables Xdebug.
But if I am running Firefox and PhpStorm, the values sometime differ a lot like:
<?php
namespace App\Controllers;
use CodeIgniter\Debug\Iterator;
class Home extends BaseController
{
public function index(): string
{
$iterator = new Iterator();
$iterator->add('command_new()', static function () {
});
$iterator->add('command()', static function () {
});
return $iterator->run(40000);
}
} |
Okay, it seems about 20% faster on my MBA. However, there is little tests of this parsing process, so close scrutiny is required to ensure that the new implementation is correct.
<?php
namespace App\Controllers;
use CodeIgniter\Debug\Iterator;
use InvalidArgumentException;
class Home extends BaseController
{
public function index(): string
{
$iterator = new Iterator();
$iterator->add('command_new()', static function () {
Home::command_new('env');
});
$iterator->add('command()', static function () {
Home::command('env');
});
return $iterator->run(400000);
}
public static function command_new(string $command)
{
$regexString = '([^\s]+?)(?:\s|$)';
$regexQuoted = '"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'';
// Match all arguments at once
preg_match_all('/' . $regexQuoted . '|' . $regexString . '/', $command, $matches, PREG_SET_ORDER);
$args = [];
foreach ($matches as $match) {
// Determine which part of the match is the actual argument
$arg = $match[1] !== '' ? stripcslashes($match[1]) : (isset($match[3]) ? stripcslashes($match[3]) : stripcslashes($match[2]));
$args[] = $arg;
}
$command = array_shift($args);
$params = [];
$optionValue = false;
foreach ($args as $i => $arg) {
if (mb_strpos($arg, '-') !== 0) {
if ($optionValue) {
// if this was an option value, it was already
// included in the previous iteration
$optionValue = false;
} else {
// add to segments if not starting with '-'
// and not an option value
$params[] = $arg;
}
continue;
}
$arg = ltrim($arg, '-');
$value = null;
if (isset($args[$i + 1]) && mb_strpos($args[$i + 1], '-') !== 0) {
$value = $args[$i + 1];
$optionValue = true;
}
$params[$arg] = $value;
}
}
public static function command(string $command)
{
$regexString = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
$regexQuoted = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';
$args = [];
$length = strlen($command);
$cursor = 0;
/**
* Adopted from Symfony's `StringInput::tokenize()` with few changes.
*
* @see https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Console/Input/StringInput.php
*/
while ($cursor < $length) {
if (preg_match('/\s+/A', $command, $match, 0, $cursor)) {
// nothing to do
} elseif (preg_match('/' . $regexQuoted . '/A', $command, $match, 0, $cursor)) {
$args[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2));
} elseif (preg_match('/' . $regexString . '/A', $command, $match, 0, $cursor)) {
$args[] = stripcslashes($match[1]);
} else {
// @codeCoverageIgnoreStart
throw new InvalidArgumentException(sprintf(
'Unable to parse input near "... %s ...".',
substr($command, $cursor, 10)
));
// @codeCoverageIgnoreEnd
}
$cursor += strlen($match[0]);
}
$command = array_shift($args);
$params = [];
$optionValue = false;
foreach ($args as $i => $arg) {
if (mb_strpos($arg, '-') !== 0) {
if ($optionValue) {
// if this was an option value, it was already
// included in the previous iteration
$optionValue = false;
} else {
// add to segments if not starting with '-'
// and not an option value
$params[] = $arg;
}
continue;
}
$arg = ltrim($arg, '-');
$value = null;
if (isset($args[$i + 1]) && mb_strpos($args[$i + 1], '-') !== 0) {
$value = $args[$i + 1];
$optionValue = true;
}
$params[$arg] = $value;
}
}
} $ php -v
PHP 8.2.16 (cli) (built: Feb 16 2024 05:30:16) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.16, Copyright (c) Zend Technologies
$ sw_vers
ProductName: macOS
ProductVersion: 12.7.3
BuildVersion: 21H1015 |
However, such micro-optimization would have little or no effect on performance improvement in the real world. I cannot assume that command() will be executed many times in a request. |
Description
Improvement.
Benchmark
Checklist: