Skip to content

Commit abc4fc9

Browse files
Optimize array_diff() for long values
1 parent 3704518 commit abc4fc9

3 files changed

Lines changed: 108 additions & 0 deletions

File tree

UPGRADING

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ PHP 8.6 UPGRADE NOTES
544544
. Reduced temporary allocations when iterating Phar directories.
545545

546546
- Standard:
547+
. Improved performance of array_diff() for integer-only arrays.
547548
. Improved performance of array_fill_keys().
548549
. Improved performance of array_map() with multiple arrays passed.
549550
. Improved performance of array_sum() and array_product() for

ext/standard/array.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5753,6 +5753,68 @@ PHP_FUNCTION(array_diff_ukey)
57535753
}
57545754
/* }}} */
57555755

5756+
static zend_always_inline bool php_array_values_are_all_long(HashTable *ht)
5757+
{
5758+
zval *value;
5759+
5760+
ZEND_HASH_FOREACH_VAL(ht, value) {
5761+
ZVAL_DEREF(value);
5762+
if (UNEXPECTED(Z_TYPE_P(value) != IS_LONG)) {
5763+
return false;
5764+
}
5765+
} ZEND_HASH_FOREACH_END();
5766+
5767+
return true;
5768+
}
5769+
5770+
static zend_never_inline bool php_array_diff_long(zval *args, uint32_t argc, uint32_t exclude_count, zval *return_value)
5771+
{
5772+
HashTable exclude;
5773+
zval *value, *entry;
5774+
zend_string *key;
5775+
zend_long idx;
5776+
uint32_t i;
5777+
5778+
/* The copy loop below cannot cheaply undo a partially built result, so
5779+
* the first array must be checked upfront. */
5780+
if (!php_array_values_are_all_long(Z_ARRVAL(args[0]))) {
5781+
return false;
5782+
}
5783+
5784+
/* Create the exclude map while checking the value types, bailing out to
5785+
* the generic implementation on the first non-long value. A late bailout
5786+
* discards the partially built map, but that is cheaper than always
5787+
* paying an extra type-check pass on the all-long path. */
5788+
zend_hash_init(&exclude, exclude_count, NULL, NULL, 0);
5789+
for (i = 1; i < argc; i++) {
5790+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL(args[i]), value) {
5791+
ZVAL_DEREF(value);
5792+
if (UNEXPECTED(Z_TYPE_P(value) != IS_LONG)) {
5793+
zend_hash_destroy(&exclude);
5794+
return false;
5795+
}
5796+
zend_hash_index_add_empty_element(&exclude, Z_LVAL_P(value));
5797+
} ZEND_HASH_FOREACH_END();
5798+
}
5799+
5800+
array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL(args[0])));
5801+
ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL(args[0]), idx, key, value) {
5802+
entry = value;
5803+
ZVAL_DEREF(entry);
5804+
if (!zend_hash_index_exists(&exclude, Z_LVAL_P(entry))) {
5805+
if (key) {
5806+
value = zend_hash_add_new(Z_ARRVAL_P(return_value), key, value);
5807+
} else {
5808+
value = zend_hash_index_add_new(Z_ARRVAL_P(return_value), idx, value);
5809+
}
5810+
zval_add_ref(value);
5811+
}
5812+
} ZEND_HASH_FOREACH_END();
5813+
5814+
zend_hash_destroy(&exclude);
5815+
return true;
5816+
}
5817+
57565818
/* {{{ Returns the entries of arr1 that have values which are not present in any of the others arguments. */
57575819
PHP_FUNCTION(array_diff)
57585820
{
@@ -5853,6 +5915,10 @@ PHP_FUNCTION(array_diff)
58535915
RETURN_THROWS();
58545916
}
58555917

5918+
if (php_array_diff_long(args, argc, (uint32_t)num, return_value)) {
5919+
return;
5920+
}
5921+
58565922
ZVAL_NULL(&dummy);
58575923
/* create exclude map */
58585924
zend_hash_init(&exclude, num, NULL, NULL, 0);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
array_diff() with long values
3+
--FILE--
4+
<?php
5+
function dump_array(array $value): void {
6+
echo json_encode($value), "\n";
7+
}
8+
9+
dump_array(array_diff([1, 2, 3], [2]));
10+
dump_array(array_diff([-2, -1, 0, 1], [-1, 1]));
11+
dump_array(array_diff([PHP_INT_MIN, -1, PHP_INT_MAX], [PHP_INT_MIN, PHP_INT_MAX]));
12+
dump_array(array_diff([1, 2, 2, 3], [2]));
13+
dump_array(array_diff(['a' => 1, 'b' => 2, 'c' => 3], [2]));
14+
dump_array(array_diff([1, 2, 3], ['2']));
15+
dump_array(array_diff([1, '2', 3], [2]));
16+
dump_array(array_diff([1, 2, 3, 4], [2], [4, 5]));
17+
dump_array(array_diff([1, 2, 3, 4], [2], ['3']));
18+
dump_array(array_diff([1, 2], [1, 2]));
19+
20+
$y = 2;
21+
dump_array(array_diff([1, 2, 3], [&$y]));
22+
23+
$x = 1;
24+
$array = [&$x, 2];
25+
$result = array_diff($array, [2]);
26+
$x = 9;
27+
var_dump($result[0]);
28+
?>
29+
--EXPECT--
30+
{"0":1,"2":3}
31+
{"0":-2,"2":0}
32+
{"1":-1}
33+
{"0":1,"3":3}
34+
{"a":1,"c":3}
35+
{"0":1,"2":3}
36+
{"0":1,"2":3}
37+
{"0":1,"2":3}
38+
{"0":1,"3":4}
39+
[]
40+
{"0":1,"2":3}
41+
int(9)

0 commit comments

Comments
 (0)