Repository navigation
Fix GH-25107: Preserve foreach iterators on array_unshift - #25111
JoaoMarcos44 wants to merge 1 commit into
Conversation
|
I think modifying ZEND_API void ZEND_FASTCALL zend_hash_iterators_advance(HashTable *ht, HashPosition step)
{
HashTableIterator *iter = EG(ht_iterators);
HashTableIterator *end = iter + EG(ht_iterators_used);
while (iter != end) {
if (iter->ht == ht) {
HashPosition pos = iter->pos;
HashPosition compacted_pos = 0;
for (HashPosition idx = 0; idx < pos && idx < ht->nNumUsed; idx++) {
zval *val = HT_IS_PACKED(ht)
? &ht->arPacked[idx]
: &ht->arData[idx].val;
if (Z_TYPE_P(val) != IS_UNDEF) {
compacted_pos++;
}
}
if (pos > ht->nNumUsed) {
compacted_pos += pos - ht->nNumUsed;
}
iter->pos = compacted_pos + step;
}
iter++;
}
} |
|
@arshidkv12 Thanks for the suggestion. I have now completed the expanded comparison. The performance advantage is not uniform: the proposed helper has substantial wins in some configurations, and substantial regressions in others. I compared the current patch, your exact helper as a replacement for the remapping in Selected medians below are microseconds per complete
Here K is the number of iterators on the target array. “Unrelated” iterators refer to other arrays but occupy the same global iterator list. N is the physical slot count before deletions. The last two rows are important counterexamples to a blanket argument against your approach. They favored the proposed helper in all nine paired rounds. Conversely, the same-position sparse case took about 16.36× as much time with the proposed helper, and the guard cannot avoid those repeated prefix scans. This fits the iterator-adjustment costs:
Repeatability of the initial result: I repeated all 34 original configurations. Among the 12 original K=8 configurations, 11 again had a higher median for the proposed helper. The remaining mixed/sparse N=100,000 case was inconclusive: its paired-ratio interquartile range crossed 1 (0.984–1.055), despite a lower marginal median. Thus the earlier “12/12” statement applies to the first run, not universally. My conclusion is now more specific: performance does not establish a universally preferable implementation. The current patch does well with larger arrays and repeated iterator positions; the proposed helper can do much better with small sparse arrays and many distinct/global iterators. I would preserve the dense fast path whichever direction is chosen. The desired semantics of the exported helper remain a separate design consideration. Scope, validation, and methodology
Benchmark preparation and analysis were AI-assisted; the builds, tests, and timings were obtained by execution. |
At least, I think it's more favorable in this specific case. However, there may be some other scenario—one that I haven't considered—in which your idea is good or even better. |
|
Note that this function is not used anywhere else. |
Fixes #25107.
Summary
Infographic
Minimal reproduction
["b","d"]["b","c","d"]Root cause
[hole, b, c, d]c2 + 1din[x, b, c, d]camong old live entries1 + 1cin[x, b, c, d]Small implementation and invariants
Regression coverage
Validation
Scope and limits