Skip to content

Commit 58d9397

Browse files
committed
Improve performance of ZEND_IS_IDENTICAL
And for ZEND_IS_NOT_IDENTICAL. - Take advantage of branch prediction in cases that are false/true - Avoid function call and duplicate fetches of Z_TYPE_P in common use cases - Avoid checking for EG(exception) when it's impossible. I think that the zval would always be IS_NULL or lower for an exception, but may be wrong. Aside 1: I see that ZEND_IS_EQUAL does have the `SMART_BRANCH` in the `SPEC`, generating optimized implementations based on what type of smart branch is used. Is excluding it from ZEND_IS_IDENTICAL deliberate, and if so, what's the reasoning? (e.g. is the use in common applications low?) - The SMART_BRANCH specialization was introduced in 27e01cd Aside 2: The function pointer for ZEND_IS_EQUAL_LONG can probably be used directly for ZEND_IS_IDENTICAL_LONG. I'm not sure how to generate that. (and DOUBLE) The amount of time for nestedloop_ni(20) decreased from 0.726s to 0.593s due to this change. This is still worse than `!=` (0.434) and `<` (0.375), which have implementations that take advantage of branch prediction for IS_LONG ```php function nestedloop_ni($n) { $x = 0; for ($a=0; $a!==$n; $a++) for ($b=0; $b!==$n; $b++) for ($c=0; $c!==$n; $c++) for ($d=0; $d!==$n; $d++) for ($e=0; $e!==$n; $e++) for ($f=0; $f!==$n; $f++) $x++; print "$x\n"; } ```
1 parent 36afe4e commit 58d9397

File tree

5 files changed

+921
-62
lines changed

5 files changed

+921
-62
lines changed

Zend/tests/bug70785.phpt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ try {
1919
undefined_function();
2020
} catch (Exception $e) {
2121
}
22+
try {
23+
$d === $f; // ZEND_VM_NEXT_OPCODE
24+
undefined_function();
25+
} catch (Exception $e) {
26+
}
2227
?>
2328
okey
2429
--EXPECT--

Zend/zend_operators.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2118,6 +2118,12 @@ ZEND_API zend_bool ZEND_FASTCALL zend_is_identical(zval *op1, zval *op2) /* {{{
21182118
}
21192119
/* }}} */
21202120

