Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Omit FETCH_THIS in closures #14181

Closed
wants to merge 1 commit into from

Conversation

iluuu1994
Copy link
Member

@iluuu1994 iluuu1994 commented May 8, 2024

Non-static closures declared in non-static methods are guaranteed to have $this. The existing comment highlights this, but fails to handle it correctly.

Zend/zend_compile.c Outdated Show resolved Hide resolved
@mvorisek
Copy link
Contributor

mvorisek commented May 9, 2024

This PR seems to be related with #10782, can it fix it?

@iluuu1994
Copy link
Member Author

@mvorisek I don't think this will fix the mentioned problem. It is related to run_time_cache per closure, this only slightly tweaks the opcodes of the closure.

@iluuu1994 iluuu1994 force-pushed the omit-fetch-this-in-closures branch from 024095f to e7830f3 Compare May 9, 2024 18:46
@iluuu1994 iluuu1994 marked this pull request as ready for review May 9, 2024 20:20
@iluuu1994 iluuu1994 requested a review from dstogov as a code owner May 9, 2024 20:20
@staabm
Copy link
Contributor

staabm commented May 10, 2024

Does this change improve closure performance?

@iluuu1994
Copy link
Member Author

It seems so.

class C {
    public function test() {
        $c = function () {
            for ($i = 0; $i < 100000000; $i++) {
                $this->foo();
            }
        };
        $c();
    }

    private function foo() {}
}

$c = new C();
$c->test();

Before:

~/Developer/php-src @d2a9edfe ?1 ❯ hyperfine 'taskset --cpu-list 0 php-dev -d opcache.enable_cli=1 test.php'                                                 8s
Benchmark 1: taskset --cpu-list 0 php-dev -d opcache.enable_cli=1 test.php
  Time (mean ± σ):     795.8 ms ±   6.9 ms    [User: 790.8 ms, System: 2.7 ms]
  Range (min … max):   779.1 ms … 805.2 ms    10 runs

After:

~/Developer/php-src omit-fetch-this-in-closures ?1 ❯ hyperfine 'taskset --cpu-list 0 php-dev -d opcache.enable_cli=1 test.php'                               4s
Benchmark 1: taskset --cpu-list 0 php-dev -d opcache.enable_cli=1 test.php
  Time (mean ± σ):     570.1 ms ±  15.6 ms    [User: 565.7 ms, System: 2.8 ms]
  Range (min … max):   557.7 ms … 609.9 ms    10 runs

But this is highly synthetic, so take it with a grain of salt. I'm not sure whether it improves real-life performance.

@dstogov
Copy link
Member

dstogov commented May 13, 2024

I like the patch, and in general everything looks right.
I tested it with PHP-Parser benchmark and it shows slight improvement with function JIT and without JIT.
However it also shows degradation with tracing JIT.
For some reason, PHP starts to generate bigger and slower code.

Can you investigate this please. (I suppose, JIT have some CLOSURE specific optimization that stopped work after your patch).

@dstogov
Copy link
Member

dstogov commented May 13, 2024

This demonstrates the worse JIT code generated for FETCH_DIM_R.

<?php
class Foo {
	public $a = 42;
	public function test() {
		return function() {
			return $this->a;
		};
	}
}
$obj = new Foo();
$fn = $obj->test();
for ($i = 0; $i < 100; $i++) {
	$fn();
}
?>
sapi/cli/php -d opcache.jit_debug=0x1ff201 fetch_this.php 2>err1.log
sapi/cli/php.patched -d opcache.jit_debug=0x1ff201 fetch_this.php 2>err2.log
diff -u err1.log err2.log

The degradation is caused by call to zend_jit_fetch_obj(). Without the patch it gets trace_ce recorded by the tracer and generates class guards and optimized code. With the patch trace_ce is NULL and the slow code is generated.

It's possible that we have similar degradation for other related instructions. (ASSIGN_OBJ, INIT_METHOD_CALL, etc)

It looks like the following extension to the tracer fixes all degradation. May be it should record $this class only for closures. (I'm not sure).

diff --git a/ext/opcache/jit/zend_jit_vm_helpers.c b/ext/opcache/jit/zend_jit_vm_helpers.c
index 7df2cd5b902..4d055ac2ece 100644
--- a/ext/opcache/jit/zend_jit_vm_helpers.c
+++ b/ext/opcache/jit/zend_jit_vm_helpers.c
@@ -692,6 +692,25 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex,
 				}
 			}
 			op1_type |= flags;
+		} else if (opline->op1_type == IS_UNUSED
+		 && (opline->opcode == ZEND_FETCH_OBJ_R
+		  || opline->opcode == ZEND_FETCH_OBJ_W
+		  || opline->opcode == ZEND_FETCH_OBJ_RW
+		  || opline->opcode == ZEND_FETCH_OBJ_IS
+		  || opline->opcode == ZEND_FETCH_OBJ_FUNC_ARG
+		  || opline->opcode == ZEND_FETCH_OBJ_UNSET
+		  || opline->opcode == ZEND_ASSIGN_OBJ
+		  || opline->opcode == ZEND_ASSIGN_OBJ_REF
+		  || opline->opcode == ZEND_ASSIGN_OBJ_OP
+		  || opline->opcode == ZEND_PRE_INC_OBJ
+		  || opline->opcode == ZEND_PRE_DEC_OBJ
+		  || opline->opcode == ZEND_POST_INC_OBJ
+		  || opline->opcode == ZEND_POST_DEC_OBJ
+		  || opline->opcode == ZEND_ISSET_ISEMPTY_PROP_OBJ
+		  || opline->opcode == ZEND_UNSET_OBJ
+		  || opline->opcode == ZEND_INIT_METHOD_CALL
+		  || opline->opcode == ZEND_CLONE)) {
+			ce1 = Z_OBJCE(EX(This));
 		}
 		if (opline->op2_type & (IS_TMP_VAR|IS_VAR|IS_CV)
 		 && opline->opcode != ZEND_INSTANCEOF

@iluuu1994 Please finalize this.

@iluuu1994 iluuu1994 force-pushed the omit-fetch-this-in-closures branch from e7830f3 to 7135a60 Compare May 14, 2024 17:36
Copy link
Member

@dstogov dstogov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks Fine!

Non-static closures are guaranteed to have $this. The existing comment
highlights this, but fails to handle it correctly.
@iluuu1994 iluuu1994 force-pushed the omit-fetch-this-in-closures branch from 81df7c5 to 3b8b9f0 Compare May 15, 2024 18:50
@iluuu1994 iluuu1994 closed this in 600d591 May 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants