diff --git a/perf/code.php b/perf/code.php new file mode 100644 index 00000000..25401163 --- /dev/null +++ b/perf/code.php @@ -0,0 +1,177 @@ +hit($i); + } + return $sum; +} + +function dynamic_method_call(int $n): int +{ + global $dynamicObject; + + $sum = 0; + for ($i = 0; $i < $n; ++$i) { + $sum += $dynamicObject->hit($i); + } + return $sum; +} + +function new_object_only(int $n): int +{ + $sum = 0; + for ($i = 0; $i < $n; ++$i) { + $obj = new EmptyObject(); + $sum += $obj instanceof EmptyObject ? 1 : 0; + } + return $sum; +} + +function temporary_new_and_call(int $n): int +{ + $sum = 0; + for ($i = 0; $i < $n; ++$i) { + $sum += (new TemporaryCallTarget())->hit($i); + } + return $sum; +} + +function empty_new_baseline(int $n): int +{ + $sum = 0; + for ($i = 0; $i < $n; ++$i) { + $sum += 1; + } + return $sum; +} + +function elapsed_ns(string $case, int $n): array +{ + $start = hrtime(true); + if ($case === 'baseline') { + $result = baseline_loop($n); + } elseif ($case === 'native') { + $result = native_method_call($n); + } elseif ($case === 'dynamic') { + $result = dynamic_method_call($n); + } elseif ($case === 'empty-new') { + $result = empty_new_baseline($n); + } elseif ($case === 'new') { + $result = new_object_only($n); + } elseif ($case === 'temporary') { + $result = temporary_new_and_call($n); + } else { + throw new RuntimeException("unknown benchmark case: " . $case); + } + return [hrtime(true) - $start, $result]; +} + +function measure_min(string $case, int $n, int $rounds): array +{ + $bestNs = 0; + $bestResult = 0; + + for ($i = 0; $i < $rounds; ++$i) { + [$ns, $result] = elapsed_ns($case, $n); + if ($i === 0 || $ns < $bestNs) { + $bestNs = $ns; + $bestResult = $result; + } + } + + return [$bestNs, $bestResult]; +} + +function report(string $name, int $ns, int $n, int $baselineNs = 0): void +{ + $adjusted = max(0, $ns - $baselineNs); + printf( + "%-20s raw=%0.6fs adjusted=%0.6fs ns/op=%0.2f\n", + $name, + $ns / 1000000000.0, + $adjusted / 1000000000.0, + $adjusted / (float) $n, + ); +} + +function main(): void +{ + global $dynamicObject; + + $n = N; + $dynamicObject = new DynamicCallTarget(); + + baseline_loop(1000); + native_method_call(1000); + dynamic_method_call(1000); + new_object_only(1000); + temporary_new_and_call(1000); + + [$baseline, $baselineResult] = measure_min('baseline', $n, ROUNDS); + [$native, $nativeResult] = measure_min('native', $n, ROUNDS); + [$dynamic, $dynamicResult] = measure_min('dynamic', $n, ROUNDS); + [$emptyNew, $emptyNewResult] = measure_min('empty-new', $n, ROUNDS); + [$newObject, $newResult] = measure_min('new', $n, ROUNDS); + [$temporary, $temporaryResult] = measure_min('temporary', $n, ROUNDS); + + printf("n=%d rounds=%d\n", $n, ROUNDS); + report('baseline loop', $baseline, $n); + report('native call', $native, $n, $baseline); + report('dynamic call', $dynamic, $n, $baseline); + report('newObject', $newObject, $n, $emptyNew); + report('newObject + call', $temporary, $n, $baseline); + + printf( + "checksums: baseline=%d native=%d dynamic=%d new=%d temporary=%d\n", + $baselineResult, + $nativeResult, + $dynamicResult, + $newResult + $emptyNewResult, + $temporaryResult, + ); +} diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index b45ea76b..add3b483 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -1063,6 +1063,29 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont return $this->context->objectProps[$this->getObjectPropVarName($object, $prop)]; } + protected function getObjectPropInfoByVar(string $var): ?array + { + return $this->context->objectProps[$var] ?? null; + } + + protected function registerObjectPropVar(string $var, array $info): void + { + if (isset($this->context->objectProps[$var])) { + return; + } + $this->context->objectProps[$var] = $info; + } + + protected function registerHoistedObjectPropVar(string $var, string $type, string $getter): void + { + $info = $this->getHoistedObjectPropInfo($type); + $this->registerObjectPropVar($var, [ + 'type' => $info['type'], + 'getter' => $getter, + 'kind' => $info['kind'], + ]); + } + protected function getNativeName(string $fn, string $ns = '', string $class = ''): string { $names = []; @@ -2420,8 +2443,10 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont $this->parsePropertyFetch($expr); $propVar = $this->getNativePropertyVar($expr); if ($propVar !== null) { - $info = $this->context->objectProps[$propVar]; - return $info['type']; + $info = $this->getObjectPropInfoByVar($propVar); + if ($info !== null) { + return $info['type']; + } } } } @@ -4942,14 +4967,7 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont if (!$this->canHoistObjectProp($objectVar, $propName)) { return null; } - if (!$this->hasObjectPropVar($propVar)) { - $info = $this->getHoistedObjectPropInfo($def->type); - $this->context->objectProps[$propVar] = [ - 'type' => $info['type'], - 'getter' => $getter, - 'kind' => $info['kind'], - ]; - } + $this->registerHoistedObjectPropVar($propVar, $def->type, $getter); $this->setNativePropertyVar($expr, $propVar); $this->setNativePropertyValueSource($expr, self::NATIVE_PROPERTY_VALUE_VAR); return $propVar; diff --git a/src/Php/Parser/BinaryOpTrait.php b/src/Php/Parser/BinaryOpTrait.php index 22214923..92552473 100644 --- a/src/Php/Parser/BinaryOpTrait.php +++ b/src/Php/Parser/BinaryOpTrait.php @@ -206,8 +206,9 @@ trait BinaryOpTrait if ($expr instanceof Expr\PropertyFetch) { $nativePropertyVar = $this->getNativePropertyVar($expr); if ($nativePropertyVar !== null && $nativePropertyVar === $value) { - if (isset($this->context->objectProps[$nativePropertyVar])) { - return $this->context->objectProps[$nativePropertyVar]['type']; + $info = $this->getObjectPropInfoByVar($nativePropertyVar); + if ($info !== null) { + return $info['type']; } $def = $this->getNativePropertyDef($expr); if ($def && $this->isNativePropertyTypedValue($expr)) { diff --git a/src/Php/Resolver/InstancePropertyFetchTarget.php b/src/Php/Resolver/InstancePropertyFetchTarget.php new file mode 100644 index 00000000..6ef354ea --- /dev/null +++ b/src/Php/Resolver/InstancePropertyFetchTarget.php @@ -0,0 +1,18 @@ +dynamicExpression !== null; + } +}