Horse | Implementations | Med./ns | Avg/ns | Dev/% |
---|---|---|---|---|
convert-only-firstclass | class.closure.traditional.array | 33.10 | 33.28 | 0.315 |
convert-only-firstclass | class.closure.firstclass.array | 33.12 | 33.17 | 0.272 |
convert-only-firstclass | class.closure.traditional.string | 33.13 | 33.22 | 0.260 |
convert-only-firstclass | class.closure.firstclass.1 | 33.15 | 33.31 | 0.340 |
convert-only-firstclass | function.closure.traditional | 33.18 | 33.33 | 0.303 |
convert-only-firstclass | class.closure.firstclass.0 | 33.23 | 33.31 | 0.273 |
convert-only-firstclass | function.closure.firstclass | 33.39 | 33.69 | 0.404 |
convert-only-traditional | class.closure.traditional.array | 36.07 | 36.30 | 0.242 |
convert-only-traditional | class.closure.traditional.string | 36.08 | 36.32 | 0.276 |
convert-only-traditional | class.closure.firstclass.1 | 36.08 | 36.36 | 0.310 |
convert-only-traditional | class.closure.firstclass.array | 36.10 | 36.33 | 0.263 |
convert-only-traditional | class.closure.firstclass.0 | 36.20 | 36.31 | 0.231 |
convert-only-traditional | function.closure.traditional | 36.27 | 36.39 | 0.262 |
convert-only-traditional | function.closure.firstclass | 36.29 | 36.39 | 0.224 |
direct | class.closure.firstclass.1 | 63.86 | 64.49 | 0.456 |
direct | class.closure.firstclass.array | 63.91 | 64.42 | 0.316 |
direct | class.closure.traditional.array | 63.98 | 64.34 | 0.301 |
direct | class.closure.traditional.string | 63.99 | 64.38 | 0.280 |
direct | class.closure.firstclass.0 | 64.00 | 64.55 | 0.323 |
direct | function.closure.traditional | 64.16 | 64.82 | 0.480 |
direct | function.closure.firstclass | 64.26 | 65.06 | 0.451 |
direct | function.string | 87.36 | 88.67 | 0.442 |
convert-only-firstclass | function.string | 92.60 | 93.39 | 0.343 |
direct | class.array | 102.60 | 103.47 | 0.342 |
convert-only-firstclass | class.array | 109.36 | 109.74 | 0.298 |
convert-only-traditional | function.string | 112.35 | 112.83 | 0.239 |
direct | class.string | 122.17 | 122.93 | 0.314 |
convert-only-firstclass | class.string | 129.49 | 130.08 | 0.298 |
orig | class.closure.firstclass.array | 136.80 | 137.53 | 0.318 |
orig | class.closure.firstclass.1 | 136.89 | 137.63 | 0.348 |
orig1 | class.closure.firstclass.1 | 136.91 | 138.18 | 0.406 |
orig | class.closure.traditional.string | 136.91 | 137.82 | 0.322 |
orig1 | class.closure.traditional.string | 136.95 | 138.79 | 0.408 |
orig | class.closure.traditional.array | 136.96 | 137.85 | 0.311 |
orig1 | class.closure.traditional.array | 137.01 | 138.29 | 0.392 |
orig | class.closure.firstclass.0 | 137.05 | 138.04 | 0.317 |
orig1 | class.closure.firstclass.array | 137.06 | 138.47 | 0.434 |
orig1 | class.closure.firstclass.0 | 137.21 | 138.23 | 0.315 |
orig1 | function.closure.traditional | 137.33 | 138.59 | 0.389 |
orig | function.closure.firstclass | 137.36 | 138.11 | 0.347 |
orig | function.closure.traditional | 137.36 | 138.36 | 0.356 |
orig1 | function.closure.firstclass | 137.89 | 138.69 | 0.289 |
convert-only-traditional | class.array | 139.83 | 140.21 | 0.199 |
firstclass.direct | function.closure.traditional | 152.16 | 153.10 | 0.293 |
firstclass.direct | class.closure.traditional.string | 152.24 | 153.35 | 0.332 |
firstclass.direct | class.closure.firstclass.1 | 152.33 | 153.63 | 0.335 |
firstclass.direct | class.closure.firstclass.array | 152.39 | 152.99 | 0.239 |
firstclass.direct | class.closure.traditional.array | 152.39 | 152.97 | 0.224 |
firstclass.direct | function.closure.firstclass | 152.62 | 153.90 | 0.302 |
firstclass.direct | class.closure.firstclass.0 | 152.66 | 153.34 | 0.272 |
firstclass | class.closure.firstclass.array | 160.00 | 161.49 | 0.391 |
firstclass | class.closure.firstclass.1 | 160.10 | 161.34 | 0.351 |
firstclass | class.closure.traditional.string | 160.12 | 161.73 | 0.377 |
firstclass | class.closure.traditional.array | 160.13 | 161.05 | 0.293 |
firstclass | function.closure.traditional | 160.15 | 161.16 | 0.304 |
firstclass | class.closure.firstclass.0 | 160.38 | 161.45 | 0.338 |
firstclass | function.closure.firstclass | 160.85 | 161.50 | 0.292 |
closure | class.closure.traditional.string | 169.79 | 170.63 | 0.234 |
closure | class.closure.traditional.array | 169.84 | 171.36 | 0.239 |
closure | class.closure.firstclass.array | 169.84 | 171.08 | 0.264 |
closure | function.closure.traditional | 169.89 | 171.70 | 0.398 |
closure | class.closure.firstclass.1 | 169.93 | 171.44 | 0.348 |
closure | class.closure.firstclass.0 | 170.16 | 171.18 | 0.258 |
closure | function.closure.firstclass | 170.44 | 171.58 | 0.303 |
convert-only-traditional | class.string | 202.15 | 202.92 | 0.182 |
orig | function.string | 205.03 | 205.99 | 0.287 |
orig1 | function.string | 205.38 | 206.92 | 0.268 |
firstclass.direct | function.string | 232.42 | 233.66 | 0.289 |
firstclass | function.string | 244.25 | 245.52 | 0.272 |
orig1 | class.array | 244.33 | 246.22 | 0.318 |
orig | class.array | 244.34 | 245.67 | 0.276 |
firstclass.direct | class.array | 247.99 | 249.52 | 0.317 |
firstclass | class.array | 258.56 | 259.90 | 0.324 |
closure | function.string | 266.95 | 268.51 | 0.288 |
firstclass.direct | class.string | 271.74 | 272.62 | 0.252 |
firstclass | class.string | 283.21 | 284.20 | 0.255 |
closure | class.array | 294.09 | 295.59 | 0.269 |
orig1 | class.string | 332.14 | 333.45 | 0.246 |
orig | class.string | 333.77 | 335.65 | 0.269 |
closure | class.string | 365.58 | 366.83 | 0.227 |
Last active
June 13, 2023 02:05
-
-
Save donquixote/85efcca90056111e967dd14cb1f9de9c to your computer and use it in GitHub Desktop.
Performance comparison for php callables
This file contains 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 | |
namespace N; | |
(static function () use ($argv) { | |
// Number of generated functions and classes. | |
// With very high numbers like 10000, the result of the experiment changes in | |
// unexpected ways. | |
$nFunctions = 300; | |
$paramlist = '$a, $b, $c'; | |
$implementationss = []; | |
for ($i = 0; $i < $nFunctions; ++$i) { | |
$function = 'N\\foo_' . $i; | |
$class = 'N\\C_' . $i; | |
$php = <<<EOT | |
namespace N; | |
function foo_$i($paramlist) {} | |
class C_$i { | |
static function f($paramlist) {} | |
static function getCallback() { | |
return static::f(...); | |
} | |
} | |
EOT; | |
eval($php); | |
foreach ([ | |
'function.string' => $function, | |
'function.closure.firstclass' => $function(...), | |
// This should be the same. | |
'function.closure.traditional' => \Closure::fromCallable($function), | |
'class.array' => [$class, 'f'], | |
'class.string' => $class . '::f', | |
// These are probably all the same. | |
'class.closure.firstclass.0' => $class::f(...), | |
'class.closure.firstclass.1' => $class::getCallback(), | |
'class.closure.firstclass.array' => [$class, 'f'](...), | |
'class.closure.traditional.array' => \Closure::fromCallable([$class, 'f']), | |
'class.closure.traditional.string' => \Closure::fromCallable($class . '::f'), | |
] as $k => $callback) { | |
$implementationss[$k][] = $callback; | |
} | |
} | |
$args = [1, 2, 3]; | |
$callback = static function (callable $hook) use ($args, &$return) { | |
# $result = call_user_func_array($hook, $args); | |
$hook(...$args); | |
}; | |
$horses = [ | |
'direct' => static function (array $implementations) use ($args) { | |
foreach ($implementations as $f) { | |
$f(...$args); | |
} | |
}, | |
'orig' => static function (array $implementations) use ($args, $callback) { | |
foreach ($implementations as $f) { | |
$callback($f); | |
} | |
}, | |
'firstclass' => static function (array $implementations) use ($args, $callback) { | |
foreach ($implementations as $f) { | |
$ff = $f(...); | |
$callback($ff); | |
} | |
}, | |
'firstclass.direct' => static function (array $implementations) use ($args, $callback) { | |
foreach ($implementations as $f) { | |
$callback($f(...)); | |
} | |
}, | |
'closure' => static function (array $implementations) use ($args, $callback) { | |
foreach ($implementations as $f) { | |
$ff = \Closure::fromCallable($f); | |
$callback($ff); | |
} | |
}, | |
'convert-only-firstclass' => static function (array $implementations) use ($args, $callback) { | |
foreach ($implementations as $f) { | |
$f(...); | |
} | |
}, | |
'convert-only-traditional' => static function (array $implementations) use ($args, $callback) { | |
foreach ($implementations as $f) { | |
\Closure::fromCallable($f);; | |
} | |
}, | |
// Register the same one again to detect side effects from ordering. | |
'orig1' => static function (array $implementations) use ($args, $callback) { | |
foreach ($implementations as $f) { | |
$callback($f); | |
} | |
}, | |
]; | |
// Warm up. Without this, results depend on the order of horses. | |
foreach ($horses as $horse) { | |
foreach ($implementationss as $implementations) { | |
$horse($implementations); | |
} | |
} | |
$nanotimess = []; | |
// Odd number is better for median. | |
$n = 501; | |
for ($i = 0; $i < $n; ++$i) { | |
foreach ($horses as $horsename => $horse) { | |
foreach ($implementationss as $k => $implementations) { | |
$t0_nanos = hrtime(TRUE); | |
$horse($implementations); | |
$dur_nanos = hrtime(TRUE) - $t0_nanos; | |
// Convert to nanoseconds for better readability. | |
// Divide by number of functions. | |
$nanotimess[$horsename . ' | ' . $k][] = $dur_nanos / $nFunctions; | |
} | |
} | |
} | |
$weights = []; | |
$rows = []; | |
foreach ($nanotimess as $key => $nanotimes) { | |
$n = count($nanotimes); | |
[$horsename, $k] = explode(' | ', $key); | |
$average = array_sum($nanotimes) / $n; | |
$deviation = sqrt(array_sum(array_map(static function ($dt) use ($average) { | |
return ($dt - $average) * ($dt - $average); | |
}, $nanotimes))) / $n; | |
$dev_percent = $deviation / $average * 100; | |
sort($nanotimes); | |
$median = $nanotimes[floor($n / 2)]; | |
$weights[] = $median; | |
$rows[] = [ | |
$horsename, | |
$k, | |
number_format($median, 2), | |
number_format($average, 2), | |
number_format($dev_percent, 3), | |
]; | |
} | |
array_multisort($weights, $rows); | |
$headers = ['Horse', 'Implementations', 'Med./ns', 'Avg/ns', 'Dev/%']; | |
$markdown = '| ' . implode(' | ', $headers) . " |\n" | |
. str_repeat('|-------', count($headers)) . " |\n"; | |
foreach ($rows as $row) { | |
$markdown .= '| ' . implode(' | ', $row) . " |\n"; | |
} | |
print $markdown; | |
})(); |
This file contains 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 | |
namespace N; | |
use Drupal\Component\Utility\NestedArray; | |
(static function () use ($argv) { | |
$iMax = 10000; | |
$paramlist = '$a, $b, $c'; | |
$implementationss = []; | |
for ($i = 0; $i < $iMax; ++$i) { | |
$function = 'N\\foo_' . $i; | |
$class = 'N\\C_' . $i; | |
$php = <<<EOT | |
namespace N; | |
function foo_{$i}($paramlist) {} | |
class C_{$i} { | |
static function f($paramlist) {} | |
static function getCallback() { | |
return static::f(...); | |
} | |
} | |
EOT; | |
eval($php); | |
foreach ([ | |
'function' => $function, | |
'class' => [$class, 'f'], | |
'class.str' => $class . '::f', | |
'class.first0' => $class::f(...), | |
'class.first1' => $class::getCallback(), | |
] as $k => $callback) { | |
$implementationss[$k][] = $callback; | |
$implementationss["$k.closure"][] = \Closure::fromCallable($callback); | |
$implementationss["$k.firstclass"][] = $callback(...); | |
} | |
} | |
$mode = (string) ($argv[1] ?? 'orig'); | |
$k = (string) ($argv[2] ?? 'function'); | |
$implementations = $implementationss[$k] ?? throw new \Exception("Unexpected '$k'."); | |
print $mode . ' | ' . $k . "\n"; | |
$args = [1, 2, 3]; | |
$callback = static function (callable $hook) use ($args, &$return) { | |
# $result = call_user_func_array($hook, $args); | |
$hook(...$args); | |
}; | |
$t0 = microtime(TRUE); | |
for ($j = 0; $j < 10; ++$j) { | |
switch ($mode) { | |
case 'direct': | |
foreach ($implementations as $f) { | |
$f(...$args); | |
} | |
break; | |
case 'debug': | |
var_export($implementations[0]); | |
exit(); | |
case 'orig': | |
foreach ($implementations as $f) { | |
$callback($f); | |
} | |
break; | |
case 'map_orig': | |
array_map($callback, $implementations); | |
break; | |
case 'walk_orig': | |
array_walk($implementations, $callback); | |
break; | |
case 'closure': | |
foreach ($implementations as $f) { | |
$ff = \Closure::fromCallable($f); | |
$callback($ff); | |
} | |
break; | |
case 'firstclass': | |
foreach ($implementations as $f) { | |
$ff = $f(...); | |
$callback($ff); | |
} | |
break; | |
default: | |
print "NOT ACCEPTED: '" . $mode . "'\n"; | |
return; | |
} | |
} | |
print ((microtime(TRUE) - $t0) * 1000) . ' ms' . "\n"; | |
})(); |
Using this diff for fast callable makes class method calls are the same cost as function
- $callback($ff);
+ $ff(...$args);
Using this diff for fast callable makes class method calls are the same cost as function
I'm a bit confused by this statement :)
What exactly you are comparing with "same cost as function"?
The fastest version is following, implementations can be returned as fisrt-class callables and function calls nearly x2 speed-up
<?php
namespace N;
use Drupal\Component\Utility\NestedArray;
(static function () use ($argv) {
$iMax = 100000;
$paramlist = '$a, $b, $c';
$callbacks = [];
for ($i = 0; $i < $iMax; ++$i) {
$php = <<<EOT
namespace N;
function foo_{$i}($paramlist) {}
class C_{$i} {
static function f($paramlist) {}
}
EOT;
eval($php);
// $callbacks['function'][] = \Closure::fromCallable('N\\foo_' . $i);
// $callbacks['class'][] = \Closure::fromCallable(['N\\C_' . $i, 'f']);
$callbacks['function'][] = ('N\\foo_' . $i)(...);
$callbacks['class'][] = ('N\\C_' . $i)::f(...);
}
$mode = (string) ($argv[1] ?? 'orig');
$k = !empty($argv[2]) ? 'class' : 'function';
$the_callbacks = $callbacks[$k];
print $mode . ' | ' . $k . "\n";
$args = [1, 2, 3];
$callback = static function (callable $hook) use ($args, &$return) {
# $result = call_user_func_array($hook, $args);
$hook(...$args);
};
$t0 = microtime(TRUE);
switch ($mode) {
case 'orig':
foreach ($the_callbacks as $f) {
$callback($f);
}
break;
case 'closure':
foreach ($the_callbacks as $f) {
//$ff = \Closure::fromCallable($f);
$callback($f);
}
break;
case 'firstclass':
foreach ($the_callbacks as $f) {
//$ff = $f(...);
$f(...$args);
}
break;
default:
print "NOT ACCEPTED: '" . $mode . "'\n";
return;
}
print ((microtime(TRUE) - $t0) * 1000) . ' ms' . "\n";
})();
running php 8.3
/var/www/html/web $ php hook.php orig 1
orig | class
50.432205200195 ms
/var/www/html/web $ php hook.php orig 0
orig | function
41.173934936523 ms
/var/www/html/web $ php hook.php firstclass 1
firstclass | class
21.111011505127 ms
/var/www/html/web $ php hook.php firstclass 0
firstclass | function
24.955987930298 ms
/var/www/html/web $ php hook.php closure 0
closure | function
37.466049194336 ms
/var/www/html/web $ php hook.php closure 1
closure | class
41.41902923584 ms
My numbers for new script
`/var/www/html/web $ php callable-performance-statistics.php``
Horse | Implementations | Duration |
---|---|---|
direct | class.firstclass | 20.177125930786 ms |
direct | class.first0.firstclass | 20.490884780884 ms |
direct | class.first1.firstclass | 21.463394165039 ms |
direct | class.closure | 21.545886993408 ms |
direct | function.firstclass | 21.63028717041 ms |
direct | class.str.firstclass | 23.139953613281 ms |
direct | class.first0 | 23.145914077759 ms |
direct | class.first0.closure | 23.294687271118 ms |
direct | class.first1.closure | 25.778532028198 ms |
direct | function | 29.096364974976 ms |
firstclass | function.closure | 32.177925109863 ms |
orig | function.closure | 32.53960609436 ms |
firstclass | function.firstclass | 33.711433410645 ms |
firstclass | class.firstclass | 33.937692642212 ms |
firstclass | class.str.closure | 34.703969955444 ms |
firstclass | class.first1.firstclass | 34.989833831787 ms |
direct | class.str.closure | 35.659074783325 ms |
firstclass | class.first0.firstclass | 36.133289337158 ms |
orig | class.str.closure | 36.141872406006 ms |
closure | function.closure | 36.442518234253 ms |
orig | class.firstclass | 36.524295806885 ms |
firstclass | class.closure | 37.166833877563 ms |
orig | class.first0.firstclass | 37.866830825806 ms |
firstclass | class.first1 | 38.102149963379 ms |
firstclass | class.first1.closure | 38.386106491089 ms |
closure | function.firstclass | 38.406372070312 ms |
orig | function.firstclass | 38.838386535645 ms |
orig | class.first1.firstclass | 39.20578956604 ms |
firstclass | class.first0 | 39.78419303894 ms |
orig | class.str.firstclass | 39.988279342651 ms |
closure | class.firstclass | 40.032625198364 ms |
orig | class.first0 | 40.232181549072 ms |
closure | class.str.closure | 40.549755096436 ms |
orig | class.first0.closure | 40.996789932251 ms |
orig | class.first1.closure | 41.669130325317 ms |
orig | class.closure | 41.853189468384 ms |
firstclass | class.str.firstclass | 41.891813278198 ms |
direct | function.closure | 42.291164398193 ms |
firstclass | class.first0.closure | 42.37174987793 ms |
closure | class.first1.firstclass | 42.494297027588 ms |
direct | class.first1 | 42.759656906128 ms |
orig | function | 43.730497360229 ms |
closure | class.first0.firstclass | 44.094800949097 ms |
closure | class.first0.closure | 44.163703918457 ms |
closure | class.str.firstclass | 44.21854019165 ms |
closure | class.first1.closure | 44.34609413147 ms |
closure | class.closure | 44.489860534668 ms |
orig | class.first1 | 45.003890991211 ms |
closure | class.first0 | 45.227766036987 ms |
closure | class.first1 | 45.444488525391 ms |
direct | class.str | 46.279430389404 ms |
direct | class | 51.458835601807 ms |
firstclass | class | 52.775859832764 ms |
firstclass | function | 54.550647735596 ms |
closure | function | 55.697202682495 ms |
orig | class | 60.397863388062 ms |
closure | class | 65.873622894287 ms |
firstclass | class.str | 71.06614112854 ms |
closure | class.str | 96.184492111206 ms |
orig | class.str | 96.925020217896 ms |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Run in cli as e.g.
Always run each version multiple times to clear variations.
Really 10 times min!
Check the code how
$argv[1]
and$argv[2]
are used.