Skip to content

Commit

Permalink
Minor optimizations to array_keys()/array_values()
Browse files Browse the repository at this point in the history
array_values():
When the input is an empty array or a packed array with no gaps,
return the original array.

array_keys():
When the input is an empty array, return the original array.
When the input is a packed array with no holes
(and no search key specified), populate the return with
a simple range(0, count($input) - 1)
  • Loading branch information
sgolemon committed Mar 14, 2017
1 parent 16ae9f8 commit c74bc87
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 19 deletions.
1 change: 1 addition & 0 deletions NEWS
Expand Up @@ -22,6 +22,7 @@ PHP NEWS
bugs #53838, #61655, #66173, #70925, #72254, etc. (Andrea)
. Raised minimum supported Windows versions to Windows 7/Server 2008 R2.
(Anatol)
. Implemented minor optimization in array_keys/array_values(). (Sara)
. Fixed bug #73969 (segfault in debug_print_backtrace). (andrewnester)
. Added PHP_OS_FAMILY constant to determine on which OS we are. (Jan Altensen)
. Fixed bug #73994 (arginfo incorrect for unpack). (krakjoe)
Expand Down
61 changes: 42 additions & 19 deletions ext/standard/array.c
Expand Up @@ -3938,20 +3938,29 @@ PHP_FUNCTION(array_keys)
zend_bool strict = 0; /* do strict comparison */
zend_ulong num_idx;
zend_string *str_idx;
zend_array *arrval;
zend_ulong elem_count;

ZEND_PARSE_PARAMETERS_START(1, 3)
Z_PARAM_ARRAY(input)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(search_value)
Z_PARAM_BOOL(strict)
ZEND_PARSE_PARAMETERS_END();
arrval = Z_ARRVAL_P(input);
elem_count = zend_hash_num_elements(arrval);

/* Base case: empty input */
if (!elem_count) {

This comment has been minimized.

Copy link
@zbenc

zbenc Jun 11, 2017

@sgolemon
This optimization should also be of use for php_array_merge_or_replace_wrapper in ext/standard/array.c and _zend_hash_merge in Zend/zend_hash.c. array_merge / array_replace with the second argument being empty is quite often the case as is $arr1 + $arr2 via zend_hash_merge.

In those cases one could even go a bit further by returning the first non-empty array if all other arguments are empty as those are in this regard commutative:

  • array_merge($arr_empty, $arr_full) -> $arr_full
  • array_merge($arr_full, $arr_empty) -> $arr_full
  • array_replace($arr_empty, $arr_full) -> $arr_full
  • array_replace($arr_full, $arr_empty) -> $arr_full
  • $arr_empty + $arr_full -> $arr_full
  • $arr_full + $arr_empty -> $arr_full

Also, the simple non-empty check could be applied to array_slice as well.

This comment has been minimized.

Copy link
@zbenc

zbenc Jul 10, 2017

@sgolemon Ping!

RETURN_ZVAL(input, 1, 0)
}

