Skip to content

Commit 8fb127a

Browse files
committed
Proposal: Add final class Deque to PHP
This has lower memory usage and better performance than SplDoublyLinkedList for push/pop operations. The API is as follows: ```php /** * A double-ended queue represented internally as a circular buffer. * This has much lower memory usage than SplDoublyLinkedList or its subclasses (SplStack, SplStack), * and operations are significantly faster than SplDoublyLinkedList. * * This supports amortized constant time pushing and popping onto the front or back of the array. * * Naming is based on https://www.php.net/spldoublylinkedlist * and on array_push/pop/unshift/shift. */ final class Deque implements IteratorAggregate, Countable, JsonSerializable, ArrayAccess { /** Construct the Deque from the values of the Traversable/array, ignoring keys */ public function __construct(iterable $iterator = []) {} /** * The final version of getIterator will iterate over a copy of the Deque's values taken at the time getIterator was called. * The iteration keys will be the offsets of the copy(0, 1, ...), and the values will be the values from front to back. * * i.e. `foreach($deque as $k => $v)` and `foreach($deque->toArray() as $k => $v)` will iterate over the same elements. * * This will be done to avoid surprises in case pushFront/popFront/clear are called. * * To access the current version of the Deque without making a copy, * use `for ($i = 0; $i < count($deque); $i++) { process($deque[$i]); }`. */ public function getIterator(): InternalIterator {} /** Returns the number of elements in the Deque. */ public function count(): int {} /** Returns true if there are 0 elements in the Deque. */ public function isEmpty(): bool {} /** Removes all elements from the Deque. */ public function clear(): void {} public function __serialize(): array {} public function __unserialize(array $data): void {} /** Construct the Deque from the values of the array, ignoring keys */ public static function __set_state(array $array): Deque {} /** Appends value(s) to the end of the Deque. */ public function push(mixed $value): void {} /** Prepends value(s) to the start of the Deque. */ public function unshift(mixed ...$values): void {} /** Pops a value from the end of the Deque. */ public function pop(): mixed {} /** Shifts a value from the front of the Deque. */ public function shift(): mixed {} /** Peeks at the value at the start of the Deque, throws if empty */ public function bottom(): mixed {} /** Peeks at the value at the end of the Deque, throws if empty */ public function top(): mixed {} /** Returns a list of the elements from front to back. */ public function toArray(): array {} /* Get and set are strictly typed, unlike offsetGet/offsetSet. */ public function get(int $offset): mixed {} public function set(int $offset, mixed $value): void {} // Must be mixed for compatibility with ArrayAccess public function offsetGet(mixed $offset): mixed {} public function offsetExists(mixed $offset): bool {} public function offsetSet(mixed $offset, mixed $value): void {} /** Throws unconditionally */ public function offsetUnset(mixed $offset): void {} /** This is JSON serialized as a JSON array with elements from front to back */ public function jsonSerialize(): array {} } ``` Earlier work on the implementation can be found at https://github.com/TysonAndre/pecl-teds (though `Teds\Deque` hasn't been updated with new names yet) This was originally based on spl_fixedarray.c and previous work I did on an RFC. Notable features of `Deque` - Significantly lower memory usage and better performance than `SplDoublyLinkedList` - Amortized constant time operations for push/pop/unshift/shift. - Reclaims memory when roughly a quarter of the capacity is used, unlike array, which never releases allocated capacity https://www.npopov.com/2014/12/22/PHPs-new-hashtable-implementation.html > One problem with the current implementation is that arData never shrinks > (unless explicitly told to). So if you create an array with a few million > elements and remove them afterwards, the array will still take a lot of > memory. We should probably half the arData size if utilization falls below a > certain level. For long-running applications when the maximum count of Deque is larger than the average count, this may be a concern. - Adds functionality that cannot be implemented nearly efficiently in an array. For example, shifting a single element onto an array (and making it first in iteration order) with `array_shift` would take linear time, because all elements in the array would need to be moved to make room for the first one - Support `$deque[] = $element`, like ArrayObject. - Having this functionality in php itself rather than a third party extension would encourage wider adoption of this Backwards incompatible changes: - Userland classlikes named \Deque in the global namespace would cause a compile error due to this class now being declared internally.
1 parent 9e80947 commit 8fb127a

