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 {
static $a = 12;
static int $a = 12;
public int $b = 0;
const TEST = 23;
static function read_static($n) {
static function read_static(int $n) {
for ($i = 0; $i < $n; ++$i) {
$x = self::$a;
}
}
static function write_static($n) {
static function write_static(int $n) {
for ($i = 0; $i < $n; ++$i) {
self::$a = 0;
}
}
static function isset_static($n) {
static function isset_static(int $n) {
for ($i = 0; $i < $n; ++$i) {
$x = isset(self::$a);
}
}
static function empty_static($n) {
static function empty_static(int $n) {
for ($i = 0; $i < $n; ++$i) {
$x = empty(self::$a);
}
@ -63,7 +63,7 @@ class Foo {
static function f() {
}
static function call_static($n) {
static function call_static(int $n) {
for ($i = 0; $i < $n; ++$i) {
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) {
$this->b += 2;
}
}
function pre_inc_prop($n) {
function pre_inc_prop(int $n) {
for ($i = 0; $i < $n; ++$i) {
++$this->b;
}
}
function pre_dec_prop($n) {
function pre_dec_prop(int $n) {
for ($i = 0; $i < $n; ++$i) {
--$this->b;
}
}
function post_inc_prop($n) {
function post_inc_prop(int $n) {
for ($i = 0; $i < $n; ++$i) {
$this->b++;
}
}
function post_dec_prop($n) {
function post_dec_prop(int $n) {
for ($i = 0; $i < $n; ++$i) {
$this->b--;
}
}
function isset_prop($n) {
function isset_prop(int $n) {
for ($i = 0; $i < $n; ++$i) {
$x = isset($this->b);
}
}
function empty_prop($n) {
function empty_prop(int $n) {
for ($i = 0; $i < $n; ++$i) {
$x = empty($this->b);
}
@ -126,13 +126,13 @@ class Foo {
function g() {
}
function call($n) {
function call(int $n) {
for ($i = 0; $i < $n; ++$i) {
$this->g();
}
}
function read_const($n) {
function read_const(int $n) {
for ($i = 0; $i < $n; ++$i) {
$x = $this::TEST;
}
@ -140,49 +140,49 @@ class Foo {
}
function read_static($n) {
function read_static(int $n) {
for ($i = 0; $i < $n; ++$i) {
$x = Foo::$a;
}
}
function write_static($n) {
function write_static(int $n) {
for ($i = 0; $i < $n; ++$i) {
Foo::$a = 0;
}
}
function isset_static($n) {
function isset_static(int $n) {
for ($i = 0; $i < $n; ++$i) {
$x = isset(Foo::$a);
}
}
function empty_static($n) {
function empty_static(int $n) {
for ($i = 0; $i < $n; ++$i) {
$x = empty(Foo::$a);
}
}
function call_static($n) {
function call_static(int $n) {
for ($i = 0; $i < $n; ++$i) {
Foo::f();
}
}
function create_object($n) {
function create_object(int $n) {
for ($i = 0; $i < $n; ++$i) {
$x = new Foo();
}
}
function read_const($n) {
function read_const(int $n) {
for ($i = 0; $i < $n; ++$i) {
$x = TEST_CONST_2;
}
}
function read_auto_global($n) {
function read_auto_global(int $n) {
for ($i = 0; $i < $n; ++$i) {
$x = $_GET;
}
@ -208,21 +208,22 @@ function read_str_offset(int $n) {
}
}
function issetor(int $n) {
$val = array(0,1,2,3,4,5,6,7,8,9);
function issetor(int $n)
{
$val = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
for ($i = 0; $i < $n; ++$i) {
$x = $val ?: null;
}
}
function issetor2($n) {
function issetor2(int $n) {
$f = false; $j = 0;
for ($i = 0; $i < $n; ++$i) {
$x = $f ?: $j + 1;
}
}
function ternary($n) {
function ternary(int $n) {
$val = array(0,1,2,3,4,5,6,7,8,9);
$f = false;
for ($i = 0; $i < $n; ++$i) {
@ -230,7 +231,7 @@ function ternary($n) {
}
}
function ternary2($n) {
function ternary2(int $n) {
$f = false; $j = 0;
for ($i = 0; $i < $n; ++$i) {
$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) {
}
}
@ -305,68 +306,68 @@ function main()
$t = end_test($t, 'undef_func()', $overhead);
simpleicall(N);
$t = end_test($t, 'int_func()', $overhead);
// Foo::read_static(N);
// $t = end_test($t, '$x = self::$x', $overhead);
// Foo::write_static(N);
// $t = end_test($t, 'self::$x = 0', $overhead);
// Foo::isset_static(N);
// $t = end_test($t, 'isset(self::$x)', $overhead);
// Foo::empty_static(N);
// $t = end_test($t, 'empty(self::$x)', $overhead);
// read_static(N);
// $t = end_test($t, '$x = Foo::$x', $overhead);
// write_static(N);
// $t = end_test($t, 'Foo::$x = 0', $overhead);
// isset_static(N);
// $t = end_test($t, 'isset(Foo::$x)', $overhead);
// empty_static(N);
// $t = end_test($t, 'empty(Foo::$x)', $overhead);
// Foo::call_static(N);
// $t = end_test($t, 'self::f()', $overhead);
// call_static(N);
// $t = end_test($t, 'Foo::f()', $overhead);
// $x = new Foo();
// $x->read_prop(N);
// $t = end_test($t, '$x = $this->x', $overhead);
// $x->write_prop(N);
// $t = end_test($t, '$this->x = 0', $overhead);
// $x->assign_add_prop(N);
// $t = end_test($t, '$this->x += 2', $overhead);
// $x->pre_inc_prop(N);
// $t = end_test($t, '++$this->x', $overhead);
// $x->pre_dec_prop(N);
// $t = end_test($t, '--$this->x', $overhead);
// $x->post_inc_prop(N);
// $t = end_test($t, '$this->x++', $overhead);
// $x->post_dec_prop(N);
// $t = end_test($t, '$this->x--', $overhead);
// $x->isset_prop(N);
// $t = end_test($t, 'isset($this->x)', $overhead);
// $x->empty_prop(N);
// $t = end_test($t, 'empty($this->x)', $overhead);
// $x->call(N);
// $t = end_test($t, '$this->f()', $overhead);
// $x->read_const(N);
// $t = end_test($t, '$x = Foo::TEST', $overhead);
// create_object(N);
// $t = end_test($t, 'new Foo()', $overhead);
// read_const(N);
// $t = end_test($t, '$x = TEST', $overhead);
// read_auto_global(N);
// $t = end_test($t, '$x = $_GET', $overhead);
// read_global_var(N);
// $t = end_test($t, '$x = $GLOBALS[\'v\']', $overhead);
// read_hash(N);
// $t = end_test($t, '$x = $hash[\'v\']', $overhead);
// read_str_offset(N);
// $t = end_test($t, '$x = $str[0]', $overhead);
// issetor(N);
// $t = end_test($t, '$x = $a ?: null', $overhead);
// issetor2(N);
// $t = end_test($t, '$x = $f ?: tmp', $overhead);
// ternary(N);
// $t = end_test($t, '$x = $f ? $f : $a', $overhead);
// ternary2(N);
// $t = end_test($t, '$x = $f ? $f : tmp', $overhead);
Foo::read_static(N);
$t = end_test($t, '$x = self::$x', $overhead);
Foo::write_static(N);
$t = end_test($t, 'self::$x = 0', $overhead);
Foo::isset_static(N);
$t = end_test($t, 'isset(self::$x)', $overhead);
Foo::empty_static(N);
$t = end_test($t, 'empty(self::$x)', $overhead);
read_static(N);
$t = end_test($t, '$x = Foo::$x', $overhead);
write_static(N);
$t = end_test($t, 'Foo::$x = 0', $overhead);
isset_static(N);
$t = end_test($t, 'isset(Foo::$x)', $overhead);
empty_static(N);
$t = end_test($t, 'empty(Foo::$x)', $overhead);
Foo::call_static(N);
$t = end_test($t, 'self::f()', $overhead);
call_static(N);
$t = end_test($t, 'Foo::f()', $overhead);
$x = new Foo();
$x->read_prop(N);
$t = end_test($t, '$x = $this->x', $overhead);
$x->write_prop(N);
$t = end_test($t, '$this->x = 0', $overhead);
$x->assign_add_prop(N);
$t = end_test($t, '$this->x += 2', $overhead);
$x->pre_inc_prop(N);
$t = end_test($t, '++$this->x', $overhead);
$x->pre_dec_prop(N);
$t = end_test($t, '--$this->x', $overhead);
$x->post_inc_prop(N);
$t = end_test($t, '$this->x++', $overhead);
$x->post_dec_prop(N);
$t = end_test($t, '$this->x--', $overhead);
$x->isset_prop(N);
$t = end_test($t, 'isset($this->x)', $overhead);
$x->empty_prop(N);
$t = end_test($t, 'empty($this->x)', $overhead);
$x->call(N);
$t = end_test($t, '$this->f()', $overhead);
$x->read_const(N);
$t = end_test($t, '$x = Foo::TEST', $overhead);
create_object(N);
$t = end_test($t, 'new Foo()', $overhead);
read_const(N);
$t = end_test($t, '$x = TEST', $overhead);
read_auto_global(N);
$t = end_test($t, '$x = $_GET', $overhead);
read_global_var(N);
$t = end_test($t, '$x = $GLOBALS[\'v\']', $overhead);
read_hash(N);
$t = end_test($t, '$x = $hash[\'v\']', $overhead);
read_str_offset(N);
$t = end_test($t, '$x = $str[0]', $overhead);
issetor(N);
$t = end_test($t, '$x = $a ?: null', $overhead);
issetor2(N);
$t = end_test($t, '$x = $f ?: tmp', $overhead);
ternary(N);
$t = end_test($t, '$x = $f ? $f : $a', $overhead);
ternary2(N);
$t = end_test($t, '$x = $f ? $f : tmp', $overhead);
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');
}
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;';
}
}
} elseif ($this->isStaticPropertyFetch($var)) {
$this->fatalError($var, 'Attempt to unset static property ' . $this->parseIdentifier($var->class) . '::$' . $this->parseIdentifier($var->name));
} elseif ($this->isVarExpr($var)) {
$name = $this->parseIdentifier($var);
if (!$this->hasVar($name)) {
@ -4650,8 +4652,29 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseNativeStaticPropertyFetch(Expr\StaticPropertyFetch $expr): string|bool
{
$class = null;
$nativeProp = $this->findNativeStaticProperty($expr, $class);
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')) {
$classPtr = $this->getClassEntryPtr($class);
return Symbol::getStaticProperty() . '(' . $classPtr . ', ' . $nativeProp . ')';
@ -5373,6 +5396,11 @@ class CompilerBase extends \PhpAot\Core\Translator
foreach ($this->context->objectProps as $name => $info) {
$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;
}

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

Loading…
Cancel
Save