TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
539 lines
14 KiB
539 lines
14 KiB
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
const DYNAMIC_CALL_ITERATIONS = 1_000_000;
|
|
const DYNAMIC_CALL_ROUNDS = 5;
|
|
|
|
function dynamicCallNoArgs(): int
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
function dynamicCallAddOne(int $value): int
|
|
{
|
|
return $value + 1;
|
|
}
|
|
|
|
function dynamicCallAddTwoArgs(int $left, int $right): int
|
|
{
|
|
return $left + $right;
|
|
}
|
|
|
|
function dynamicCallAddFourArgs(int $a, int $b, int $c, int $d): int
|
|
{
|
|
return $a + $b + $c + $d;
|
|
}
|
|
|
|
function dynamicCallAddTwo(int $value): int
|
|
{
|
|
return $value + 2;
|
|
}
|
|
|
|
function dynamicCallAddThree(int $value): int
|
|
{
|
|
return $value + 3;
|
|
}
|
|
|
|
function dynamicCallAddFour(int $value): int
|
|
{
|
|
return $value + 4;
|
|
}
|
|
|
|
function dynamicCallAddFive(int $value): int
|
|
{
|
|
return $value + 5;
|
|
}
|
|
|
|
function dynamicCallAddSix(int $value): int
|
|
{
|
|
return $value + 6;
|
|
}
|
|
|
|
function dynamicCallAddSeven(int $value): int
|
|
{
|
|
return $value + 7;
|
|
}
|
|
|
|
function dynamicCallAddEight(int $value): int
|
|
{
|
|
return $value + 8;
|
|
}
|
|
|
|
final class DynamicCallTarget
|
|
{
|
|
public static function addOne(int $value): int
|
|
{
|
|
return $value + 1;
|
|
}
|
|
|
|
public static function addTwoStatic(int $value): int
|
|
{
|
|
return $value + 2;
|
|
}
|
|
|
|
public function addTwo(int $value): int
|
|
{
|
|
return $value + 2;
|
|
}
|
|
|
|
public function hitOne(int $value): int
|
|
{
|
|
return $value + 1;
|
|
}
|
|
|
|
public function hitTwo(int $value): int
|
|
{
|
|
return $value + 2;
|
|
}
|
|
|
|
public function hitZero(): int
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
public function __invoke(int $value): int
|
|
{
|
|
return $value + 3;
|
|
}
|
|
}
|
|
|
|
final class DynamicCallAlternateTarget
|
|
{
|
|
public static function addOne(int $value): int
|
|
{
|
|
return $value + 1;
|
|
}
|
|
|
|
public static function addTwoStatic(int $value): int
|
|
{
|
|
return $value + 2;
|
|
}
|
|
|
|
public function hitOne(int $value): int
|
|
{
|
|
return $value + 1;
|
|
}
|
|
}
|
|
|
|
final class ScopedDynamicCallTarget
|
|
{
|
|
private function hitZero(): int
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
protected function hitOne(int $value): int
|
|
{
|
|
return $value + 1;
|
|
}
|
|
|
|
public function runDynamicNameZeroArgs(int $iterations): int
|
|
{
|
|
$method = 'hitZero';
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $this->$method();
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
public function runDynamicName(int $iterations): int
|
|
{
|
|
$method = 'hitOne';
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $this->$method($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
public function runNamedDynamicReceiver(object $target, int $iterations): int
|
|
{
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $target->hitOne($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
}
|
|
|
|
function createDynamicMethodReceiver(): object
|
|
{
|
|
return new DynamicCallTarget();
|
|
}
|
|
|
|
function runDirectCall(int $iterations): int
|
|
{
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += dynamicCallAddOne($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runMonomorphicStringCall(int $iterations): int
|
|
{
|
|
$callback = 'dynamicCallAddOne';
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $callback($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runMonomorphicStringCallZeroArgs(int $iterations): int
|
|
{
|
|
$callback = 'dynamicCallNoArgs';
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $callback();
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runMonomorphicStringCallTwoArgs(int $iterations): int
|
|
{
|
|
$callback = 'dynamicCallAddTwoArgs';
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $callback($i, 1);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runMonomorphicStringCallFourArgs(int $iterations): int
|
|
{
|
|
$callback = 'dynamicCallAddFourArgs';
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $callback($i, 1, 2, 3);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runAlternatingStringCall(int $iterations): int
|
|
{
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$callback = ($i & 1) === 0 ? 'dynamicCallAddOne' : 'dynamicCallAddTwo';
|
|
$sum += $callback($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runMegamorphicStringCall(int $iterations): int
|
|
{
|
|
$callbacks = [
|
|
'dynamicCallAddOne',
|
|
'dynamicCallAddTwo',
|
|
'dynamicCallAddThree',
|
|
'dynamicCallAddFour',
|
|
'dynamicCallAddFive',
|
|
'dynamicCallAddSix',
|
|
'dynamicCallAddSeven',
|
|
'dynamicCallAddEight',
|
|
];
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$callback = $callbacks[($i * 5 + 3) & 7];
|
|
$sum += $callback($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runMonomorphicClosureCall(int $iterations): int
|
|
{
|
|
$callback = static fn (int $value): int => $value + 1;
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $callback($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runAlternatingClosureCall(int $iterations): int
|
|
{
|
|
$first = static fn (int $value): int => $value + 1;
|
|
$second = static fn (int $value): int => $value + 2;
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$callback = ($i & 1) === 0 ? $first : $second;
|
|
$sum += $callback($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runStaticMethodStringCall(int $iterations): int
|
|
{
|
|
$callback = 'DynamicCallTarget::addOne';
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $callback($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runDynamicStaticClassCall(int $iterations): int
|
|
{
|
|
$class = DynamicCallTarget::class;
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $class::addOne($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runDynamicStaticMethodCall(int $iterations): int
|
|
{
|
|
$method = 'addOne';
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += DynamicCallTarget::$method($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runDynamicStaticClassAndMethodCall(int $iterations): int
|
|
{
|
|
$class = DynamicCallTarget::class;
|
|
$method = 'addOne';
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $class::$method($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runAlternatingDynamicStaticClassCall(int $iterations): int
|
|
{
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$class = ($i & 1) === 0 ? DynamicCallTarget::class : DynamicCallAlternateTarget::class;
|
|
$sum += $class::addOne($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runAlternatingDynamicStaticMethodCall(int $iterations): int
|
|
{
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$method = ($i & 1) === 0 ? 'addOne' : 'addTwoStatic';
|
|
$sum += DynamicCallTarget::$method($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runAlternatingDynamicStaticClassAndMethodCall(int $iterations): int
|
|
{
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$class = ($i & 1) === 0 ? DynamicCallTarget::class : DynamicCallAlternateTarget::class;
|
|
$method = ($i & 1) === 0 ? 'addOne' : 'addTwoStatic';
|
|
$sum += $class::$method($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runObjectMethodArrayCall(int $iterations): int
|
|
{
|
|
$target = new DynamicCallTarget();
|
|
$callback = [$target, 'addTwo'];
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $callback($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runInvokableObjectCall(int $iterations): int
|
|
{
|
|
$callback = new DynamicCallTarget();
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $callback($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runMonomorphicMethodNameCall(int $iterations): int
|
|
{
|
|
$target = new DynamicCallTarget();
|
|
$method = 'hitOne';
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $target->$method($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runAlternatingMethodNameCall(int $iterations): int
|
|
{
|
|
$target = new DynamicCallTarget();
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$method = ($i & 1) === 0 ? 'hitOne' : 'hitTwo';
|
|
$sum += $target->$method($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runPolymorphicMethodReceiverCall(int $iterations): int
|
|
{
|
|
$targets = [new DynamicCallTarget(), new DynamicCallAlternateTarget()];
|
|
$method = 'hitOne';
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$target = $targets[$i & 1];
|
|
$sum += $target->$method($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runNamedMethodDynamicReceiverZeroArgs(int $iterations): int
|
|
{
|
|
// The declared `object` return type deliberately hides the concrete class
|
|
// from TypePHP while keeping the call site monomorphic at runtime.
|
|
$target = createDynamicMethodReceiver();
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $target->hitZero();
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runNamedMethodDynamicReceiverCall(int $iterations): int
|
|
{
|
|
$target = createDynamicMethodReceiver();
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$sum += $target->hitOne($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runNamedMethodPolymorphicReceiverCall(int $iterations): int
|
|
{
|
|
$targets = [new DynamicCallTarget(), new DynamicCallAlternateTarget()];
|
|
$sum = 0;
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$target = $targets[$i & 1];
|
|
$sum += $target->hitOne($i);
|
|
}
|
|
return $sum;
|
|
}
|
|
|
|
function runScopedMethodNameZeroArgs(int $iterations): int
|
|
{
|
|
$target = new ScopedDynamicCallTarget();
|
|
return $target->runDynamicNameZeroArgs($iterations);
|
|
}
|
|
|
|
function runScopedMethodNameCall(int $iterations): int
|
|
{
|
|
$target = new ScopedDynamicCallTarget();
|
|
return $target->runDynamicName($iterations);
|
|
}
|
|
|
|
function runScopedNamedDynamicReceiverCall(int $iterations): int
|
|
{
|
|
$target = new ScopedDynamicCallTarget();
|
|
return $target->runNamedDynamicReceiver($target, $iterations);
|
|
}
|
|
|
|
function runDynamicCallCase(string $case, int $iterations): int
|
|
{
|
|
return match ($case) {
|
|
'direct' => runDirectCall($iterations),
|
|
'string_monomorphic_zero' => runMonomorphicStringCallZeroArgs($iterations),
|
|
'string_monomorphic' => runMonomorphicStringCall($iterations),
|
|
'string_monomorphic_two' => runMonomorphicStringCallTwoArgs($iterations),
|
|
'string_monomorphic_four' => runMonomorphicStringCallFourArgs($iterations),
|
|
'string_alternating' => runAlternatingStringCall($iterations),
|
|
'string_megamorphic' => runMegamorphicStringCall($iterations),
|
|
'closure_monomorphic' => runMonomorphicClosureCall($iterations),
|
|
'closure_alternating' => runAlternatingClosureCall($iterations),
|
|
'static_method_string' => runStaticMethodStringCall($iterations),
|
|
'static_class_dynamic' => runDynamicStaticClassCall($iterations),
|
|
'static_method_dynamic' => runDynamicStaticMethodCall($iterations),
|
|
'static_class_method_dynamic' => runDynamicStaticClassAndMethodCall($iterations),
|
|
'static_class_alternating' => runAlternatingDynamicStaticClassCall($iterations),
|
|
'static_method_alternating' => runAlternatingDynamicStaticMethodCall($iterations),
|
|
'static_class_method_alternating' => runAlternatingDynamicStaticClassAndMethodCall($iterations),
|
|
'object_method_array' => runObjectMethodArrayCall($iterations),
|
|
'invokable_object' => runInvokableObjectCall($iterations),
|
|
'method_name_monomorphic' => runMonomorphicMethodNameCall($iterations),
|
|
'method_name_alternating' => runAlternatingMethodNameCall($iterations),
|
|
'method_receiver_polymorphic' => runPolymorphicMethodReceiverCall($iterations),
|
|
'named_method_dynamic_receiver_zero' => runNamedMethodDynamicReceiverZeroArgs($iterations),
|
|
'named_method_dynamic_receiver' => runNamedMethodDynamicReceiverCall($iterations),
|
|
'named_method_polymorphic_receiver' => runNamedMethodPolymorphicReceiverCall($iterations),
|
|
'scoped_method_name_zero' => runScopedMethodNameZeroArgs($iterations),
|
|
'scoped_method_name' => runScopedMethodNameCall($iterations),
|
|
'scoped_named_dynamic_receiver' => runScopedNamedDynamicReceiverCall($iterations),
|
|
default => throw new RuntimeException("Unknown benchmark case: {$case}"),
|
|
};
|
|
}
|
|
|
|
function measureDynamicCallCase(string $case): array
|
|
{
|
|
for ($warmup = 0; $warmup < 2; $warmup++) {
|
|
runDynamicCallCase($case, 1_000);
|
|
}
|
|
|
|
$best = 1.0e30;
|
|
$bestResult = 0;
|
|
for ($round = 0; $round < DYNAMIC_CALL_ROUNDS; $round++) {
|
|
$start = hrtime(true);
|
|
$result = runDynamicCallCase($case, DYNAMIC_CALL_ITERATIONS);
|
|
$elapsed = hrtime(true) - $start;
|
|
if ($elapsed < $best) {
|
|
$best = $elapsed;
|
|
$bestResult = $result;
|
|
}
|
|
}
|
|
|
|
return [$best / DYNAMIC_CALL_ITERATIONS, $bestResult];
|
|
}
|
|
|
|
function main(): void
|
|
{
|
|
$selectedCase = getenv('DYNAMIC_CALL_CASE');
|
|
foreach ([
|
|
'direct',
|
|
'string_monomorphic_zero',
|
|
'string_monomorphic',
|
|
'string_monomorphic_two',
|
|
'string_monomorphic_four',
|
|
'string_alternating',
|
|
'string_megamorphic',
|
|
'closure_monomorphic',
|
|
'closure_alternating',
|
|
'static_method_string',
|
|
'static_class_dynamic',
|
|
'static_method_dynamic',
|
|
'static_class_method_dynamic',
|
|
'static_class_alternating',
|
|
'static_method_alternating',
|
|
'static_class_method_alternating',
|
|
'object_method_array',
|
|
'invokable_object',
|
|
'method_name_monomorphic',
|
|
'method_name_alternating',
|
|
'method_receiver_polymorphic',
|
|
'named_method_dynamic_receiver_zero',
|
|
'named_method_dynamic_receiver',
|
|
'named_method_polymorphic_receiver',
|
|
'scoped_method_name_zero',
|
|
'scoped_method_name',
|
|
'scoped_named_dynamic_receiver',
|
|
] as $case) {
|
|
if (is_string($selectedCase) && $selectedCase !== '' && $selectedCase !== $case) {
|
|
continue;
|
|
}
|
|
[$nanoseconds, $result] = measureDynamicCallCase($case);
|
|
printf("%s_ns=%.3f\n", $case, $nanoseconds);
|
|
printf("checksum_%s=%d\n", $case, $result);
|
|
}
|
|
}
|
|
|