[php-src] PR #23945: ext/spl: Share arrays copy-on-write in ArrayObject and ArrayIterator
| From: | nicolas-grekas | Date: | Sun, 27 Sep 2026 15:02:56 +0000 |
| Subject: | [php-src] PR #23945: ext/spl: Share arrays copy-on-write in ArrayObject and ArrayIterator | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-38856@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/23945
Author: nicolas-grekas
ArrayObject and ArrayIterator duplicate the array they're given as soon as its refcount is
above 1. That's the common case for
return new \ArrayIterator($this->items); in
getIterator(), so every foreach over such a collection copies it. getArrayCopy(), exchangeArray(),
clone and (array) duplicate the storage too.
This shares these arrays instead and separates the storage before any write, as the TODOs in
spl_array.c suggested. Arrays holding references are still duplicated since zend_array_dup() unwraps
references with refcount 1, and so are immutable arrays since they can't hold iterators. When
the storage is separated during iteration, iterators move to the copy of their position that
zend_array_dup() registers on the new table.
Instructions per operation (callgrind, 100 elements, same with opcache): getIterator() + foreach
-11%, new ArrayObject() -78%, getArrayCopy() -89%, clone -84%, exchangeArray() -88%, (array) -41%,
foreach over an ArrayObject -0.4%. Walking Twig's node trees with foreach: -3%, with
getIterator()->getArrayCopy(): -8%.
This also fixes GH-17935, the array-backed cases of #22921, and a use-after-free when an error
handler calls exchangeArray() while an element is being created.
It overlaps with #20725, which fixes GH-17935 by separating at the write sites, and conflicts with
#22921, which separates the storage on each access and would undo the sharing. /cc
@alexandre-daubois @iliaal