Skip to content

Commit b1f82d8

Browse files
committed
zend_vm: Add OPcode specialization for === []
Checking whether an array is empty with a strict comparison against the empty array is a common pattern in PHP. A GitHub search for `"=== []" language:PHP` reveals 44k hits. From the set of `!$a`, `count($a) === 0`, `empty($a)` and `$a === []` it however is also the slowest option. A test script: <?php $variable = array_fill(0, 10, random_int(1, 2)); $f = true; for ($i = 0; $i < 50_000_000; $i++) { $isEmpty = $variable === []; $f = $f && $isEmpty; } var_dump($f); with the `$isEmpty = …;` statement appropriately replaced results in: Benchmark 1: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 count.php Time (mean ± σ): 467.6 ms ± 2.3 ms [User: 463.3 ms, System: 3.4 ms] Range (min … max): 464.6 ms … 473.4 ms 10 runs Benchmark 2: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 empty.php Time (mean ± σ): 305.3 ms ± 0.3 ms [User: 302.0 ms, System: 3.1 ms] Range (min … max): 304.9 ms … 305.7 ms 10 runs Benchmark 3: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 identical.php Time (mean ± σ): 630.3 ms ± 3.9 ms [User: 624.8 ms, System: 3.8 ms] Range (min … max): 627.4 ms … 637.6 ms 10 runs Benchmark 4: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 not.php Time (mean ± σ): 311.8 ms ± 3.4 ms [User: 307.9 ms, System: 3.6 ms] Range (min … max): 308.7 ms … 320.7 ms 10 runs Summary sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 empty.php ran 1.02 ± 0.01 times faster than sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 not.php 1.53 ± 0.01 times faster than sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 count.php 2.06 ± 0.01 times faster than sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 identical.php This patch adds another OPcode specialization for `ZEND_IS_IDENTICAL` that specifically matches a comparison against the empty array. With this specialization the `=== []` check becomes the fastest of them all, which is not surprising given how specific it is: Benchmark 1: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 count.php Time (mean ± σ): 384.1 ms ± 2.3 ms [User: 379.3 ms, System: 3.8 ms] Range (min … max): 382.2 ms … 389.8 ms 10 runs Benchmark 2: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 empty.php Time (mean ± σ): 305.8 ms ± 3.2 ms [User: 301.7 ms, System: 3.8 ms] Range (min … max): 304.4 ms … 314.9 ms 10 runs Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options. Benchmark 3: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 identical.php Time (mean ± σ): 293.9 ms ± 2.9 ms [User: 289.7 ms, System: 3.3 ms] Range (min … max): 291.5 ms … 299.4 ms 10 runs Benchmark 4: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 not.php Time (mean ± σ): 306.8 ms ± 0.4 ms [User: 303.8 ms, System: 2.7 ms] Range (min … max): 306.3 ms … 307.3 ms 10 runs Summary sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 identical.php ran 1.04 ± 0.01 times faster than sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 empty.php 1.04 ± 0.01 times faster than sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 not.php 1.31 ± 0.02 times faster than sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 count.php As a follow-up optimization it might be possible to transform the other emptiness checks, such as `count($arr) === 0` into `$arr === []` if `$arr` is known to be `MAY_BE_ARRAY` only.
1 parent 4122daa commit b1f82d8

File tree

3 files changed

+324
-246
lines changed

3 files changed

+324
-246
lines changed

Zend/zend_vm_def.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10062,6 +10062,19 @@ ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_IS_NOT_EQUAL|ZEND_IS_NOT_IDENTICAL, (op1_info
1006210062
ZEND_VM_SMART_BRANCH(result, 0);
1006310063
}
1006410064

10065+
ZEND_VM_TYPE_SPEC_HANDLER(ZEND_IS_IDENTICAL, (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0), ZEND_IS_IDENTICAL_EMPTY_ARRAY, TMPVARCV, CONST, SPEC(SMART_BRANCH,COMMUTATIVE))
10066+
{
10067+
USE_OPLINE
10068+
zval *op1;
10069+
bool result;
10070+
10071+
op1 = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
10072+
result = Z_TYPE_P(op1) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(op1)) == 0;
10073+
FREE_OP1();
10074+
FREE_OP2();
10075+
ZEND_VM_SMART_BRANCH(result, 0);
10076+
}
10077+
1006510078
ZEND_VM_TYPE_SPEC_HANDLER(ZEND_IS_IDENTICAL, op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info & (MAY_BE_UNDEF|MAY_BE_REF)), ZEND_IS_IDENTICAL_NOTHROW, CV, CONST|CV, SPEC(COMMUTATIVE))
1006610079
{
1006710080
/* This is declared below the specializations for MAY_BE_LONG/MAY_BE_DOUBLE so those will be used instead if possible. */

Zend/zend_vm_execute.h

Lines changed: 132 additions & 70 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)