1
0
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:
Tim Düsterhus
2025-10-23 09:28:19 +02:00
committed by GitHub
parent 09c39a8fd9
commit 025e16cb54
5 changed files with 123 additions and 15 deletions

View File

@@ -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.

View File

@@ -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);
}
/* }}} */

View 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)

View File

@@ -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

View File

@@ -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