perf(compiler): 优化静态属性访问性能并增强类型检查

- 添加对静态属性 unset 操作的错误处理
- 实现静态属性的 C++ 引用优化提升访问性能
- 为整型和浮点型静态属性添加原生引用支持
- 在函数上下文中添加静态属性引用映射表
- 更新微基准测试包含静态属性操作性能测试
- 添加静态属性 unset 的单元测试用例
- 修复静态属性访问相关的类型声明问题
pull/1/head
韩天峰 3 months ago
parent 2af89f270e
commit 5e549a4c15
  1. 185
      examples/micro_bench.php
  2. 25
      perf/class-static-prop.php
  3. 14
      perf/empty_class_static_prop.php
  4. 11
      phpunit/code/unset-static-prop.php
  5. 5
      phpunit/src/UndefineTest.php
  6. 28
      src/Php/CompilerBase.php
  7. 3
      src/Php/Context/FunctionContext.php

@ -32,29 +32,29 @@ function simpleicall(int $n)
} }
class Foo { class Foo {
static $a = 12; static int $a = 12;
public int $b = 0; public int $b = 0;
const TEST = 23; const TEST = 23;
static function read_static($n) { static function read_static(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$x = self::$a; $x = self::$a;
} }
} }
static function write_static($n) { static function write_static(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
self::$a = 0; self::$a = 0;
} }
} }
static function isset_static($n) { static function isset_static(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$x = isset(self::$a); $x = isset(self::$a);
} }
} }
static function empty_static($n) { static function empty_static(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$x = empty(self::$a); $x = empty(self::$a);
} }
@ -63,7 +63,7 @@ class Foo {
static function f() { static function f() {
} }
static function call_static($n) { static function call_static(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
self::f(); self::f();
} }
@ -81,43 +81,43 @@ class Foo {
} }
} }
function assign_add_prop($n) { function assign_add_prop(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$this->b += 2; $this->b += 2;
} }
} }
function pre_inc_prop($n) { function pre_inc_prop(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
++$this->b; ++$this->b;
} }
} }
function pre_dec_prop($n) { function pre_dec_prop(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
--$this->b; --$this->b;
} }
} }
function post_inc_prop($n) { function post_inc_prop(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$this->b++; $this->b++;
} }
} }
function post_dec_prop($n) { function post_dec_prop(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$this->b--; $this->b--;
} }
} }
function isset_prop($n) { function isset_prop(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$x = isset($this->b); $x = isset($this->b);
} }
} }
function empty_prop($n) { function empty_prop(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$x = empty($this->b); $x = empty($this->b);
} }
@ -126,13 +126,13 @@ class Foo {
function g() { function g() {
} }
function call($n) { function call(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$this->g(); $this->g();
} }
} }
function read_const($n) { function read_const(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$x = $this::TEST; $x = $this::TEST;
} }
@ -140,49 +140,49 @@ class Foo {
} }
function read_static($n) { function read_static(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$x = Foo::$a; $x = Foo::$a;
} }
} }
function write_static($n) { function write_static(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
Foo::$a = 0; Foo::$a = 0;
} }
} }
function isset_static($n) { function isset_static(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$x = isset(Foo::$a); $x = isset(Foo::$a);
} }
} }
function empty_static($n) { function empty_static(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$x = empty(Foo::$a); $x = empty(Foo::$a);
} }
} }
function call_static($n) { function call_static(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
Foo::f(); Foo::f();
} }
} }
function create_object($n) { function create_object(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$x = new Foo(); $x = new Foo();
} }
} }
function read_const($n) { function read_const(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$x = TEST_CONST_2; $x = TEST_CONST_2;
} }
} }
function read_auto_global($n) { function read_auto_global(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$x = $_GET; $x = $_GET;
} }
@ -208,21 +208,22 @@ function read_str_offset(int $n) {
} }
} }
function issetor(int $n) { function issetor(int $n)
$val = array(0,1,2,3,4,5,6,7,8,9); {
$val = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$x = $val ?: null; $x = $val ?: null;
} }
} }
function issetor2($n) { function issetor2(int $n) {
$f = false; $j = 0; $f = false; $j = 0;
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$x = $f ?: $j + 1; $x = $f ?: $j + 1;
} }
} }
function ternary($n) { function ternary(int $n) {
$val = array(0,1,2,3,4,5,6,7,8,9); $val = array(0,1,2,3,4,5,6,7,8,9);
$f = false; $f = false;
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
@ -230,7 +231,7 @@ function ternary($n) {
} }
} }
function ternary2($n) { function ternary2(int $n) {
$f = false; $j = 0; $f = false; $j = 0;
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
$x = $f ? $f : $j + 1; $x = $f ? $f : $j + 1;
@ -239,7 +240,7 @@ function ternary2($n) {
/*****/ /*****/
function empty_loop($n) { function empty_loop(int $n) {
for ($i = 0; $i < $n; ++$i) { for ($i = 0; $i < $n; ++$i) {
} }
} }
@ -305,68 +306,68 @@ function main()
$t = end_test($t, 'undef_func()', $overhead); $t = end_test($t, 'undef_func()', $overhead);
simpleicall(N); simpleicall(N);
$t = end_test($t, 'int_func()', $overhead); $t = end_test($t, 'int_func()', $overhead);
// Foo::read_static(N); Foo::read_static(N);
// $t = end_test($t, '$x = self::$x', $overhead); $t = end_test($t, '$x = self::$x', $overhead);
// Foo::write_static(N); Foo::write_static(N);
// $t = end_test($t, 'self::$x = 0', $overhead); $t = end_test($t, 'self::$x = 0', $overhead);
// Foo::isset_static(N); Foo::isset_static(N);
// $t = end_test($t, 'isset(self::$x)', $overhead); $t = end_test($t, 'isset(self::$x)', $overhead);
// Foo::empty_static(N); Foo::empty_static(N);
// $t = end_test($t, 'empty(self::$x)', $overhead); $t = end_test($t, 'empty(self::$x)', $overhead);
// read_static(N); read_static(N);
// $t = end_test($t, '$x = Foo::$x', $overhead); $t = end_test($t, '$x = Foo::$x', $overhead);
// write_static(N); write_static(N);
// $t = end_test($t, 'Foo::$x = 0', $overhead); $t = end_test($t, 'Foo::$x = 0', $overhead);
// isset_static(N); isset_static(N);
// $t = end_test($t, 'isset(Foo::$x)', $overhead); $t = end_test($t, 'isset(Foo::$x)', $overhead);
// empty_static(N); empty_static(N);
// $t = end_test($t, 'empty(Foo::$x)', $overhead); $t = end_test($t, 'empty(Foo::$x)', $overhead);
// Foo::call_static(N); Foo::call_static(N);
// $t = end_test($t, 'self::f()', $overhead); $t = end_test($t, 'self::f()', $overhead);
// call_static(N); call_static(N);
// $t = end_test($t, 'Foo::f()', $overhead); $t = end_test($t, 'Foo::f()', $overhead);
// $x = new Foo(); $x = new Foo();
// $x->read_prop(N); $x->read_prop(N);
// $t = end_test($t, '$x = $this->x', $overhead); $t = end_test($t, '$x = $this->x', $overhead);
// $x->write_prop(N); $x->write_prop(N);
// $t = end_test($t, '$this->x = 0', $overhead); $t = end_test($t, '$this->x = 0', $overhead);
// $x->assign_add_prop(N); $x->assign_add_prop(N);
// $t = end_test($t, '$this->x += 2', $overhead); $t = end_test($t, '$this->x += 2', $overhead);
// $x->pre_inc_prop(N); $x->pre_inc_prop(N);
// $t = end_test($t, '++$this->x', $overhead); $t = end_test($t, '++$this->x', $overhead);
// $x->pre_dec_prop(N); $x->pre_dec_prop(N);
// $t = end_test($t, '--$this->x', $overhead); $t = end_test($t, '--$this->x', $overhead);
// $x->post_inc_prop(N); $x->post_inc_prop(N);
// $t = end_test($t, '$this->x++', $overhead); $t = end_test($t, '$this->x++', $overhead);
// $x->post_dec_prop(N); $x->post_dec_prop(N);
// $t = end_test($t, '$this->x--', $overhead); $t = end_test($t, '$this->x--', $overhead);
// $x->isset_prop(N); $x->isset_prop(N);
// $t = end_test($t, 'isset($this->x)', $overhead); $t = end_test($t, 'isset($this->x)', $overhead);
// $x->empty_prop(N); $x->empty_prop(N);
// $t = end_test($t, 'empty($this->x)', $overhead); $t = end_test($t, 'empty($this->x)', $overhead);
// $x->call(N); $x->call(N);
// $t = end_test($t, '$this->f()', $overhead); $t = end_test($t, '$this->f()', $overhead);
// $x->read_const(N); $x->read_const(N);
// $t = end_test($t, '$x = Foo::TEST', $overhead); $t = end_test($t, '$x = Foo::TEST', $overhead);
// create_object(N); create_object(N);
// $t = end_test($t, 'new Foo()', $overhead); $t = end_test($t, 'new Foo()', $overhead);
// read_const(N); read_const(N);
// $t = end_test($t, '$x = TEST', $overhead); $t = end_test($t, '$x = TEST', $overhead);
// read_auto_global(N); read_auto_global(N);
// $t = end_test($t, '$x = $_GET', $overhead); $t = end_test($t, '$x = $_GET', $overhead);
// read_global_var(N); read_global_var(N);
// $t = end_test($t, '$x = $GLOBALS[\'v\']', $overhead); $t = end_test($t, '$x = $GLOBALS[\'v\']', $overhead);
// read_hash(N); read_hash(N);
// $t = end_test($t, '$x = $hash[\'v\']', $overhead); $t = end_test($t, '$x = $hash[\'v\']', $overhead);
// read_str_offset(N); read_str_offset(N);
// $t = end_test($t, '$x = $str[0]', $overhead); $t = end_test($t, '$x = $str[0]', $overhead);
// issetor(N); issetor(N);
// $t = end_test($t, '$x = $a ?: null', $overhead); $t = end_test($t, '$x = $a ?: null', $overhead);
// issetor2(N); issetor2(N);
// $t = end_test($t, '$x = $f ?: tmp', $overhead); $t = end_test($t, '$x = $f ?: tmp', $overhead);
// ternary(N); ternary(N);
// $t = end_test($t, '$x = $f ? $f : $a', $overhead); $t = end_test($t, '$x = $f ? $f : $a', $overhead);
// ternary2(N); ternary2(N);
// $t = end_test($t, '$x = $f ? $f : tmp', $overhead); $t = end_test($t, '$x = $f ? $f : tmp', $overhead);
total(); total();
} }

@ -0,0 +1,25 @@
<?php
use native_types;
class Foo {
static public int $a;
}
function isset_static(int $n) {
for ($i = 0; $i < $n; ++$i) {
$x = isset(Foo::$a);
}
}
function static_prop_add(int $n) {
Foo::$a = 0;
for ($i = 0; $i < $n; ++$i) {
Foo::$a += 13;
}
var_dump(Foo::$a);
}
function main(): void {
isset_static(100000);
static_prop_add(100000);
}

@ -0,0 +1,14 @@
<?php
use native_types;
class Foo {
static public int $a;
}
function main() {
$s = microtime( true);
$n = 1000_0000;
for ($i = 0; $i < $n; ++$i) {
$x = empty(Foo::$a);
}
echo microtime( true) - $s, "\n";
}

@ -0,0 +1,11 @@
<?php
class Foo
{
static public int $a;
}
function main(): void
{
unset(Foo::$a);
}

@ -12,4 +12,9 @@ class UndefineTest extends \BaseTest
{ {
$this->exec('Cannot use [] for array unset', 'unset-array-dim-fetch-empty.php'); $this->exec('Cannot use [] for array unset', 'unset-array-dim-fetch-empty.php');
} }
public function testUnsetStaticProp(): void
{
$this->exec('Attempt to unset static property', 'unset-static-prop.php');
}
} }

