Skip to content

Commit 397d4c2

Browse files
authored
Fix GH-13193: Significant performance degradation in 'foreach' starting from PHP 8.2.13 (caused by garbage collection) (#13265)
* Fix GH-13193: Significant performance degradation in 'foreach' starting from PHP 8.2.13 (caused by garbage collection) * Don't run zend_gc_remove_root_tmpvars() if GC is not active or GC buffer is empty
1 parent f26dd13 commit 397d4c2

File tree

1 file changed

+38
-3
lines changed

1 file changed

+38
-3
lines changed

Zend/zend_gc.c

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1465,14 +1465,19 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe
14651465
}
14661466

14671467
static void zend_get_gc_buffer_release(void);
1468-
static void zend_gc_root_tmpvars(void);
1468+
static void zend_gc_check_root_tmpvars(void);
1469+
static void zend_gc_remove_root_tmpvars(void);
14691470

14701471
ZEND_API int zend_gc_collect_cycles(void)
14711472
{
14721473
int total_count = 0;
14731474
bool should_rerun_gc = 0;
14741475
bool did_rerun_gc = 0;
14751476

1477+
if (GC_G(num_roots) && GC_G(gc_active)) {
1478+
zend_gc_remove_root_tmpvars();
1479+
}
1480+
14761481
rerun_gc:
14771482
if (GC_G(num_roots)) {
14781483
int count;
@@ -1669,7 +1674,7 @@ ZEND_API int zend_gc_collect_cycles(void)
16691674

16701675
finish:
16711676
zend_get_gc_buffer_release();
1672-
zend_gc_root_tmpvars();
1677+
zend_gc_check_root_tmpvars();
16731678
return total_count;
16741679
}
16751680

@@ -1707,7 +1712,7 @@ static void zend_get_gc_buffer_release(void) {
17071712
* cycles. However, there are some rare exceptions where this is possible, in which case we rely
17081713
* on the producing code to root the value. If a GC run occurs between the rooting and consumption
17091714
* of the value, we would end up leaking it. To avoid this, root all live TMPVAR values here. */
1710-
static void zend_gc_root_tmpvars(void) {
1715+
static void zend_gc_check_root_tmpvars(void) {
17111716
zend_execute_data *ex = EG(current_execute_data);
17121717
for (; ex; ex = ex->prev_execute_data) {
17131718
zend_function *func = ex->func;
@@ -1737,6 +1742,36 @@ static void zend_gc_root_tmpvars(void) {
17371742
}
17381743
}
17391744

1745+
static void zend_gc_remove_root_tmpvars(void) {
1746+
zend_execute_data *ex = EG(current_execute_data);
1747+
for (; ex; ex = ex->prev_execute_data) {
1748+
zend_function *func = ex->func;
1749+
if (!func || !ZEND_USER_CODE(func->type)) {
1750+
continue;
1751+
}
1752+
1753+
uint32_t op_num = ex->opline - ex->func->op_array.opcodes;
1754+
for (uint32_t i = 0; i < func->op_array.last_live_range; i++) {
1755+
const zend_live_range *range = &func->op_array.live_range[i];
1756+
if (range->start > op_num) {
1757+
break;
1758+
}
1759+
if (range->end <= op_num) {
1760+
continue;
1761+
}
1762+
1763+
uint32_t kind = range->var & ZEND_LIVE_MASK;
1764+
if (kind == ZEND_LIVE_TMPVAR || kind == ZEND_LIVE_LOOP) {
1765+
uint32_t var_num = range->var & ~ZEND_LIVE_MASK;
1766+
zval *var = ZEND_CALL_VAR(ex, var_num);
1767+
if (Z_REFCOUNTED_P(var)) {
1768+
GC_REMOVE_FROM_BUFFER(Z_COUNTED_P(var));
1769+
}
1770+
}
1771+
}
1772+
}
1773+
}
1774+
17401775
#ifdef ZTS
17411776
size_t zend_gc_globals_size(void)
17421777
{

0 commit comments

Comments
 (0)