-
Notifications
You must be signed in to change notification settings - Fork 7.8k
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
Significant performance degradation in 'foreach' starting from PHP 8.2.13 (caused by garbage collection) #13193
Comments
I confirm this drop in performance under Linux. 0.3 sec to 1.1s Warning I notice that it seems extremely difficult to understand the usefulness of this test. $array = [0, 1];
foreach($array as &$v) {
$array[0] =& $array;
$b = 1;
$array =& $b;
gc_collect_cycles();
break;
}
var_dump(gc_collect_cycles()); Tip If we create a reference above the loop, the memory leak no longer exists $a = [0, 1];
$array = &$a;
foreach($array as &$v) {
//
} |
Yop @dstogov I looked this report to get my hands of php. As I said above, I tried to create a temporary reference to feed the "foreach" loop. I was able to see from the @sboikov1983 test that from 1.1s I fell back to 0.3s #if 1
znode tmp_result;
opnum_reset = get_next_op_number();
zend_emit_op(&tmp_result, ZEND_MAKE_REF, &expr_node, NULL);
opnum_reset = get_next_op_number();
opline = zend_emit_op(&reset_node, by_ref ? ZEND_FE_RESET_RW : ZEND_FE_RESET_R, &tmp_result, NULL);
#else
opnum_reset = get_next_op_number();
opline = zend_emit_op(&reset_node, by_ref ? ZEND_FE_RESET_RW : ZEND_FE_RESET_R, &expr_node, NULL);
#endif Not having a global vision of how PHP works. I don't know what this modification is worth. Is this the right way to do it? |
The degradation occurs because abe3673 adds a big array into GC buffer, and then it's iteratively checked by GC. The fix looks right, and I don't see a simple way to fix this degradation. I'll think a bit more... |
The issue was closed by mistake |
I thought we may reduce the degradation by limiting effect if the old fix only to references. <?php
function foo() {
$a = [];
$a[0] = 1;
$a[1] = &$a;
return $a;
}
foreach(foo() as $v) { // foo() returns a circular array
var_dump(gc_collect_cycles()); // GC can't free the array because it's still iterated
break;
}
var_dump(gc_collect_cycles()); // GC is able to detect and free the array only if it was added to garbage candidates buffer after the first GC above (see the old fix)
?> @iluuu1994 @nielsdos may be you have any ideas? |
I don't really have a better idea. The only thing I think about is changing the VM instead of GC, like so: https://gist.github.com/nielsdos/80c7f19ab12a4ae6678dc403246435ba To be honest I am not very familiar with the GC and its nuances, I did not read much code of it yet. So probably I am missing something why my idea is wrong. |
@nielsdod the current solution adds iterated variable into GC buffer only after GC launched from inside a |
Okay I understand, thanks for pointing that out. |
@SVGAnimate we can't disable array modification and this won't fix the problem anyway (see my last example above). The problem that on each I think, the problem may be reduced in general, if before the main GC collector algorithm we will remove from GC buffer all elements reachable from the VM roots (global and static variables, CVs and alive temporary variables of stack frames, etc...) I'm not sure when I get time to check this idea. |
I had thought of something completely different to maintain PHP performance. What do you think about not creating an internal reference(using ZEND_FE_RESET_R instead of ZEND_FE_RESET_RW )? This involves adding a context to the zend_compile_stmt(stmt_ast, context_alias) It can also be used for list() PS: Backward compatibility bugs will not be supported. There is therefore little chance that this will be accepted... |
@sboikov1983 It's normal that PHP is slower, now it fixes memory leaks. We can not have everything. |
…rting from PHP 8.2.13 (caused by garbage collection)
I'm slightly confused, did this release? I haven't seen it in any notes, and when I run the test script on |
Yes this was part of 8.2.16 for example. I suppose the time you got was with OP's script? |
Yep! |
We are also seeing this in a "real world" example, in our phpunit test run of ~20k tests, a small slice of our tests explicitly call gc_collect_cycles(), we have seen those tests get much slower from 7.4 -> 8.2.19, but they are pure "unit tests", and the slowness comes from the gc_collect_cycles() call. I am OK to reopen this issue, or leave it, up to y'all, I think the larger problem that led us down the path of explicitly doing this was we used to need this due to the old GC's (PHP 5.x) behavior of hitting 10k root nodes and thrashing |
@SpencerMalone I can reproduce this, it seems that there is an extra check that causes the @dstogov I am confused by the Lines 1920 to 1922 in ead3698
As far as I understand, diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c
index e7d9c8ef292..935ce06f46c 100644
--- a/Zend/zend_gc.c
+++ b/Zend/zend_gc.c
@@ -1474,7 +1474,7 @@ ZEND_API int zend_gc_collect_cycles(void)
bool should_rerun_gc = 0;
bool did_rerun_gc = 0;
- if (GC_G(num_roots) && GC_G(gc_active)) {
+ if (GC_G(num_roots) && !GC_G(gc_active)) {
zend_gc_remove_root_tmpvars();
}
I may be wrong though. |
@nielsdos I can't remember why did I add that |
Description
We noticed significant performance degradation in our application after migration to PHP 8.2.13 (and PHP 8.3.0 has the same issue). Our application performs a lot of computations and in some cases execution time increased from 2 to 6 hours.
Here is a simple script demonstrating the problem (run as 'php -dmemory_limit=-1 perf_test.php'):
Here is timing for PHP 8.2.12 and PHP 8.2.13 when GC is enabled and disabled:
For 8.2.12 enabling GC doesn't have major impact on performance, but for 8.2.13 enabling GC increases time from 0.1 sec to 8 seconds (the same for 8.3.0). This is just a simple script to demonstrate the issue, in real application impact on performance is dramatic.
As I understand reason of additional GC overhead is this commit. I understand that it fixes potential memory leak, but perphaps there is a way to optimize it, so it won't cause such dramatic performance degradation?
I also understand that it's possible to selectively disable/enable GC in the application, or somehow get rid of 'foreach' loops. But since our application has thousands of such loops, changing the code would be very hard (and potentially the same issue can be in the 3rd party libraries which we can't change).
PHP Version
PHP 8.3
Operating System
Ubuntu 20.04, Windows 11
The text was updated successfully, but these errors were encountered: