Skip to content

Commit 025e16c

Browse files
authored
zend_compile: Optimize arguments for ZEND_NEW (php#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
1 parent 09c39a8 commit 025e16c

File tree

5 files changed

+123
-15
lines changed

5 files changed

+123
-15
lines changed

UPGRADING

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ PHP 8.6 UPGRADE NOTES
103103
. `printf()` using only `%s` and `%d` will be compiled into the equivalent
104104
string interpolation, avoiding the overhead of a function call and repeatedly
105105
parsing the format string.
106+
. Arguments are now passed more efficiently to known constructors (e.g. when
107+
using new self()).
106108

107109
- JSON:
108110
. Improve performance of encoding arrays and objects.

Zend/zend_compile.c

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5376,17 +5376,27 @@ static bool zend_is_constructor(zend_string *name) /* {{{ */
53765376
}
53775377
/* }}} */
53785378

5379-
static zend_function *zend_get_compatible_func_or_null(zend_class_entry *ce, zend_string *lcname) /* {{{ */
5379+
static bool is_func_accessible(const zend_function *fbc)
53805380
{
5381-
zend_function *fbc = zend_hash_find_ptr(&ce->function_table, lcname);
5382-
if (!fbc || (fbc->common.fn_flags & ZEND_ACC_PUBLIC) || ce == CG(active_class_entry)) {
5383-
return fbc;
5381+
if ((fbc->common.fn_flags & ZEND_ACC_PUBLIC) || fbc->common.scope == CG(active_class_entry)) {
5382+
return true;
53845383
}
53855384

53865385
if (!(fbc->common.fn_flags & ZEND_ACC_PRIVATE)
53875386
&& (fbc->common.scope->ce_flags & ZEND_ACC_LINKED)
53885387
&& (!CG(active_class_entry) || (CG(active_class_entry)->ce_flags & ZEND_ACC_LINKED))
53895388
&& zend_check_protected(zend_get_function_root_class(fbc), CG(active_class_entry))) {
5389+
return true;
5390+
}
5391+
5392+
return false;
5393+
}
5394+
5395+
static zend_function *zend_get_compatible_func_or_null(zend_class_entry *ce, zend_string *lcname) /* {{{ */
5396+
{
5397+
zend_function *fbc = zend_hash_find_ptr(&ce->function_table, lcname);
5398+
5399+
if (!fbc || is_func_accessible(fbc)) {
53905400
return fbc;
53915401
}
53925402

@@ -5489,16 +5499,40 @@ static void zend_compile_new(znode *result, zend_ast *ast) /* {{{ */
54895499

54905500
opline = zend_emit_op(result, ZEND_NEW, NULL, NULL);
54915501

5492-
if (class_node.op_type == IS_CONST) {
5493-
opline->op1_type = IS_CONST;
5494-
opline->op1.constant = zend_add_class_name_literal(
5495-
Z_STR(class_node.u.constant));
5502+
zend_set_class_name_op1(opline, &class_node);
5503+
5504+
if (opline->op1_type == IS_CONST) {
54965505
opline->op2.num = zend_alloc_cache_slot();
5497-
} else {
5498-
SET_NODE(opline->op1, &class_node);
54995506
}
55005507

5501-
zend_compile_call_common(&ctor_result, args_ast, NULL, ast->lineno);
5508+
zend_class_entry *ce = NULL;
5509+
if (opline->op1_type == IS_CONST) {
5510+
zend_string *lcname = Z_STR_P(CT_CONSTANT(opline->op1) + 1);
5511+
ce = zend_hash_find_ptr(CG(class_table), lcname);
5512+
if (ce) {
5513+
if (zend_compile_ignore_class(ce, CG(active_op_array)->filename)) {
5514+
ce = NULL;
5515+
}
5516+
} else if (CG(active_class_entry)
5517+
&& zend_string_equals_ci(CG(active_class_entry)->name, lcname)) {
5518+
ce = CG(active_class_entry);
5519+
}
5520+
} else if (opline->op1_type == IS_UNUSED
5521+
&& (opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF
5522+
&& zend_is_scope_known()) {
5523+
ce = CG(active_class_entry);
5524+
}
5525+
5526+
5527+
zend_function *fbc = NULL;
5528+
if (ce
5529+
&& ce->default_object_handlers->get_constructor == zend_std_get_constructor
5530+
&& ce->constructor
5531+
&& is_func_accessible(ce->constructor)) {
5532+
fbc = ce->constructor;
5533+
}
5534+
5535+
zend_compile_call_common(&ctor_result, args_ast, fbc, ast->lineno);
55025536
zend_do_free(&ctor_result);
55035537
}
55045538
/* }}} */
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
--TEST--
2+
Named Parameters are optimized for known constructors
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.opt_debug_level=0x20000
8+
--SKIPIF--
9+
<?php
10+
if(substr(PHP_OS, 0, 3) == 'WIN') die("skip on windows: Internal classes cannot be optimized");
11+
?>
12+
--EXTENSIONS--
13+
opcache
14+
--FILE--
15+
<?php
16+
17+
final class MyClass
18+
{
19+
private function __construct(
20+
private \Random\Engine $foo,
21+
private int $bar = 0,
22+
) {}
23+
24+
public static function new(int $bar): self
25+
{
26+
$engine = new \Random\Engine\Xoshiro256StarStar(seed: 123);
27+
return new self(foo: $engine, bar: $bar);
28+
}
29+
}
30+
31+
MyClass::new(bar: 1);
32+
33+
?>
34+
--EXPECTF--
35+
$_main:
36+
; (lines=4, args=0, vars=0, tmps=0)
37+
; (after optimizer)
38+
; %s
39+
0000 INIT_STATIC_METHOD_CALL 1 string("MyClass") string("new")
40+
0001 SEND_VAL int(1) 1
41+
0002 DO_UCALL
42+
0003 RETURN int(1)
43+
44+
MyClass::__construct:
45+
; (lines=7, args=2, vars=2, tmps=0)
46+
; (after optimizer)
47+
; %s
48+
0000 CV0($foo) = RECV 1
49+
0001 CV1($bar) = RECV_INIT 2 int(0)
50+
0002 ASSIGN_OBJ THIS string("foo")
51+
0003 OP_DATA CV0($foo)
52+
0004 ASSIGN_OBJ THIS string("bar")
53+
0005 OP_DATA CV1($bar)
54+
0006 RETURN null
55+
56+
MyClass::new:
57+
; (lines=10, args=1, vars=2, tmps=1)
58+
; (after optimizer)
59+
; %s
60+
0000 CV0($bar) = RECV 1
61+
0001 V2 = NEW 1 string("Random\\Engine\\Xoshiro256StarStar")
62+
0002 SEND_VAL int(123) 1
63+
0003 DO_FCALL
64+
0004 CV1($engine) = QM_ASSIGN V2
65+
0005 V2 = NEW 2 (self) (exception)
66+
0006 SEND_VAR CV1($engine) 1
67+
0007 SEND_VAR CV0($bar) 2
68+
0008 DO_FCALL
69+
0009 RETURN V2
70+
LIVE RANGES:
71+
2: 0002 - 0004 (new)
72+
2: 0006 - 0009 (new)

ext/opcache/tests/opt/gh18107_1.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ $_main:
3030
0000 T1 = ISSET_ISEMPTY_CV (isset) CV0($badvar)
3131
0001 JMPNZ T1 0006
3232
0002 V3 = NEW 1 string("Exception")
33-
0003 SEND_VAL_EX string("Should happen") 1
33+
0003 SEND_VAL%S string("Should happen") 1
3434
0004 DO_FCALL
3535
0005 THROW V3
3636
0006 JMP 0006
3737
0007 V6 = NEW 1 string("Exception")
38-
0008 SEND_VAL_EX string("Should not happen") 1
38+
0008 SEND_VAL%S string("Should not happen") 1
3939
0009 DO_FCALL
4040
0010 THROW V6
4141
0011 FAST_RET T5

ext/opcache/tests/opt/gh18107_2.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@ $_main:
3333
0000 T2 = ISSET_ISEMPTY_CV (isset) CV0($badvar)
3434
0001 JMPNZ T2 0008
3535
0002 V4 = NEW 1 string("Exception")
36-
0003 SEND_VAL_EX string("Should happen") 1
36+
0003 SEND_VAL%S string("Should happen") 1
3737
0004 DO_FCALL
3838
0005 THROW V4
3939
0006 CV1($e) = CATCH string("Throwable")
4040
0007 ECHO string("foo")
4141
0008 T6 = FAST_CALL 0010
4242
0009 JMP 0015
4343
0010 V7 = NEW 1 string("Exception")
44-
0011 SEND_VAL_EX string("Should not happen") 1
44+
0011 SEND_VAL%S string("Should not happen") 1
4545
0012 DO_FCALL
4646
0013 THROW V7
4747
0014 FAST_RET T6

0 commit comments

Comments
 (0)