33 files changed

+2455
-35
lines changed

ext/spl/config.m4

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
PHP_NEW_EXTENSION(spl, php_spl.c spl_functions.c spl_iterators.c spl_array.c spl_directory.c spl_exceptions.c spl_observer.c spl_dllist.c spl_heap.c spl_fixedarray.c, no,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
2-
PHP_INSTALL_HEADERS([ext/spl], [php_spl.h spl_array.h spl_directory.h spl_engine.h spl_exceptions.h spl_functions.h spl_iterators.h spl_observer.h spl_dllist.h spl_heap.h spl_fixedarray.h])
1+
PHP_NEW_EXTENSION(spl, php_spl.c spl_functions.c spl_iterators.c spl_array.c spl_directory.c spl_exceptions.c spl_observer.c spl_dllist.c spl_heap.c spl_fixedarray.c spl_deque.c, no,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
2+
PHP_INSTALL_HEADERS([ext/spl], [php_spl.h spl_array.h spl_directory.h spl_engine.h spl_exceptions.h spl_functions.h spl_iterators.h spl_observer.h spl_dllist.h spl_heap.h spl_fixedarray.h spl_deque.h])
33
PHP_ADD_EXTENSION_DEP(spl, pcre, true)
44
PHP_ADD_EXTENSION_DEP(spl, standard, true)
55
PHP_ADD_EXTENSION_DEP(spl, json)

ext/spl/config.w32

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// vim:ft=javascript
22

3-
EXTENSION("spl", "php_spl.c spl_functions.c spl_iterators.c spl_array.c spl_directory.c spl_exceptions.c spl_observer.c spl_dllist.c spl_heap.c spl_fixedarray.c", false /*never shared */, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
3+
EXTENSION("spl", "php_spl.c spl_functions.c spl_iterators.c spl_array.c spl_directory.c spl_exceptions.c spl_observer.c spl_dllist.c spl_heap.c spl_fixedarray.c spl_deque.c", false /*never shared */, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
44
PHP_SPL="yes";
5-
PHP_INSTALL_HEADERS("ext/spl", "php_spl.h spl_array.h spl_directory.h spl_engine.h spl_exceptions.h spl_functions.h spl_iterators.h spl_observer.h spl_dllist.h spl_heap.h spl_fixedarray.h");
5+
PHP_INSTALL_HEADERS("ext/spl", "php_spl.h spl_array.h spl_directory.h spl_engine.h spl_exceptions.h spl_functions.h spl_iterators.h spl_observer.h spl_dllist.h spl_heap.h spl_fixedarray.h spl_deque.h");

ext/spl/php_spl.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "spl_exceptions.h"
3333
#include "spl_observer.h"
3434
#include "spl_dllist.h"
35+
#include "spl_deque.h"
3536
#include "spl_fixedarray.h"
3637
#include "spl_heap.h"
3738
#include "zend_exceptions.h"
@@ -708,6 +709,7 @@ PHP_MINIT_FUNCTION(spl)
708709
PHP_MINIT(spl_directory)(INIT_FUNC_ARGS_PASSTHRU);
709710
PHP_MINIT(spl_dllist)(INIT_FUNC_ARGS_PASSTHRU);
710711
PHP_MINIT(spl_heap)(INIT_FUNC_ARGS_PASSTHRU);
712+
PHP_MINIT(spl_deque)(INIT_FUNC_ARGS_PASSTHRU);
711713
PHP_MINIT(spl_fixedarray)(INIT_FUNC_ARGS_PASSTHRU);
712714
PHP_MINIT(spl_observer)(INIT_FUNC_ARGS_PASSTHRU);
713715

0 commit comments

Comments
 (0)