mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
zend_compile: Optimize arguments for ZEND_NEW (#20259)
* zend_compile: Add `is_func_accessible()` helper
* zend_compile: Use `zend_set_class_name_op1()` in `zend_compile_new()`
* zend_compile: Optimize arguments for ZEND_NEW
Apply the optimization for static method calls to `new` calls, since `new` is
effectively a static method for all intents and purposes.
For:
<?php
final class MyClass
{
private function __construct(
private \Random\Engine $foo,
private int $bar = 0,
) {}
public static function new(int $bar): self
{
$engine = new \Random\Engine\Xoshiro256StarStar(seed: 123);
return new self(foo: $engine, bar: $bar);
}
}
for ($i = 0; $i < 3_000_000; $i++) {
MyClass::new($i);
}
This is ~1.13 faster for a gcc 13.3 release build on a Intel(R) Core(TM)
i7-1365U.
Benchmark 1: /tmp/bench/php.old /tmp/bench/test6.php
Time (mean ± σ): 409.5 ms ± 1.9 ms [User: 406.6 ms, System: 2.2 ms]
Range (min … max): 407.4 ms … 414.0 ms 10 runs
Benchmark 2: /tmp/bench/php.new /tmp/bench/test6.php
Time (mean ± σ): 360.9 ms ± 1.7 ms [User: 358.5 ms, System: 2.2 ms]
Range (min … max): 359.2 ms … 365.0 ms 10 runs
Summary
/tmp/bench/php.new /tmp/bench/test6.php ran
1.13 ± 0.01 times faster than /tmp/bench/php.old /tmp/bench/test6.php
This commit is contained in:
@@ -103,6 +103,8 @@ PHP 8.6 UPGRADE NOTES
|
||||
. `printf()` using only `%s` and `%d` will be compiled into the equivalent
|
||||
string interpolation, avoiding the overhead of a function call and repeatedly
|
||||
parsing the format string.
|
||||
. Arguments are now passed more efficiently to known constructors (e.g. when
|
||||
using new self()).
|
||||
|
||||
- JSON:
|
||||
. Improve performance of encoding arrays and objects.
|
||||
|
||||
@@ -5376,17 +5376,27 @@ static bool zend_is_constructor(zend_string *name) /* {{{ */
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static zend_function *zend_get_compatible_func_or_null(zend_class_entry *ce, zend_string *lcname) /* {{{ */
|
||||
static bool is_func_accessible(const zend_function *fbc)
|
||||
{
|
||||
zend_function *fbc = zend_hash_find_ptr(&ce->function_table, lcname);
|
||||
if (!fbc || (fbc->common.fn_flags & ZEND_ACC_PUBLIC) || ce == CG(active_class_entry)) {
|
||||
return fbc;
|
||||
if ((fbc->common.fn_flags & ZEND_ACC_PUBLIC) || fbc->common.scope == CG(active_class_entry)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(fbc->common.fn_flags & ZEND_ACC_PRIVATE)
|
||||
&& (fbc->common.scope->ce_flags & ZEND_ACC_LINKED)
|
||||
&& (!CG(active_class_entry) || (CG(active_class_entry)->ce_flags & ZEND_ACC_LINKED))
|
||||
&& zend_check_protected(zend_get_function_root_class(fbc), CG(active_class_entry))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static zend_function *zend_get_compatible_func_or_null(zend_class_entry *ce, zend_string *lcname) /* {{{ */
|
||||
{
|
||||
zend_function *fbc = zend_hash_find_ptr(&ce->function_table, lcname);
|
||||
|
||||
if (!fbc || is_func_accessible(fbc)) {
|
||||
return fbc;
|
||||
}
|
||||
|
||||
@@ -5489,16 +5499,40 @@ static void zend_compile_new(znode *result, zend_ast *ast) /* {{{ */
|
||||
|
||||
opline = zend_emit_op(result, ZEND_NEW, NULL, NULL);
|
||||
|
||||
if (class_node.op_type == IS_CONST) {
|
||||
opline->op1_type = IS_CONST;
|
||||
opline->op1.constant = zend_add_class_name_literal(
|
||||
Z_STR(class_node.u.constant));
|
||||
zend_set_class_name_op1(opline, &class_node);
|
||||
|
||||
if (opline->op1_type == IS_CONST) {
|
||||
opline->op2.num = zend_alloc_cache_slot();
|
||||
} else {
|
||||
SET_NODE(opline->op1, &class_node);
|
||||
}
|
||||
|
||||
zend_compile_call_common(&ctor_result, args_ast, NULL, ast->lineno);
|
||||
zend_class_entry *ce = NULL;
|
||||
if (opline->op1_type == IS_CONST) {
|
||||
zend_string *lcname = Z_STR_P(CT_CONSTANT(opline->op1) + 1);
|
||||
ce = zend_hash_find_ptr(CG(class_table), lcname);
|
||||
if (ce) {
|
||||
if (zend_compile_ignore_class(ce, CG(active_op_array)->filename)) {
|
||||
ce = NULL;
|
||||
}
|
||||
} else if (CG(active_class_entry)
|
||||
&& zend_string_equals_ci(CG(active_class_entry)->name, lcname)) {
|
||||
ce = CG(active_class_entry);
|
||||
}
|
||||
} else if (opline->op1_type == IS_UNUSED
|
||||
&& (opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF
|
||||
&& zend_is_scope_known()) {
|
||||
ce = CG(active_class_entry);
|
||||
}
|
||||
|
||||
|
||||
zend_function *fbc = NULL;
|
||||
if (ce
|
||||
&& ce->default_object_handlers->get_constructor == zend_std_get_constructor
|
||||
&& ce->constructor
|
||||
&& is_func_accessible(ce->constructor)) {
|
||||
fbc = ce->constructor;
|
||||
}
|
||||
|
||||
zend_compile_call_common(&ctor_result, args_ast, fbc, ast->lineno);
|
||||
zend_do_free(&ctor_result);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
72
ext/opcache/tests/named_parameter_new.phpt
Normal file
72
ext/opcache/tests/named_parameter_new.phpt
Normal file
@@ -0,0 +1,72 @@
|
||||
--TEST--
|
||||
Named Parameters are optimized for known constructors
|
||||
--INI--
|
||||
opcache.enable=1
|
||||
opcache.enable_cli=1
|
||||
opcache.optimization_level=-1
|
||||
opcache.opt_debug_level=0x20000
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if(substr(PHP_OS, 0, 3) == 'WIN') die("skip on windows: Internal classes cannot be optimized");
|
||||
?>
|
||||
--EXTENSIONS--
|
||||
opcache
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
final class MyClass
|
||||
{
|
||||
private function __construct(
|
||||
private \Random\Engine $foo,
|
||||
private int $bar = 0,
|
||||
) {}
|
||||
|
||||
public static function new(int $bar): self
|
||||
{
|
||||
$engine = new \Random\Engine\Xoshiro256StarStar(seed: 123);
|
||||
return new self(foo: $engine, bar: $bar);
|
||||
}
|
||||
}
|
||||
|
||||
MyClass::new(bar: 1);
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
$_main:
|
||||
; (lines=4, args=0, vars=0, tmps=0)
|
||||
; (after optimizer)
|
||||
; %s
|
||||
0000 INIT_STATIC_METHOD_CALL 1 string("MyClass") string("new")
|
||||
0001 SEND_VAL int(1) 1
|
||||
0002 DO_UCALL
|
||||
0003 RETURN int(1)
|
||||
|
||||
MyClass::__construct:
|
||||
; (lines=7, args=2, vars=2, tmps=0)
|
||||
; (after optimizer)
|
||||
; %s
|
||||
0000 CV0($foo) = RECV 1
|
||||
0001 CV1($bar) = RECV_INIT 2 int(0)
|
||||
0002 ASSIGN_OBJ THIS string("foo")
|
||||
0003 OP_DATA CV0($foo)
|
||||
0004 ASSIGN_OBJ THIS string("bar")
|
||||
0005 OP_DATA CV1($bar)
|
||||
0006 RETURN null
|
||||
|
||||
MyClass::new:
|
||||
; (lines=10, args=1, vars=2, tmps=1)
|
||||
; (after optimizer)
|
||||
; %s
|
||||
0000 CV0($bar) = RECV 1
|
||||
0001 V2 = NEW 1 string("Random\\Engine\\Xoshiro256StarStar")
|
||||
0002 SEND_VAL int(123) 1
|
||||
0003 DO_FCALL
|
||||
0004 CV1($engine) = QM_ASSIGN V2
|
||||
0005 V2 = NEW 2 (self) (exception)
|
||||
0006 SEND_VAR CV1($engine) 1
|
||||
0007 SEND_VAR CV0($bar) 2
|
||||
0008 DO_FCALL
|
||||
0009 RETURN V2
|
||||
LIVE RANGES:
|
||||
2: 0002 - 0004 (new)
|
||||
2: 0006 - 0009 (new)
|
||||
@@ -30,12 +30,12 @@ $_main:
|
||||
0000 T1 = ISSET_ISEMPTY_CV (isset) CV0($badvar)
|
||||
0001 JMPNZ T1 0006
|
||||
0002 V3 = NEW 1 string("Exception")
|
||||
0003 SEND_VAL_EX string("Should happen") 1
|
||||
0003 SEND_VAL%S string("Should happen") 1
|
||||
0004 DO_FCALL
|
||||
0005 THROW V3
|
||||
0006 JMP 0006
|
||||
0007 V6 = NEW 1 string("Exception")
|
||||
0008 SEND_VAL_EX string("Should not happen") 1
|
||||
0008 SEND_VAL%S string("Should not happen") 1
|
||||
0009 DO_FCALL
|
||||
0010 THROW V6
|
||||
0011 FAST_RET T5
|
||||
|
||||
@@ -33,7 +33,7 @@ $_main:
|
||||
0000 T2 = ISSET_ISEMPTY_CV (isset) CV0($badvar)
|
||||
0001 JMPNZ T2 0008
|
||||
0002 V4 = NEW 1 string("Exception")
|
||||
0003 SEND_VAL_EX string("Should happen") 1
|
||||
0003 SEND_VAL%S string("Should happen") 1
|
||||
0004 DO_FCALL
|
||||
0005 THROW V4
|
||||
0006 CV1($e) = CATCH string("Throwable")
|
||||
@@ -41,7 +41,7 @@ $_main:
|
||||
0008 T6 = FAST_CALL 0010
|
||||
0009 JMP 0015
|
||||
0010 V7 = NEW 1 string("Exception")
|
||||
0011 SEND_VAL_EX string("Should not happen") 1
|
||||
0011 SEND_VAL%S string("Should not happen") 1
|
||||
0012 DO_FCALL
|
||||
0013 THROW V7
|
||||
0014 FAST_RET T6
|
||||
|
||||
Reference in New Issue
Block a user