Skip to content

Commit fc910a3

Browse files
PHP array_shift(): Optimize tail move on hole-free packed arrays
When a packed array has no holes and no active iterators, the slot vacated by the shifted element is the only hole, so the tail can be moved down with a single memmove instead of testing every slot for IS_UNDEF and comparing source and destination indexes. A tail of a single element is moved directly instead, avoiding the memmove call overhead on tiny arrays.
1 parent f142b81 commit fc910a3

4 files changed

Lines changed: 61 additions & 8 deletions

File tree

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ PHP NEWS
5454
. Fixed read buffer compaction in php_stream_filter_flush(). (crystarm)
5555
. Io\Poll\Context::wait() now rejects a $maxEvents value greater than
5656
INT_MAX instead of truncating it. (marc-mabe)
57+
. Improved performance of array_shift() on packed arrays without holes.
58+
(mehmetcansahin)
5759

5860
- SimpleXML:
5961
. Fixed writing to a dimension of the object returned by attributes() not

UPGRADING

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,7 @@ PHP 8.6 UPGRADE NOTES
977977
. Improved performance of array_fill_keys().
978978
. Improved performance of array_intersect().
979979
. Improved performance of array_map() with multiple arrays passed.
980+
. Improved performance of array_shift() on packed arrays without holes.
980981
. Improved performance of array_sum() and array_product() for
981982
integer-only arrays.
982983
. Improved performance of array_unshift().

ext/standard/array.c

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3576,6 +3576,8 @@ PHP_FUNCTION(array_shift)
35763576
/* re-index like it did before */
35773577
if (HT_IS_PACKED(Z_ARRVAL_P(stack))) {
35783578
uint32_t k = 0;
3579+
/* Must be read before the deletion below opens a hole. */
3580+
bool without_holes = HT_IS_WITHOUT_HOLES(Z_ARRVAL_P(stack));
35793581

35803582
/* Get the first value and copy it into the return value */
35813583
idx = 0;
@@ -3596,15 +3598,32 @@ PHP_FUNCTION(array_shift)
35963598
zend_hash_packed_del_val(Z_ARRVAL_P(stack), val);
35973599

35983600
if (EXPECTED(!HT_HAS_ITERATORS(Z_ARRVAL_P(stack)))) {
3599-
for (idx = 0; idx < Z_ARRVAL_P(stack)->nNumUsed; idx++) {
3600-
val = Z_ARRVAL_P(stack)->arPacked + idx;
3601-
if (Z_TYPE_P(val) == IS_UNDEF) continue;
3602-
if (idx != k) {
3603-
zval *q = Z_ARRVAL_P(stack)->arPacked + k;
3604-
ZVAL_COPY_VALUE(q, val);
3605-
ZVAL_UNDEF(val);
3601+
if (without_holes) {
3602+
/* The slot just vacated is the only hole, so the rest moves down by one
3603+
* without having to test every slot. */
3604+
HashTable *ht = Z_ARRVAL_P(stack);
3605+
3606+
if (ht->nNumUsed > 0) {
3607+
k = ht->nNumUsed - 1;
3608+
if (k == 1) {
3609+
/* Avoid the memmove() call overhead for the common tiny-array case. */
3610+
ZVAL_COPY_VALUE(ht->arPacked, ht->arPacked + 1);
3611+
} else {
3612+
memmove(ht->arPacked, ht->arPacked + 1, sizeof(zval) * k);
3613+
}
3614+
ZVAL_UNDEF(ht->arPacked + k);
3615+
}
3616+
} else {
3617+
for (idx = 0; idx < Z_ARRVAL_P(stack)->nNumUsed; idx++) {
3618+
val = Z_ARRVAL_P(stack)->arPacked + idx;
3619+
if (Z_TYPE_P(val) == IS_UNDEF) continue;
3620+
if (idx != k) {
3621+
zval *q = Z_ARRVAL_P(stack)->arPacked + k;
3622+
ZVAL_COPY_VALUE(q, val);
3623+
ZVAL_UNDEF(val);
3624+
}
3625+
k++;
36063626
}
3607-
k++;
36083627
}
36093628
} else {
36103629
uint32_t iter_pos = zend_hash_iterators_lower_pos(Z_ARRVAL_P(stack), 0);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
array_shift() on packed arrays with holes created by unset()
3+
--FILE--
4+
<?php
5+
// Holes at the head and in the middle: the compaction loop must skip them.
6+
$a = [1, 2, 3, 4, 5];
7+
unset($a[0], $a[2]);
8+
var_dump(array_shift($a), $a);
9+
10+
// Shift a holed array down to empty: the next appended key must be 0.
11+
$a = [10, 20];
12+
unset($a[0]);
13+
var_dump(array_shift($a), $a);
14+
$a[] = 30;
15+
var_dump($a);
16+
?>
17+
--EXPECT--
18+
int(2)
19+
array(2) {
20+
[0]=>
21+
int(4)
22+
[1]=>
23+
int(5)
24+
}
25+
int(20)
26+
array(0) {
27+
}
28+
array(1) {
29+
[0]=>
30+
int(30)
31+
}

0 commit comments

Comments
 (0)