Skip to content

Commit 0cca79a

Browse files
committed
zend_execute: Streamline typechecks in zend_check_type_slow() if an object is given
For the callable.php vs closure.php benchmark: $ taskset -c 0 hyperfine -L file callable.php,closure.php -L version before,after '/tmp/bench/{version} {file}' Benchmark 1: /tmp/bench/before callable.php Time (mean ± σ): 351.1 ms ± 2.8 ms [User: 348.3 ms, System: 2.0 ms] Range (min … max): 349.3 ms … 359.0 ms 10 runs Benchmark 2: /tmp/bench/before closure.php Time (mean ± σ): 274.6 ms ± 2.4 ms [User: 270.9 ms, System: 2.9 ms] Range (min … max): 273.3 ms … 281.5 ms 10 runs Benchmark 3: /tmp/bench/after callable.php Time (mean ± σ): 272.4 ms ± 0.5 ms [User: 270.3 ms, System: 2.0 ms] Range (min … max): 271.6 ms … 273.3 ms 10 runs Benchmark 4: /tmp/bench/after closure.php Time (mean ± σ): 277.4 ms ± 2.2 ms [User: 274.3 ms, System: 2.4 ms] Range (min … max): 275.7 ms … 283.3 ms 10 runs Summary /tmp/bench/after callable.php ran 1.01 ± 0.01 times faster than /tmp/bench/before closure.php 1.02 ± 0.01 times faster than /tmp/bench/after closure.php 1.29 ± 0.01 times faster than /tmp/bench/before callable.php For the array_find benchmark: Benchmark 1: /tmp/bench/before native.php Time (mean ± σ): 627.6 ms ± 7.1 ms [User: 622.5 ms, System: 2.8 ms] Range (min … max): 622.1 ms … 641.4 ms 10 runs Benchmark 2: /tmp/bench/after native.php Time (mean ± σ): 598.0 ms ± 5.5 ms [User: 594.4 ms, System: 2.7 ms] Range (min … max): 589.9 ms … 604.9 ms 10 runs Summary /tmp/bench/after native.php ran 1.05 ± 0.02 times faster than /tmp/bench/before native.php For a test with scalar types: <?php function func(string $f): string { return strrev($f); } for ($i = 0; $i < 10000000; $i++) { func('abc'); } the timings are: Benchmark 1: /tmp/bench/before scalar.php Time (mean ± σ): 212.5 ms ± 1.7 ms [User: 209.8 ms, System: 2.2 ms] Range (min … max): 211.4 ms … 218.1 ms 14 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 2: /tmp/bench/after scalar.php Time (mean ± σ): 200.9 ms ± 2.0 ms [User: 198.0 ms, System: 2.4 ms] Range (min … max): 199.7 ms … 207.2 ms 14 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. Summary /tmp/bench/after scalar.php ran 1.06 ± 0.01 times faster than /tmp/bench/before scalar.php And a union type using only scalars: <?php function func(string|int $f): string { return strrev($f); } for ($i = 0; $i < 10000000; $i++) { func('abc'); } results in: Benchmark 1: /tmp/bench/before union.php Time (mean ± σ): 212.8 ms ± 1.8 ms [User: 210.0 ms, System: 2.2 ms] Range (min … max): 212.0 ms … 219.0 ms 14 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 2: /tmp/bench/after union.php Time (mean ± σ): 200.1 ms ± 0.4 ms [User: 197.7 ms, System: 2.3 ms] Range (min … max): 199.4 ms … 200.8 ms 14 runs Summary /tmp/bench/after union.php ran 1.06 ± 0.01 times faster than /tmp/bench/before union.php Union types with objects are the only thing tested that were more or less the same: <?php class A { public function get() { return 'abc'; } } class B { } function func(A|B $f): string { return strrev($f->get()); } for ($i = 0; $i < 10000000; $i++) { func(new A()); } results in: Benchmark 1: /tmp/bench/before union_obj.php Time (mean ± σ): 700.6 ms ± 10.4 ms [User: 696.5 ms, System: 3.9 ms] Range (min … max): 688.6 ms … 717.8 ms 10 runs Benchmark 2: /tmp/bench/after union_obj.php Time (mean ± σ): 706.7 ms ± 15.0 ms [User: 702.5 ms, System: 3.8 ms] Range (min … max): 688.9 ms … 725.8 ms 10 runs Summary /tmp/bench/before union_obj.php ran 1.01 ± 0.03 times faster than /tmp/bench/after union_obj.php
1 parent 8e216a2 commit 0cca79a