/* Initialize return array */
if (search_value != NULL) {
array_init(return_value);

if (strict) {
ZEND_HASH_FOREACH_KEY_VAL_IND(Z_ARRVAL_P(input), num_idx, str_idx, entry) {
ZEND_HASH_FOREACH_KEY_VAL_IND(arrval, num_idx, str_idx, entry) {
ZVAL_DEREF(entry);
if (fast_is_identical_function(search_value, entry)) {
if (str_idx) {
Expand All @@ -3963,7 +3972,7 @@ PHP_FUNCTION(array_keys)
}
} ZEND_HASH_FOREACH_END();
} else {
ZEND_HASH_FOREACH_KEY_VAL_IND(Z_ARRVAL_P(input), num_idx, str_idx, entry) {
ZEND_HASH_FOREACH_KEY_VAL_IND(arrval, num_idx, str_idx, entry) {
if (fast_equal_check_function(search_value, entry)) {
if (str_idx) {
ZVAL_STR_COPY(&new_val, str_idx);
Expand All @@ -3975,21 +3984,27 @@ PHP_FUNCTION(array_keys)
} ZEND_HASH_FOREACH_END();
}
} else {
array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL_P(input)));
if (!zend_hash_num_elements(Z_ARRVAL_P(input))) {
return;
}
array_init_size(return_value, elem_count);
zend_hash_real_init(Z_ARRVAL_P(return_value), 1);
ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) {
/* Go through input array and add keys to the return array */
ZEND_HASH_FOREACH_KEY_VAL_IND(Z_ARRVAL_P(input), num_idx, str_idx, entry) {
if (str_idx) {
ZVAL_STR_COPY(&new_val, str_idx);
} else {
ZVAL_LONG(&new_val, num_idx);
if (HT_IS_PACKED(arrval) && HT_IS_WITHOUT_HOLES(arrval)) {
/* Optimistic case: range(0..n-1) for vector-like packed array */
zval new_val;
ZVAL_LONG(&new_val, 0);
for (; Z_LVAL(new_val) < elem_count; ++Z_LVAL(new_val)) {
ZEND_HASH_FILL_ADD(&new_val);
}
ZEND_HASH_FILL_ADD(&new_val);
} ZEND_HASH_FOREACH_END();
} else {
/* Go through input array and add keys to the return array */
ZEND_HASH_FOREACH_KEY_VAL_IND(Z_ARRVAL_P(input), num_idx, str_idx, entry) {
if (str_idx) {
ZVAL_STR_COPY(&new_val, str_idx);
} else {
ZVAL_LONG(&new_val, num_idx);
}
ZEND_HASH_FILL_ADD(&new_val);
} ZEND_HASH_FOREACH_END();
}
} ZEND_HASH_FILL_END();
}
}
Expand All @@ -4001,23 +4016,31 @@ PHP_FUNCTION(array_values)
{
zval *input, /* Input array */
*entry; /* An entry in the input array */
zend_array *arrval;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY(input)
ZEND_PARSE_PARAMETERS_END();

/* Initialize return array */
array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL_P(input)));
arrval = Z_ARRVAL_P(input);

if (!zend_hash_num_elements(Z_ARRVAL_P(input))) {
return;
/* Return empty input as is */
if (!zend_hash_num_elements(arrval)) {
RETURN_ZVAL(input, 1, 0);
}

/* Return vector-like packed arrays as-is */
if (HT_IS_PACKED(arrval) && HT_IS_WITHOUT_HOLES(arrval)) {
RETURN_ZVAL(input, 1, 0);
}

/* Initialize return array */
array_init_size(return_value, zend_hash_num_elements(arrval));
zend_hash_real_init(Z_ARRVAL_P(return_value), 1);

/* Go through input array and add values to the return array */
ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) {
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(input), entry) {
ZEND_HASH_FOREACH_VAL(arrval, entry) {
if (UNEXPECTED(Z_ISREF_P(entry) && Z_REFCOUNT_P(entry) == 1)) {
entry = Z_REFVAL_P(entry);
}
Expand Down
84 changes: 84 additions & 0 deletions ext/standard/tests/array/packed_001.phpt
@@ -0,0 +1,84 @@
--TEST--
array_keys() and array_values() w/ packed optimization
--FILE--
<?php

$x = [1,2,3];
unset($x[1]);

$inputs = [
[],
[1,2,3],
[0=>1, 1=>2, 2=>3],
[1=>1, 2=>2, 3=>3],
[0=>1, 2=>3],
$x,
];

foreach ($inputs as $input) {
print_r(array_keys($input));
print_r(array_values($input));
}
--EXPECT--
Array
(
)
Array
(
)
Array
(
[0] => 0
[1] => 1
[2] => 2
)
Array
(
[0] => 1
[1] => 2
[2] => 3
)
Array
(
[0] => 0
[1] => 1
[2] => 2
)
Array
(
[0] => 1
[1] => 2
[2] => 3
)
Array
(
[0] => 1
[1] => 2
[2] => 3
)
Array
(
[0] => 1
[1] => 2
[2] => 3
)
Array
(
[0] => 0
[1] => 2
)
Array
(
[0] => 1
[1] => 3
)
Array
(
[0] => 0
[1] => 2
)
Array
(
[0] => 1
[1] => 3
)

0 comments on commit c74bc87

Please sign in to comment.