From 9d14b9b5b1e72ae216be91ef73d34ae2bb7aa2c0 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 27 Sep 2026 17:33:15 +0200 Subject: [PATCH] Speed up zend_hash_iterator_add() when iterators are nested zend_hash_iterator_add() scanned EG(ht_iterators) from slot 0 on every call, so each nested foreach by reference, foreach over object properties, ArrayIterator or array_walk() paid one probe per iterator already alive. EG(ht_iterators_free_hint) tracks that all slots below it are in use: zend_hash_iterator_add() starts looking from there and zend_hash_iterator_del() lowers it. The returned slot is still the lowest free one, so slot indexes and EG(ht_iterators_used) are the same as before. --- Zend/zend_execute_API.c | 1 + Zend/zend_globals.h | 1 + Zend/zend_hash.c | 17 +++++++++++------ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 94273a4bc59f..b133b5dd83f6 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -182,6 +182,7 @@ void init_executor(void) /* {{{ */ EG(ht_iterators_count) = sizeof(EG(ht_iterators_slots)) / sizeof(HashTableIterator); EG(ht_iterators_used) = 0; + EG(ht_iterators_free_hint) = 0; EG(ht_iterators) = EG(ht_iterators_slots); memset(EG(ht_iterators), 0, sizeof(EG(ht_iterators_slots))); diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index bf93bf0013a9..77be5433ee48 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -282,6 +282,7 @@ struct _zend_executor_globals { uint32_t ht_iterators_count; /* number of allocated slots */ uint32_t ht_iterators_used; /* number of used slots */ + uint32_t ht_iterators_free_hint; /* no free slot below this one */ HashTableIterator *ht_iterators; HashTableIterator ht_iterators_slots[16]; diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index 0b2ed1c8cbd3..c6ea32d111f5 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -689,25 +689,26 @@ static void zend_hash_remove_iterator_copies(uint32_t idx) { ZEND_API uint32_t ZEND_FASTCALL zend_hash_iterator_add(HashTable *ht, HashPosition pos) { - HashTableIterator *iter = EG(ht_iterators); - HashTableIterator *end = iter + EG(ht_iterators_count); - uint32_t idx; + /* Slots below the hint are in use, start looking for a free one from there */ + uint32_t idx = EG(ht_iterators_free_hint); + HashTableIterator *iter; if (EXPECTED(!HT_ITERATORS_OVERFLOW(ht))) { HT_INC_ITERATORS_COUNT(ht); } - while (iter != end) { + while (idx < EG(ht_iterators_count)) { + iter = EG(ht_iterators) + idx; if (iter->ht == NULL) { iter->ht = ht; iter->pos = pos; - idx = iter - EG(ht_iterators); iter->next_copy = idx; + EG(ht_iterators_free_hint) = idx + 1; if (idx + 1 > EG(ht_iterators_used)) { EG(ht_iterators_used) = idx + 1; } return idx; } - iter++; + idx++; } if (EG(ht_iterators) == EG(ht_iterators_slots)) { EG(ht_iterators) = emalloc(sizeof(HashTableIterator) * (EG(ht_iterators_count) + 8)); @@ -723,6 +724,7 @@ ZEND_API uint32_t ZEND_FASTCALL zend_hash_iterator_add(HashTable *ht, HashPositi idx = iter - EG(ht_iterators); iter->next_copy = idx; EG(ht_iterators_used) = idx + 1; + EG(ht_iterators_free_hint) = idx + 1; return idx; } @@ -818,6 +820,9 @@ ZEND_API void ZEND_FASTCALL zend_hash_iterator_del(uint32_t idx) HT_DEC_ITERATORS_COUNT(iter->ht); } iter->ht = NULL; + if (idx < EG(ht_iterators_free_hint)) { + EG(ht_iterators_free_hint) = idx; + } if (UNEXPECTED(iter->next_copy != idx)) { zend_hash_remove_iterator_copies(idx);