Skip to content

Commit 4b79dba

Browse files
committed
Added Inheritance Cache.
This is a new transparent technology that eliminates overhead of PHP class inheritance. PHP classes are compiled and cached (by opcahce) separately, however their "linking" was done at run-time - on each request. The process of "linking" may involve a number of compatibility checks and borrowing methods/properties/constants form parent and traits. This takes significant time, but the result is the same on each request. Inheritance Cache performs "linking" for unique set of all the depending classes (parent, interfaces, traits, property types, method types involved into compatibility checks) once and stores result in opcache shared memory. As a part of the this patch, I removed limitations for immutable classes (unresolved constants, typed properties and covariant type checks). So now all classes stored in opcache are "immutable". They may be lazily loaded into process memory, if necessary, but this usually occurs just once (on first linking). The patch shows 8% improvement on Symphony "Hello World" app.
1 parent 550aee0 commit 4b79dba

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1595
-1011
lines changed

NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ PHP NEWS
2121
. Fixed bug #80330 (Replace language in APIs and source code/docs).
2222
(Darek Ślusarczyk)
2323

24+
- Opcache:
25+
. Added inheritance cache. (Dmitry)
26+
2427
- OpenSSL:
2528
. Bump minimal OpenSSL version to 1.0.2. (Jakub Zelenka)
2629

Zend/tests/anon/015.phpt

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
static variables in methods inherited from parent class
3+
--FILE--
4+
<?php
5+
class C {
6+
function foo ($y = null) {
7+
static $x = null;
8+
if (!is_null($y)) {
9+
$x = [$y];
10+
}
11+
return $x;
12+
}
13+
}
14+
$c = new C();
15+
$c->foo(42);
16+
$d = new class extends C {};
17+
var_dump($d->foo());
18+
var_dump($d->foo(24));
19+
var_dump($c->foo());
20+
?>
21+
--EXPECT--
22+
array(1) {
23+
[0]=>
24+
int(42)
25+
}
26+
array(1) {
27+
[0]=>
28+
int(24)
29+
}
30+
array(1) {
31+
[0]=>
32+
int(42)
33+
}

Zend/tests/anon/016.phpt

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
static variables in methods inherited from parent class (can't cache objects)
3+
--FILE--
4+
<?php
5+
class C {
6+
function foo ($y = null) {
7+
static $x = null;
8+
if (!is_null($y)) {
9+
$x = [$y];
10+
}
11+
return $x;
12+
}
13+
}
14+
$c = new C();
15+
$c->foo(new stdClass);
16+
$d = new class extends C {};
17+
var_dump($d->foo());
18+
var_dump($d->foo(24));
19+
var_dump($c->foo());
20+
?>
21+
--EXPECT--
22+
array(1) {
23+
[0]=>
24+
object(stdClass)#2 (0) {
25+
}
26+
}
27+
array(1) {
28+
[0]=>
29+
int(24)
30+
}
31+
array(1) {
32+
[0]=>
33+
object(stdClass)#2 (0) {
34+
}
35+
}

Zend/zend.c

+1
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ static void compiler_globals_ctor(zend_compiler_globals *compiler_globals) /* {{
660660
zend_hash_copy(compiler_globals->auto_globals, global_auto_globals_table, auto_global_copy_ctor);
661661

662662
compiler_globals->script_encoding_list = NULL;
663+
compiler_globals->current_linking_class = NULL;
663664

664665
#if ZEND_MAP_PTR_KIND == ZEND_MAP_PTR_KIND_PTR_OR_OFFSET
665666
/* Map region is going to be created and resized at run-time. */

Zend/zend.h

+25
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,28 @@ typedef struct _zend_trait_alias {
107107
uint32_t modifiers;
108108
} zend_trait_alias;
109109

