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 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 | |
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 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 | |
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"; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
My numbers for new script
`/var/www/html/web $ php callable-performance-statistics.php``