File tree

1 file changed

+22
-26
lines changed

1 file changed

+22
-26
lines changed

Zend/zend_execute.c

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,9 +1088,7 @@ static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_inf
10881088
}
10891089

10901090
static zend_always_inline bool zend_value_instanceof_static(const zval *zv) {
1091-
if (Z_TYPE_P(zv) != IS_OBJECT) {
1092-
return 0;
1093-
}
1091+
ZEND_ASSERT(Z_TYPE_P(zv) == IS_OBJECT);
10941092

10951093
zend_class_entry *called_scope = zend_get_called_scope(EG(current_execute_data));
10961094
if (!called_scope) {
@@ -1144,9 +1142,24 @@ static zend_always_inline bool zend_check_type_slow(
11441142
const zend_type *type, zval *arg, const zend_reference *ref,
11451143
bool is_return_type, bool is_internal)
11461144
{
1147-
if (ZEND_TYPE_IS_COMPLEX(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) {
1148-
zend_class_entry *ce;
1149-
if (UNEXPECTED(ZEND_TYPE_HAS_LIST(*type))) {
1145+
const uint32_t type_mask = ZEND_TYPE_FULL_MASK(*type);
1146+
1147+
if (EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) {
1148+
if (EXPECTED(ZEND_TYPE_HAS_NAME(*type))) {
1149+
zend_class_entry *ce = zend_fetch_ce_from_type(type);
1150+
/* If we have a CE we check if it satisfies the type constraint,
1151+
* otherwise it will check if a standard type satisfies it. */
1152+
if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) {
1153+
return true;
1154+
}
1155+
}
1156+
if ((type_mask & MAY_BE_STATIC) && zend_value_instanceof_static(arg)) {
1157+
return true;
1158+
}
1159+
if ((type_mask & MAY_BE_CALLABLE) && EXPECTED(Z_OBJCE_P(arg) == zend_ce_closure)) {
1160+
return true;
1161+
}
1162+
if (EXPECTED(ZEND_TYPE_HAS_LIST(*type))) {
11501163
if (ZEND_TYPE_IS_INTERSECTION(*type)) {
11511164
return zend_check_intersection_type_from_list(ZEND_TYPE_LIST(*type), Z_OBJCE_P(arg));
11521165
} else {
@@ -1158,36 +1171,19 @@ static zend_always_inline bool zend_check_type_slow(
11581171
}
11591172
} else {
11601173
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type));
1161-
ce = zend_fetch_ce_from_type(list_type);
1174+
zend_class_entry *ce = zend_fetch_ce_from_type(list_type);
11621175
/* Instance of a single type part of a union is sufficient to pass the type check */
11631176
if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) {
11641177
return true;
11651178
}
11661179
}
11671180
} ZEND_TYPE_LIST_FOREACH_END();
11681181
}
1169-
} else {
1170-
ce = zend_fetch_ce_from_type(type);
1171-
/* If we have a CE we check if it satisfies the type constraint,
1172-
* otherwise it will check if a standard type satisfies it. */
1173-
if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) {
1174-
return true;
1175-
}
11761182
}
11771183
}
11781184

1179-
const uint32_t type_mask = ZEND_TYPE_FULL_MASK(*type);
1180-
if (
1181-
(type_mask & MAY_BE_CALLABLE)
1182-
&& (
1183-
/* Fast check for closures. */
1184-
EXPECTED(Z_OBJCE_P(arg) == zend_ce_closure)
1185-
|| zend_is_callable(arg, is_internal ? IS_CALLABLE_SUPPRESS_DEPRECATIONS : 0, NULL)
1186-
)
1187-
) {
1188-
return 1;
1189-
}
1190-
if ((type_mask & MAY_BE_STATIC) && zend_value_instanceof_static(arg)) {
1185+
if ((type_mask & MAY_BE_CALLABLE) &&
1186+
zend_is_callable(arg, is_internal ? IS_CALLABLE_SUPPRESS_DEPRECATIONS : 0, NULL)) {
11911187
return 1;
11921188
}
11931189
if (ref && ZEND_REF_HAS_TYPE_SOURCES(ref)) {

0 commit comments

Comments
 (0)