110+
typedef struct _zend_class_mutable_data {
111+
zval *default_properties_table;
112+
HashTable *constants_table;
113+
uint32_t ce_flags;
114+
} zend_class_mutable_data;
115+
116+
typedef struct _zend_class_dependency {
117+
zend_string *name;
118+
zend_class_entry *ce;
119+
} zend_class_dependency;
120+
121+
typedef struct _zend_inheritance_cache_entry zend_inheritance_cache_entry;
122+
123+
struct _zend_inheritance_cache_entry {
124+
zend_inheritance_cache_entry *next;
125+
zend_class_entry *ce;
126+
zend_class_entry *parent;
127+
zend_class_dependency *dependencies;
128+
uint32_t dependencies_count;
129+
zend_class_entry *traits_and_interfaces[1];
130+
};
131+
110132
struct _zend_class_entry {
111133
char type;
112134
zend_string *name;
@@ -127,6 +149,9 @@ struct _zend_class_entry {
127149
HashTable properties_info;
128150
HashTable constants_table;
129151

152+
ZEND_MAP_PTR_DEF(zend_class_mutable_data*, mutable_data);
153+
zend_inheritance_cache_entry *inheritance_cache;
154+
130155
struct _zend_property_info **properties_info_table;
131156

132157
zend_function *constructor;

Zend/zend_API.c

+143-16
Original file line numberDiff line numberDiff line change
@@ -1213,39 +1213,147 @@ ZEND_API void zend_merge_properties(zval *obj, HashTable *properties) /* {{{ */
12131213
}
12141214
/* }}} */
12151215

1216+
static zend_class_mutable_data *zend_allocate_mutable_data(zend_class_entry *class_type) /* {{{ */
1217+
{
1218+
zend_class_mutable_data *mutable_data;
1219+
1220+
ZEND_ASSERT(class_type->ce_flags & ZEND_ACC_IMMUTABLE);
1221+
ZEND_ASSERT(ZEND_MAP_PTR(class_type->mutable_data) != NULL);
1222+
ZEND_ASSERT(ZEND_MAP_PTR_GET_IMM(class_type->mutable_data) == NULL);
1223+
1224+
mutable_data = zend_arena_alloc(&CG(arena), sizeof(zend_class_mutable_data));
1225+
memset(mutable_data, 0, sizeof(zend_class_mutable_data));
1226+
mutable_data->ce_flags = class_type->ce_flags;
1227+
ZEND_MAP_PTR_SET_IMM(class_type->mutable_data, mutable_data);
1228+
1229+
return mutable_data;
1230+
}
1231+
/* }}} */
1232+
1233+
ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_type) /* {{{ */
1234+
{
1235+
zend_class_mutable_data *mutable_data;
1236+
HashTable *constants_table;
1237+
zend_string *key;
1238+
zend_class_constant *new_c, *c;
1239+
1240+
constants_table = zend_arena_alloc(&CG(arena), sizeof(HashTable));
1241+
zend_hash_init(constants_table, zend_hash_num_elements(&class_type->constants_table), NULL, NULL, 0);
1242+
zend_hash_extend(constants_table, zend_hash_num_elements(&class_type->constants_table), 0);
1243+
1244+
ZEND_HASH_FOREACH_STR_KEY_PTR(&class_type->constants_table, key, c) {
1245+
if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
1246+
new_c = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant));
1247+
memcpy(new_c, c, sizeof(zend_class_constant));
1248+
c = new_c;
1249+
}
1250+
_zend_hash_append_ptr(constants_table, key, c);
1251+
} ZEND_HASH_FOREACH_END();
1252+
1253+
ZEND_ASSERT(class_type->ce_flags & ZEND_ACC_IMMUTABLE);
1254+
ZEND_ASSERT(ZEND_MAP_PTR(class_type->mutable_data) != NULL);
1255+
1256+
mutable_data = ZEND_MAP_PTR_GET_IMM(class_type->mutable_data);
1257+
if (!mutable_data) {
1258+
mutable_data = zend_allocate_mutable_data(class_type);
1259+
}
1260+
1261+
mutable_data->constants_table = constants_table;
1262+
1263+
return constants_table;
1264+
}
1265+
12161266
ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) /* {{{ */
12171267
{
1218-
if (!(class_type->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
1219-
zend_class_constant *c;
1220-
zval *val;
1221-
zend_property_info *prop_info;
1268+
zend_class_mutable_data *mutable_data = NULL;
1269+
zval *default_properties_table = NULL;
1270+
zval *static_members_table = NULL;
1271+
zend_class_constant *c;
1272+
zval *val;
1273+
zend_property_info *prop_info;
1274+
uint32_t ce_flags;
12221275

1223-
if (class_type->parent) {
1224-
if (UNEXPECTED(zend_update_class_constants(class_type->parent) != SUCCESS)) {
1225-
return FAILURE;
1276+
ce_flags = class_type->ce_flags;
1277+
1278+
if (ce_flags & ZEND_ACC_CONSTANTS_UPDATED) {
1279+
return SUCCESS;
1280+
}
1281+
1282+
if (ce_flags & ZEND_ACC_IMMUTABLE) {
1283+
mutable_data = ZEND_MAP_PTR_GET_IMM(class_type->mutable_data);
1284+
if (mutable_data) {
1285+
ce_flags = mutable_data->ce_flags;
1286+
if (ce_flags & ZEND_ACC_CONSTANTS_UPDATED) {
1287+
return SUCCESS;
12261288
}
1289+
} else {
1290+
mutable_data = zend_allocate_mutable_data(class_type);
12271291
}
1292+
}
12281293

1229-
ZEND_HASH_FOREACH_PTR(&class_type->constants_table, c) {
1230-
val = &c->value;
1231-
if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
1294+
if (class_type->parent) {
1295+
if (UNEXPECTED(zend_update_class_constants(class_type->parent) != SUCCESS)) {
1296+
return FAILURE;
1297+
}
1298+
}
1299+
1300+
if (ce_flags & ZEND_ACC_HAS_AST_CONSTANTS) {
1301+
HashTable *constants_table;
1302+
1303+
if (ce_flags & ZEND_ACC_IMMUTABLE) {
1304+
constants_table = mutable_data->constants_table;
1305+
if (!constants_table) {
1306+
constants_table = zend_separate_class_constants_table(class_type);
1307+
}
1308+
} else {
1309+
constants_table = &class_type->constants_table;
1310+
}
1311+
ZEND_HASH_FOREACH_PTR(constants_table, c) {
1312+
if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
1313+
val = &c->value;
12321314
if (UNEXPECTED(zval_update_constant_ex(val, c->ce) != SUCCESS)) {
12331315
return FAILURE;
12341316
}
12351317
}
12361318
} ZEND_HASH_FOREACH_END();
1319+
}
12371320

1238-
if (class_type->default_static_members_count && !CE_STATIC_MEMBERS(class_type)) {
1239-
if (class_type->type == ZEND_INTERNAL_CLASS || (class_type->ce_flags & (ZEND_ACC_IMMUTABLE|ZEND_ACC_PRELOADED))) {
1321+
if (class_type->default_static_members_count) {
1322+
static_members_table = CE_STATIC_MEMBERS(class_type);
1323+
if (!static_members_table) {
1324+
if (class_type->type == ZEND_INTERNAL_CLASS || (ce_flags & (ZEND_ACC_IMMUTABLE|ZEND_ACC_PRELOADED))) {
12401325
zend_class_init_statics(class_type);
1326+
static_members_table = CE_STATIC_MEMBERS(class_type);
12411327
}
12421328
}
1329+
}
1330+
1331+
default_properties_table = class_type->default_properties_table;
1332+
if ((ce_flags & ZEND_ACC_IMMUTABLE)
1333+
&& (ce_flags & ZEND_ACC_HAS_AST_PROPERTIES)) {
1334+
zval *src, *dst, *end;
1335+
1336+
default_properties_table = mutable_data->default_properties_table;
1337+
if (!default_properties_table) {
1338+
default_properties_table = zend_arena_alloc(&CG(arena), sizeof(zval) * class_type->default_properties_count);
1339+
src = class_type->default_properties_table;
1340+
dst = default_properties_table;
1341+
end = dst + class_type->default_properties_count;
1342+
do {
1343+
ZVAL_COPY_VALUE_PROP(dst, src);
1344+
src++;
1345+
dst++;
1346+
} while (dst != end);
1347+
mutable_data->default_properties_table = default_properties_table;
1348+
}
1349+
}
12431350

1351+
if (ce_flags & (ZEND_ACC_HAS_AST_PROPERTIES|ZEND_ACC_HAS_AST_STATICS)) {
12441352
ZEND_HASH_FOREACH_PTR(&class_type->properties_info, prop_info) {
12451353
if (prop_info->flags & ZEND_ACC_STATIC) {
1246-
val = CE_STATIC_MEMBERS(class_type) + prop_info->offset;
1354+
val = static_members_table + prop_info->offset;
12471355
} else {
1248-
val = (zval*)((char*)class_type->default_properties_table + prop_info->offset - OBJ_PROP_TO_OFFSET(0));
1356+
val = (zval*)((char*)default_properties_table + prop_info->offset - OBJ_PROP_TO_OFFSET(0));
12491357
}
12501358
if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
12511359
if (ZEND_TYPE_IS_SET(prop_info->type)) {
@@ -1268,8 +1376,21 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) /
12681376
}
12691377
}
12701378
} ZEND_HASH_FOREACH_END();
1379+
}
12711380

1272-
class_type->ce_flags |= ZEND_ACC_CONSTANTS_UPDATED;
1381+
ce_flags |= ZEND_ACC_CONSTANTS_UPDATED;
1382+
ce_flags &= ~ZEND_ACC_HAS_AST_CONSTANTS;
1383+
ce_flags &= ~ZEND_ACC_HAS_AST_PROPERTIES;
1384+
if (class_type->ce_flags & ZEND_ACC_IMMUTABLE) {
1385+
ce_flags &= ~ZEND_ACC_HAS_AST_STATICS;
1386+
if (mutable_data) {
1387+
mutable_data->ce_flags = ce_flags;
1388+
}
1389+
} else {
1390+
if (!(ce_flags & ZEND_ACC_PRELOADED)) {
1391+
ce_flags &= ~ZEND_ACC_HAS_AST_STATICS;
1392+
}
1393+
class_type->ce_flags = ce_flags;
12731394
}
12741395

12751396
return SUCCESS;
@@ -1279,7 +1400,7 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) /
12791400
static zend_always_inline void _object_properties_init(zend_object *object, zend_class_entry *class_type) /* {{{ */
12801401
{
12811402
if (class_type->default_properties_count) {
1282-
zval *src = class_type->default_properties_table;
1403+
zval *src = CE_DEFAULT_PROPERTIES_TABLE(class_type);
12831404
zval *dst = object->properties_table;
12841405
zval *end = src + class_type->default_properties_count;
12851406

@@ -3809,6 +3930,11 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z
38093930
property_info = zend_arena_alloc(&CG(arena), sizeof(zend_property_info));
38103931
if (Z_TYPE_P(property) == IS_CONSTANT_AST) {
38113932
ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED;
3933+
if (access_type & ZEND_ACC_STATIC) {
3934+
ce->ce_flags |= ZEND_ACC_HAS_AST_STATICS;
3935+
} else {
3936+
ce->ce_flags |= ZEND_ACC_HAS_AST_PROPERTIES;
3937+
}
38123938
}
38133939
}
38143940

@@ -4124,6 +4250,7 @@ ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *c
41244250
c->ce = ce;
41254251
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
41264252
ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED;
4253+
ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS;
41274254
}
41284255

41294256
if (!zend_hash_add_ptr(&ce->constants_table, name, c)) {

Zend/zend_API.h

+33
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,12 @@ typedef struct _zend_fcall_info_cache {
278278
#define CE_STATIC_MEMBERS(ce) \
279279
((zval*)ZEND_MAP_PTR_GET((ce)->static_members_table))
280280

281+
#define CE_CONSTANTS_TABLE(ce) \
282+
zend_class_constants_table(ce)
283+
284+
#define CE_DEFAULT_PROPERTIES_TABLE(ce) \
285+
zend_class_default_properties_table(ce)
286+
281287
#define ZEND_FCI_INITIALIZED(fci) ((fci).size != 0)
282288

283289
ZEND_API int zend_next_free_module(void);
@@ -382,6 +388,33 @@ ZEND_API void zend_declare_class_constant_stringl(zend_class_entry *ce, const ch
382388
ZEND_API void zend_declare_class_constant_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value);
383389

384390
ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type);
391+
ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_type);
392+
393+
static zend_always_inline HashTable *zend_class_constants_table(zend_class_entry *ce) {
394+
if ((ce->ce_flags & ZEND_ACC_HAS_AST_CONSTANTS)
395+
&& (ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
396+
zend_class_mutable_data *mutable_data =
397+
(zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data);
398+
if (mutable_data && mutable_data->constants_table) {
399+
return mutable_data->constants_table;
400+
} else {
401+
return zend_separate_class_constants_table(ce);
402+
}
403+
} else {
404+
return &ce->constants_table;
405+
}
406+
}
407+
408+
static zend_always_inline zval *zend_class_default_properties_table(zend_class_entry *ce) {
409+
if ((ce->ce_flags & ZEND_ACC_HAS_AST_PROPERTIES)
410+
&& (ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
411+
zend_class_mutable_data *mutable_data =
412+
(zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data);
413+
return mutable_data->default_properties_table;
414+
} else {
415+
return ce->default_properties_table;
416+
}
417+
}
385418

386419
ZEND_API void zend_update_property_ex(zend_class_entry *scope, zend_object *object, zend_string *name, zval *value);
387420
ZEND_API void zend_update_property(zend_class_entry *scope, zend_object *object, const char *name, size_t name_length, zval *value);

0 commit comments

Comments
 (0)