@ -3736,6 +3736,8 @@ class CompilerBase extends \PhpAot\Core\Translator
$lines[] = $this->getObjectPropVarName($object, $propName) . ' = 0;'; $lines[] = $this->getObjectPropVarName($object, $propName) . ' = 0;';
} }
} }
} elseif ($this->isStaticPropertyFetch($var)) {
$this->fatalError($var, 'Attempt to unset static property ' . $this->parseIdentifier($var->class) . '::$' . $this->parseIdentifier($var->name));
} elseif ($this->isVarExpr($var)) { } elseif ($this->isVarExpr($var)) {
$name = $this->parseIdentifier($var); $name = $this->parseIdentifier($var);
if (!$this->hasVar($name)) { if (!$this->hasVar($name)) {
@ -4650,8 +4652,29 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseNativeStaticPropertyFetch(Expr\StaticPropertyFetch $expr): string|bool protected function parseNativeStaticPropertyFetch(Expr\StaticPropertyFetch $expr): string|bool
{ {
$class = null;
$nativeProp = $this->findNativeStaticProperty($expr, $class); $nativeProp = $this->findNativeStaticProperty($expr, $class);
if ($nativeProp) { if ($nativeProp) {
// Hoist typed int/float static properties to C++ native references,
// analogous to the $this->intProp reference optimization.
if ($this->nativeTypes && $expr->hasAttribute('nativePropertyDef')) {
/** @var PropertyDef $def */
$def = $expr->getAttribute('nativePropertyDef');
if ($def->type === self::TYPE_INT || $def->type === self::TYPE_FLOAT) {
$propName = $this->parseIdentifier($expr->name);
$refVar = '_static_' . str_replace('\\', '_', $class) . '_' . $propName;
if (!isset($this->context->staticPropRefs[$refVar])) {
$classPtr = $this->getClassEntryPtr($class);
$this->context->staticPropRefs[$refVar] = [
'type' => $def->type,
'classPtr' => $classPtr,
'offsetExpr' => $nativeProp,
];
}
return $refVar;
}
}
if ($expr->hasAttribute('nativeProperty')) { if ($expr->hasAttribute('nativeProperty')) {
$classPtr = $this->getClassEntryPtr($class); $classPtr = $this->getClassEntryPtr($class);
return Symbol::getStaticProperty() . '(' . $classPtr . ', ' . $nativeProp . ')'; return Symbol::getStaticProperty() . '(' . $classPtr . ', ' . $nativeProp . ')';
@ -5373,6 +5396,11 @@ class CompilerBase extends \PhpAot\Core\Translator
foreach ($this->context->objectProps as $name => $info) { foreach ($this->context->objectProps as $name => $info) {
$code .= $this->getIndent() . $info['type'] . ' &' . $name . ' = Z_LVAL_P(' . $info['getter'] . '.unwrap_ptr());' . PHP_EOL; $code .= $this->getIndent() . $info['type'] . ' &' . $name . ' = Z_LVAL_P(' . $info['getter'] . '.unwrap_ptr());' . PHP_EOL;
} }
foreach ($this->context->staticPropRefs as $name => $info) {
$getter = Symbol::getStaticProperty() . '(' . $info['classPtr'] . ', ' . $info['offsetExpr'] . ')';
$zvalMacro = ($info['type'] === self::TYPE_FLOAT) ? 'Z_DVAL_P' : 'Z_LVAL_P';
$code .= $this->getIndent() . $info['type'] . ' &' . $name . ' = ' . $zvalMacro . '(' . $getter . '.unwrap_ptr());' . PHP_EOL;
}
return $code; return $code;
} }

@ -55,6 +55,8 @@ class FunctionContext
public array $beforeStmtLines = []; public array $beforeStmtLines = [];
public array $afterStmtLines = []; public array $afterStmtLines = [];
public array $objectProps; public array $objectProps;
/** Map of ref var name => ['type' => ..., 'class' => ..., 'prop' => ..., 'ceExpr' => ..., 'offsetExpr' => ...] */
public array $staticPropRefs = [];
public int $scopeLevel = 0; public int $scopeLevel = 0;
/** /**
* @var array<int, ScopeContext> * @var array<int, ScopeContext>
@ -72,6 +74,7 @@ class FunctionContext
$this->objectProps = []; $this->objectProps = [];
$this->stableObjects = []; $this->stableObjects = [];
$this->hoistedProps = []; $this->hoistedProps = [];
$this->staticPropRefs = [];
$this->ceWrappers = []; $this->ceWrappers = [];
$this->tmpVarIndex = 0; $this->tmpVarIndex = 0;
$this->scopeLayouts = []; $this->scopeLayouts = [];

Loading…
Cancel
Save