2121+
ZEND_API zend_bool ZEND_FASTCALL zend_is_identical_array(HashTable *h1, HashTable *h2) /* {{{ */
2122+
{
2123+
return (h1 == h2 ||
2124+
zend_hash_compare(h1, h2, (compare_func_t) hash_zval_identical_function, 1) == 0);
2125+
}
2126+
21212127
ZEND_API int ZEND_FASTCALL is_identical_function(zval *result, zval *op1, zval *op2) /* {{{ */
21222128
{
21232129
ZVAL_BOOL(result, zend_is_identical(op1, op2));

Zend/zend_operators.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ ZEND_API int ZEND_FASTCALL shift_right_function(zval *result, zval *op1, zval *o
5555
ZEND_API int ZEND_FASTCALL concat_function(zval *result, zval *op1, zval *op2);
5656

5757
ZEND_API zend_bool ZEND_FASTCALL zend_is_identical(zval *op1, zval *op2);
58+
ZEND_API zend_bool ZEND_FASTCALL zend_is_identical_array(HashTable *h1, HashTable *h2);
5859

5960
ZEND_API int ZEND_FASTCALL is_equal_function(zval *result, zval *op1, zval *op2);
6061
ZEND_API int ZEND_FASTCALL is_identical_function(zval *result, zval *op1, zval *op2);

Zend/zend_vm_def.h

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -451,25 +451,102 @@ ZEND_VM_COLD_CONSTCONST_HANDLER(16, ZEND_IS_IDENTICAL, CONST|TMP|VAR|CV, CONST|T
451451
SAVE_OPLINE();
452452
op1 = GET_OP1_ZVAL_PTR_DEREF(BP_VAR_R);
453453
op2 = GET_OP2_ZVAL_PTR_DEREF(BP_VAR_R);
454-
result = fast_is_identical_function(op1, op2);
454+
if (Z_TYPE_P(op1) != Z_TYPE_P(op2)) {
455+
/* They are not identical, return false. Note that this has to check for undefined variable errors when IS_NULL is possible. */
456+
ZEND_VM_C_LABEL(is_different_nothrow):
457+
FREE_OP1();
458+
FREE_OP2();
459+
ZEND_VM_SMART_BRANCH(0, 1);
460+
return;
461+
}
462+
if (Z_TYPE_P(op1) <= IS_TRUE) {
463+
/* They are identical, return true */
464+
FREE_OP1();
465+
FREE_OP2();
466+
ZEND_VM_SMART_BRANCH(1, 1);
467+
return;
468+
}
469+
switch (Z_TYPE_P(op1)) {
470+
case IS_LONG:
471+
result = (Z_LVAL_P(op1) == Z_LVAL_P(op2));
472+
break;
473+
case IS_RESOURCE:
474+
result = (Z_RES_P(op1) == Z_RES_P(op2));
475+
break;
476+
case IS_DOUBLE:
477+
result = (Z_DVAL_P(op1) == Z_DVAL_P(op2));
478+
break;
479+
case IS_STRING:
480+
result = zend_string_equals(Z_STR_P(op1), Z_STR_P(op2));
481+
break;
482+
case IS_ARRAY:
483+
/* This may cause EG(exception) due to infinite nesting, but other zval types won't? */
484+
/* XXX hash_zval_identical_function is not static */
485+
result = zend_is_identical_array(Z_ARRVAL_P(op1), Z_ARRVAL_P(op2));
486+
break;
487+
case IS_OBJECT:
488+
result = (Z_OBJ_P(op1) == Z_OBJ_P(op2));
489+
break;
490+
default:
491+
ZEND_VM_C_GOTO(is_different_nothrow);
492+
}
455493
FREE_OP1();
456494
FREE_OP2();
457-
ZEND_VM_SMART_BRANCH(result, 1);
495+
ZEND_VM_SMART_BRANCH(result, 0);
458496
}
459497

460498
ZEND_VM_COLD_CONSTCONST_HANDLER(17, ZEND_IS_NOT_IDENTICAL, CONST|TMP|VAR|CV, CONST|TMP|VAR|CV, SPEC(COMMUTATIVE))
461499
{
462500
USE_OPLINE
463-
zval *op1, *op2;
501+
zval *op1, *op2;
464502
zend_bool result;
465503

466504
SAVE_OPLINE();
467505
op1 = GET_OP1_ZVAL_PTR_DEREF(BP_VAR_R);
468506
op2 = GET_OP2_ZVAL_PTR_DEREF(BP_VAR_R);
469-
result = fast_is_not_identical_function(op1, op2);
507+
508+
if (Z_TYPE_P(op1) != Z_TYPE_P(op2)) {
509+
/* They are not identical, return true. Note that this has to check for undefined variable errors when IS_NULL is possible. */
510+
ZEND_VM_C_LABEL(is_different_nothrow):
511+
FREE_OP1();
512+
FREE_OP2();
513+
ZEND_VM_SMART_BRANCH(1, 1);
514+
return;
515+
}
516+
if (Z_TYPE_P(op1) <= IS_TRUE) {
517+
/* They are identical, return false */
518+
FREE_OP1();
519+
FREE_OP2();
520+
ZEND_VM_SMART_BRANCH(0, 1);
521+
return;
522+
}
523+
switch (Z_TYPE_P(op1)) {
524+
case IS_LONG:
525+
result = (Z_LVAL_P(op1) != Z_LVAL_P(op2));
526+
break;
527+
case IS_RESOURCE:
528+
result = (Z_RES_P(op1) != Z_RES_P(op2));
529+
break;
530+
case IS_DOUBLE:
531+
result = (Z_DVAL_P(op1) != Z_DVAL_P(op2));
532+
break;
533+
case IS_STRING:
534+
result = !zend_string_equals(Z_STR_P(op1), Z_STR_P(op2));
535+
break;
536+
case IS_ARRAY:
537+
/* This may cause EG(exception) due to infinite nesting, but other zval types won't? */
538+
/* XXX hash_zval_identical_function is not static */
539+
result = !zend_is_identical_array(Z_ARRVAL_P(op1), Z_ARRVAL_P(op2));
540+
break;
541+
case IS_OBJECT:
542+
result = (Z_OBJ_P(op1) != Z_OBJ_P(op2));
543+
break;
544+
default:
545+
ZEND_VM_C_GOTO(is_different_nothrow);
546+
}
470547
FREE_OP1();
471548
FREE_OP2();
472-
ZEND_VM_SMART_BRANCH(result, 1);
549+
ZEND_VM_SMART_BRANCH(result, 0);
473550
}
474551

475552
ZEND_VM_HELPER(zend_is_equal_helper, ANY, ANY, zval *op_1, zval *op_2)
@@ -1496,7 +1573,7 @@ ZEND_VM_HELPER(zend_pre_dec_helper, VAR|CV, ANY)
14961573

14971574
SAVE_OPLINE();
14981575
if (OP1_TYPE == IS_CV && UNEXPECTED(Z_TYPE_P(var_ptr) == IS_UNDEF)) {
1499-
ZVAL_NULL(var_ptr);
1576+
ZVAL_NULL(var_ptr);
15001577
ZVAL_UNDEFINED_OP1();
15011578
}
15021579

@@ -2234,7 +2311,7 @@ ZEND_VM_C_LABEL(fetch_obj_is_fast_copy):
22342311
}
22352312

22362313
retval = zobj->handlers->read_property(zobj, name, BP_VAR_IS, cache_slot, EX_VAR(opline->result.var));
2237-
2314+
22382315
if (OP2_TYPE != IS_CONST) {
22392316
zend_tmp_string_release(tmp_name);
22402317
}
@@ -5528,10 +5605,10 @@ ZEND_VM_HANDLER(147, ZEND_ADD_ARRAY_UNPACK, ANY, ANY)
55285605
{
55295606
USE_OPLINE
55305607
zval *op1;
5531-
5608+
55325609
SAVE_OPLINE();
55335610
op1 = GET_OP1_ZVAL_PTR(BP_VAR_R);
5534-
5611+
55355612
ZEND_VM_C_LABEL(add_unpack_again):
55365613
if (EXPECTED(Z_TYPE_P(op1) == IS_ARRAY)) {
55375614
HashTable *ht = Z_ARRVAL_P(op1);
@@ -5572,11 +5649,11 @@ ZEND_VM_C_LABEL(add_unpack_again):
55725649
}
55735650
HANDLE_EXCEPTION();
55745651
}
5575-
5652+
55765653
if (iter->funcs->rewind) {
55775654
iter->funcs->rewind(iter);
55785655
}
5579-
5656+
55805657
for (; iter->funcs->valid(iter) == SUCCESS; ) {
55815658
zval *val;
55825659

@@ -5625,7 +5702,7 @@ ZEND_VM_C_LABEL(add_unpack_again):
56255702
} else {
56265703
zend_throw_error(NULL, "Only arrays and Traversables can be unpacked");
56275704
}
5628-
5705+
56295706
FREE_OP1();
56305707
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
56315708
}

0 commit comments

Comments
 (0)