-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Test array argument in-place optimization #11167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
344 changes: 344 additions & 0 deletions
344
ext/standard/tests/array/array_modify_arg_in_place_optimization.phpt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,344 @@ | ||
--TEST-- | ||
Test array argument in-place optimization | ||
--EXTENSIONS-- | ||
zend_test | ||
--FILE-- | ||
<?php | ||
|
||
enum EN | ||
{ | ||
case Y; | ||
case N; | ||
} | ||
|
||
$listInt = []; | ||
$listStr = []; | ||
$mapInt = []; | ||
$mapObj = []; | ||
if (time() > 10) { // always true, but not const expr as time() is not CTE function | ||
$listInt[] = 1; | ||
$listInt[] = 2; | ||
$listStr[] = 'a'; | ||
$mapInt[1] = 0; | ||
$mapObj['e'] = EN::Y; | ||
$mapObj['f'] = EN::N; | ||
} | ||
|
||
$anotherList = [8, 9]; | ||
|
||
// the arrays now have GC_IMMUTABLE and GC_PERSISTENT flags cleared | ||
|
||
echo "*** array_merge ***\n"; | ||
|
||
// const empty 2nd array | ||
$ptrBefore = zend_get_array_ptr($listInt); | ||
$listInt = array_merge($listInt, []); | ||
$ptrAfter = zend_get_array_ptr($listInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$ptrBefore = zend_get_array_ptr($listStr); | ||
$listStr = array_merge($listStr, []); | ||
$ptrAfter = zend_get_array_ptr($listStr); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$ptrBefore = zend_get_array_ptr($mapInt); | ||
$mapInt = array_merge($mapInt, []); | ||
$ptrAfter = zend_get_array_ptr($mapInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$ptrBefore = zend_get_array_ptr($mapObj); | ||
$mapObj = array_merge($mapObj, []); | ||
$ptrAfter = zend_get_array_ptr($mapObj); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
// const non-empty 2nd array | ||
$ptrBefore = zend_get_array_ptr($listInt); | ||
$listInt = array_merge($listInt, [4]); | ||
$ptrAfter = zend_get_array_ptr($listInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$ptrBefore = zend_get_array_ptr($listStr); | ||
$listStr = array_merge($listStr, [4]); | ||
$ptrAfter = zend_get_array_ptr($listStr); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$ptrBefore = zend_get_array_ptr($mapInt); | ||
$mapInt = array_merge($mapInt, [4]); | ||
$ptrAfter = zend_get_array_ptr($mapInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$ptrBefore = zend_get_array_ptr($mapObj); | ||
$mapObj = array_merge($mapObj, [4]); | ||
$ptrAfter = zend_get_array_ptr($mapObj); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
// non-const non-empty 2nd array | ||
$ptrBefore = zend_get_array_ptr($listInt); | ||
$listInt = array_merge($listInt, $anotherList); | ||
$ptrAfter = zend_get_array_ptr($listInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$ptrBefore = zend_get_array_ptr($listStr); | ||
$listStr = array_merge($listStr, $anotherList); | ||
$ptrAfter = zend_get_array_ptr($listStr); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$ptrBefore = zend_get_array_ptr($mapInt); | ||
$mapInt = array_merge($mapInt, $anotherList); | ||
$ptrAfter = zend_get_array_ptr($mapInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$ptrBefore = zend_get_array_ptr($mapObj); | ||
$mapObj = array_merge($mapObj, $anotherList); | ||
$ptrAfter = zend_get_array_ptr($mapObj); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
// non-1st argument as a result | ||
$ptrBefore = zend_get_array_ptr($listInt); | ||
$listInt = array_merge($anotherList, $listInt); | ||
$ptrAfter = zend_get_array_ptr($listInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$ptrBefore = zend_get_array_ptr($listStr); | ||
$listStr = array_merge($anotherList, $listStr); | ||
$ptrAfter = zend_get_array_ptr($listStr); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$ptrBefore = zend_get_array_ptr($mapInt); | ||
$mapInt = array_merge($anotherList, $mapInt); | ||
$ptrAfter = zend_get_array_ptr($mapInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$ptrBefore = zend_get_array_ptr($mapObj); | ||
$mapObj = array_merge($anotherList, $mapObj); | ||
$ptrAfter = zend_get_array_ptr($mapObj); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
echo "---\n"; | ||
foreach ($listInt as $v) { | ||
$ptrBefore = zend_get_array_ptr($listInt); | ||
$listInt = array_merge($listInt, [$v]); // 2nd and 3rd iteration must not copy the array | ||
$ptrAfter = zend_get_array_ptr($listInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
if ($v === 1) { // 3rd iteration | ||
break; | ||
} | ||
} | ||
foreach (array_keys($listInt) as $k) { | ||
$ptrBefore = zend_get_array_ptr($listInt); | ||
$listInt = array_merge($listInt, [$listInt[$k]]); // array must never be copied | ||
$ptrAfter = zend_get_array_ptr($listInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
if ($listInt[$k] === 1) { // 3rd iteration | ||
break; | ||
} | ||
} | ||
|
||
// TODO array_merge should be optimized to always return the 1st array zval if there is no element to be added | ||
|
||
print_r($anotherList); // must not be modified | ||
print_r($listInt); | ||
print_r($listStr); | ||
print_r($mapInt); | ||
print_r($mapObj); | ||
|
||
$listInt = array_slice($listInt, 0, 2, true); | ||
$listStr = array_slice($listStr, 0, 1, true); | ||
$mapInt = array_slice($mapInt, 0, 1, true); | ||
$mapObj = array_slice($mapObj, 0, 2, true); | ||
|
||
|
||
echo "*** array merge with unpacking ***\n"; | ||
// TODO | ||
|
||
echo "*** array_diff family ***\n"; | ||
// TODO array_diff family should be optimized - https://github.com/php/php-src/pull/11060#discussion_r1175022196 | ||
|
||
echo "*** array_intersect family ***\n"; | ||
$oneListInt = array_slice($listInt, 0, 1, true); | ||
$oneMapObj = array_slice($mapObj, 0, 1, true); | ||
|
||
$listInt[] = -1; | ||
$ptrBefore = zend_get_array_ptr($listInt); | ||
$listInt = array_intersect($listInt, $oneListInt); | ||
$ptrAfter = zend_get_array_ptr($listInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$mapObj[] = -1; | ||
$ptrBefore = zend_get_array_ptr($mapObj); | ||
$mapObj = array_intersect($mapObj, $oneMapObj); | ||
$ptrAfter = zend_get_array_ptr($mapObj); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$listInt[] = -1; | ||
$ptrBefore = zend_get_array_ptr($listInt); | ||
$listInt = array_intersect_assoc($listInt, $oneListInt); | ||
$ptrAfter = zend_get_array_ptr($listInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$mapObj[] = -1; | ||
$ptrBefore = zend_get_array_ptr($mapObj); | ||
$mapObj = array_intersect_assoc($mapObj, $oneMapObj); | ||
$ptrAfter = zend_get_array_ptr($mapObj); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$listInt[] = -1; | ||
$ptrBefore = zend_get_array_ptr($listInt); | ||
$listInt = array_intersect_key($listInt, $oneListInt); | ||
$ptrAfter = zend_get_array_ptr($listInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$listInt[] = -1; | ||
$ptrBefore = zend_get_array_ptr($listInt); | ||
$listInt = array_intersect_ukey($listInt, $oneListInt, fn ($k1, $k2) => $k1 <=> $k2); | ||
$ptrAfter = zend_get_array_ptr($listInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$mapObj[] = -1; | ||
$ptrBefore = zend_get_array_ptr($mapObj); | ||
$mapObj = array_intersect_ukey($mapObj, $oneListInt, fn ($k1, $k2) => $k1 <=> $k2); | ||
$ptrAfter = zend_get_array_ptr($mapObj); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
echo "*** array_unique ***\n"; | ||
|
||
$listInt[] = end($listInt); | ||
$ptrBefore = zend_get_array_ptr($listInt); | ||
$listInt = array_unique($listInt); | ||
$ptrAfter = zend_get_array_ptr($listInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$mapObj[] = end($mapObj); | ||
$ptrBefore = zend_get_array_ptr($mapObj); | ||
$mapObj = array_unique($mapObj, SORT_REGULAR); | ||
$ptrAfter = zend_get_array_ptr($mapObj); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
echo "*** array_replace ***\n"; | ||
|
||
$listInt[] = end($listInt); | ||
$ptrBefore = zend_get_array_ptr($listInt); | ||
$listInt = array_replace($listInt, ['*']); | ||
$ptrAfter = zend_get_array_ptr($listInt); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
$mapObj[] = end($mapObj); | ||
$ptrBefore = zend_get_array_ptr($mapObj); | ||
$mapObj = array_replace($mapObj, ['*']); | ||
$ptrAfter = zend_get_array_ptr($mapObj); | ||
var_dump($ptrAfter === $ptrBefore); | ||
|
||
print_r($listInt); | ||
print_r($mapObj); | ||
|
||
?> | ||
--EXPECT-- | ||
*** array_merge *** | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(false) | ||
bool(false) | ||
bool(false) | ||
bool(false) | ||
--- | ||
bool(false) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
Array | ||
( | ||
[0] => 8 | ||
[1] => 9 | ||
) | ||
Array | ||
( | ||
[0] => 8 | ||
[1] => 9 | ||
[2] => 1 | ||
[3] => 2 | ||
[4] => 4 | ||
[5] => 8 | ||
[6] => 9 | ||
[7] => 8 | ||
[8] => 9 | ||
[9] => 1 | ||
[10] => 8 | ||
[11] => 9 | ||
[12] => 1 | ||
) | ||
Array | ||
( | ||
[0] => 8 | ||
[1] => 9 | ||
[2] => a | ||
[3] => 4 | ||
[4] => 8 | ||
[5] => 9 | ||
) | ||
Array | ||
( | ||
[0] => 8 | ||
[1] => 9 | ||
[2] => 0 | ||
[3] => 4 | ||
[4] => 8 | ||
[5] => 9 | ||
) | ||
Array | ||
( | ||
[0] => 8 | ||
[1] => 9 | ||
[e] => EN Enum | ||
( | ||
[name] => Y | ||
) | ||
|
||
[f] => EN Enum | ||
( | ||
[name] => N | ||
) | ||
|
||
[2] => 4 | ||
[3] => 8 | ||
[4] => 9 | ||
) | ||
*** array merge with unpacking *** | ||
*** array_diff family *** | ||
*** array_intersect family *** | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
bool(true) | ||
*** array_unique *** | ||
bool(true) | ||
bool(true) | ||
*** array_replace *** | ||
bool(true) | ||
bool(true) | ||
Array | ||
( | ||
[0] => * | ||
[1] => 8 | ||
) | ||
Array | ||
( | ||
[0] => * | ||
[3] => 8 | ||
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is
zend_get_zval_ptr
implemented correctly? It seems$ptrAfter === $ptrBefore
is always true, which is not expected here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to be aware of how zvals and arrays work behind the scenes.
A zval contains some data and some type information. For arrays this type is IS_ARRAY, and its data is a pointer to the array. When I do
$a = $b
, a and b are two different zvals, but if b is an array, then a will point to the same array because the pointer is copied (and the array refcount is increased by one).Similarly if you have:
then the
zend_get_zval_ptr
for $a will always return the same pointer for all three lines, because you're always asking the pointer for the container variable a. What you actually want to ask the engine is the pointer to the array (e.g. Z_ARRVAL_P(zval_pointer_for_a_here)`. Then you'll see that each line will have a different array pointer, while having the same zval pointer.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nielsdos
$listInt
in the test should have RC = 1 (excl. in the foreach test), but still the results are very different vs. what I would expect, can you please give me an example of php code where variable is "RC1 temporaries" and where "CV"?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mvorisek CVs are never RC1 when passed to a function. #11166 will change this but only work with opcache (and optimizations) enabled. TMP/VAR will be RC1 if allocated within a function and then returned, of when a new value is generated from a temporary expression (e.g.
[0 => 1] + [1 => 2]
).range
is another way to generate a temporary array with RC1.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So
array_merge
function in$listInt = array_merge($listInt, []);
expr, when$listInt
had RC1 originally, will not be optimized, asarray_merge
will receive RC2? If yes, then can the testing using the before/after PTR be done before #11166 is landed?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right
Sure, but that will only work in a limited context. E.g. pseudo main (root of the file) won't work, so you'll need to wrap it in a separate function. And as mentioned above, it will only work when opcache is enabled, as the optimizer otherwise doesn't run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nielsdos would you be ok to add this test to your #11166 directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's wait until that PR is merged and keep this one separate. Otherwise it will be a back and forth between us two which is a lot of overhead.