Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dstogov committed Feb 9, 2021
1 parent 550aee0 commit 4b79dba
Show file tree
Hide file tree
Showing 41 changed files with 1,595 additions and 1,011 deletions.
3 changes: 3 additions & 0 deletions NEWS
Expand Up @@ -21,6 +21,9 @@ PHP NEWS
. Fixed bug #80330 (Replace language in APIs and source code/docs).
(Darek Ślusarczyk)

- Opcache:
. Added inheritance cache. (Dmitry)

- OpenSSL:
. Bump minimal OpenSSL version to 1.0.2. (Jakub Zelenka)

Expand Down
33 changes: 33 additions & 0 deletions Zend/tests/anon/015.phpt
@@ -0,0 +1,33 @@
--TEST--
static variables in methods inherited from parent class
--FILE--
<?php
class C {
function foo ($y = null) {
static $x = null;
if (!is_null($y)) {
$x = [$y];
}
return $x;
}
}
$c = new C();
$c->foo(42);
$d = new class extends C {};
var_dump($d->foo());
var_dump($d->foo(24));
var_dump($c->foo());
?>
--EXPECT--
array(1) {
[0]=>
int(42)
}
array(1) {
[0]=>
int(24)
}
array(1) {
[0]=>
int(42)
}
35 changes: 35 additions & 0 deletions Zend/tests/anon/016.phpt
@@ -0,0 +1,35 @@
--TEST--
static variables in methods inherited from parent class (can't cache objects)
--FILE--
<?php
class C {
function foo ($y = null) {
static $x = null;
if (!is_null($y)) {
$x = [$y];
}
return $x;
}
}
$c = new C();
$c->foo(new stdClass);
$d = new class extends C {};
var_dump($d->foo());
var_dump($d->foo(24));
var_dump($c->foo());
?>
--EXPECT--
array(1) {
[0]=>
object(stdClass)#2 (0) {
}
}
array(1) {
[0]=>
int(24)
}
array(1) {
[0]=>
object(stdClass)#2 (0) {
}
}
1 change: 1 addition & 0 deletions Zend/zend.c
Expand Up @@ -660,6 +660,7 @@ static void compiler_globals_ctor(zend_compiler_globals *compiler_globals) /* {{
zend_hash_copy(compiler_globals->auto_globals, global_auto_globals_table, auto_global_copy_ctor);

compiler_globals->script_encoding_list = NULL;
compiler_globals->current_linking_class = NULL;

#if ZEND_MAP_PTR_KIND == ZEND_MAP_PTR_KIND_PTR_OR_OFFSET
/* Map region is going to be created and resized at run-time. */
Expand Down
25 changes: 25 additions & 0 deletions Zend/zend.h
Expand Up @@ -107,6 +107,28 @@ typedef struct _zend_trait_alias {
uint32_t modifiers;
} zend_trait_alias;

typedef struct _zend_class_mutable_data {
zval *default_properties_table;
HashTable *constants_table;
uint32_t ce_flags;
} zend_class_mutable_data;

typedef struct _zend_class_dependency {
zend_string *name;
zend_class_entry *ce;
} zend_class_dependency;

typedef struct _zend_inheritance_cache_entry zend_inheritance_cache_entry;

struct _zend_inheritance_cache_entry {
zend_inheritance_cache_entry *next;
zend_class_entry *ce;
zend_class_entry *parent;
zend_class_dependency *dependencies;
uint32_t dependencies_count;
zend_class_entry *traits_and_interfaces[1];
};

struct _zend_class_entry {
char type;
zend_string *name;
Expand All @@ -127,6 +149,9 @@ struct _zend_class_entry {
HashTable properties_info;
HashTable constants_table;

ZEND_MAP_PTR_DEF(zend_class_mutable_data*, mutable_data);
zend_inheritance_cache_entry *inheritance_cache;

struct _zend_property_info **properties_info_table;

zend_function *constructor;
Expand Down
159 changes: 143 additions & 16 deletions Zend/zend_API.c
Expand Up @@ -1213,39 +1213,147 @@ ZEND_API void zend_merge_properties(zval *obj, HashTable *properties) /* {{{ */
}
/* }}} */

static zend_class_mutable_data *zend_allocate_mutable_data(zend_class_entry *class_type) /* {{{ */
{
zend_class_mutable_data *mutable_data;

ZEND_ASSERT(class_type->ce_flags & ZEND_ACC_IMMUTABLE);
ZEND_ASSERT(ZEND_MAP_PTR(class_type->mutable_data) != NULL);
ZEND_ASSERT(ZEND_MAP_PTR_GET_IMM(class_type->mutable_data) == NULL);

mutable_data = zend_arena_alloc(&CG(arena), sizeof(zend_class_mutable_data));
memset(mutable_data, 0, sizeof(zend_class_mutable_data));
mutable_data->ce_flags = class_type->ce_flags;
ZEND_MAP_PTR_SET_IMM(class_type->mutable_data, mutable_data);

return mutable_data;
}
/* }}} */

ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_type) /* {{{ */
{
zend_class_mutable_data *mutable_data;
HashTable *constants_table;
zend_string *key;
zend_class_constant *new_c, *c;

constants_table = zend_arena_alloc(&CG(arena), sizeof(HashTable));
zend_hash_init(constants_table, zend_hash_num_elements(&class_type->constants_table), NULL, NULL, 0);
zend_hash_extend(constants_table, zend_hash_num_elements(&class_type->constants_table), 0);

ZEND_HASH_FOREACH_STR_KEY_PTR(&class_type->constants_table, key, c) {
if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
new_c = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant));
memcpy(new_c, c, sizeof(zend_class_constant));
c = new_c;
}
_zend_hash_append_ptr(constants_table, key, c);
} ZEND_HASH_FOREACH_END();

ZEND_ASSERT(class_type->ce_flags & ZEND_ACC_IMMUTABLE);
ZEND_ASSERT(ZEND_MAP_PTR(class_type->mutable_data) != NULL);

mutable_data = ZEND_MAP_PTR_GET_IMM(class_type->mutable_data);
if (!mutable_data) {
mutable_data = zend_allocate_mutable_data(class_type);
}

mutable_data->constants_table = constants_table;

return constants_table;
}

ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) /* {{{ */
{
if (!(class_type->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
zend_class_constant *c;
zval *val;
zend_property_info *prop_info;
zend_class_mutable_data *mutable_data = NULL;
zval *default_properties_table = NULL;
zval *static_members_table = NULL;
zend_class_constant *c;
zval *val;
zend_property_info *prop_info;
uint32_t ce_flags;

if (class_type->parent) {
if (UNEXPECTED(zend_update_class_constants(class_type->parent) != SUCCESS)) {
return FAILURE;
ce_flags = class_type->ce_flags;

if (ce_flags & ZEND_ACC_CONSTANTS_UPDATED) {
return SUCCESS;
}

if (ce_flags & ZEND_ACC_IMMUTABLE) {
mutable_data = ZEND_MAP_PTR_GET_IMM(class_type->mutable_data);
if (mutable_data) {
ce_flags = mutable_data->ce_flags;
if (ce_flags & ZEND_ACC_CONSTANTS_UPDATED) {
return SUCCESS;
}
} else {
mutable_data = zend_allocate_mutable_data(class_type);
}
}

ZEND_HASH_FOREACH_PTR(&class_type->constants_table, c) {
val = &c->value;
if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
if (class_type->parent) {
if (UNEXPECTED(zend_update_class_constants(class_type->parent) != SUCCESS)) {
return FAILURE;
}
}

if (ce_flags & ZEND_ACC_HAS_AST_CONSTANTS) {
HashTable *constants_table;

if (ce_flags & ZEND_ACC_IMMUTABLE) {
constants_table = mutable_data->constants_table;
if (!constants_table) {
constants_table = zend_separate_class_constants_table(class_type);
}
} else {
constants_table = &class_type->constants_table;
}
ZEND_HASH_FOREACH_PTR(constants_table, c) {
if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
val = &c->value;
if (UNEXPECTED(zval_update_constant_ex(val, c->ce) != SUCCESS)) {
return FAILURE;
}
}
} ZEND_HASH_FOREACH_END();
}

if (class_type->default_static_members_count && !CE_STATIC_MEMBERS(class_type)) {
if (class_type->type == ZEND_INTERNAL_CLASS || (class_type->ce_flags & (ZEND_ACC_IMMUTABLE|ZEND_ACC_PRELOADED))) {
if (class_type->default_static_members_count) {
static_members_table = CE_STATIC_MEMBERS(class_type);
if (!static_members_table) {
if (class_type->type == ZEND_INTERNAL_CLASS || (ce_flags & (ZEND_ACC_IMMUTABLE|ZEND_ACC_PRELOADED))) {
zend_class_init_statics(class_type);
static_members_table = CE_STATIC_MEMBERS(class_type);
}
}
}

default_properties_table = class_type->default_properties_table;
if ((ce_flags & ZEND_ACC_IMMUTABLE)
&& (ce_flags & ZEND_ACC_HAS_AST_PROPERTIES)) {
zval *src, *dst, *end;

default_properties_table = mutable_data->default_properties_table;
if (!default_properties_table) {
default_properties_table = zend_arena_alloc(&CG(arena), sizeof(zval) * class_type->default_properties_count);
src = class_type->default_properties_table;
dst = default_properties_table;
end = dst + class_type->default_properties_count;
do {
ZVAL_COPY_VALUE_PROP(dst, src);
src++;
dst++;
} while (dst != end);
mutable_data->default_properties_table = default_properties_table;
}
}

if (ce_flags & (ZEND_ACC_HAS_AST_PROPERTIES|ZEND_ACC_HAS_AST_STATICS)) {
ZEND_HASH_FOREACH_PTR(&class_type->properties_info, prop_info) {
if (prop_info->flags & ZEND_ACC_STATIC) {
val = CE_STATIC_MEMBERS(class_type) + prop_info->offset;
val = static_members_table + prop_info->offset;
} else {
val = (zval*)((char*)class_type->default_properties_table + prop_info->offset - OBJ_PROP_TO_OFFSET(0));
val = (zval*)((char*)default_properties_table + prop_info->offset - OBJ_PROP_TO_OFFSET(0));
}
if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
if (ZEND_TYPE_IS_SET(prop_info->type)) {
Expand All @@ -1268,8 +1376,21 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) /
}
}
} ZEND_HASH_FOREACH_END();
}

class_type->ce_flags |= ZEND_ACC_CONSTANTS_UPDATED;
ce_flags |= ZEND_ACC_CONSTANTS_UPDATED;
ce_flags &= ~ZEND_ACC_HAS_AST_CONSTANTS;
ce_flags &= ~ZEND_ACC_HAS_AST_PROPERTIES;
if (class_type->ce_flags & ZEND_ACC_IMMUTABLE) {
ce_flags &= ~ZEND_ACC_HAS_AST_STATICS;
if (mutable_data) {
mutable_data->ce_flags = ce_flags;
}
} else {
if (!(ce_flags & ZEND_ACC_PRELOADED)) {
ce_flags &= ~ZEND_ACC_HAS_AST_STATICS;
}
class_type->ce_flags = ce_flags;
}

return SUCCESS;
Expand All @@ -1279,7 +1400,7 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) /
static zend_always_inline void _object_properties_init(zend_object *object, zend_class_entry *class_type) /* {{{ */
{
if (class_type->default_properties_count) {
zval *src = class_type->default_properties_table;
zval *src = CE_DEFAULT_PROPERTIES_TABLE(class_type);
zval *dst = object->properties_table;
zval *end = src + class_type->default_properties_count;

Expand Down Expand Up @@ -3809,6 +3930,11 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z
property_info = zend_arena_alloc(&CG(arena), sizeof(zend_property_info));
if (Z_TYPE_P(property) == IS_CONSTANT_AST) {
ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED;
if (access_type & ZEND_ACC_STATIC) {
ce->ce_flags |= ZEND_ACC_HAS_AST_STATICS;
} else {
ce->ce_flags |= ZEND_ACC_HAS_AST_PROPERTIES;
}
}
}

Expand Down Expand Up @@ -4124,6 +4250,7 @@ ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *c
c->ce = ce;
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED;
ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS;
}

if (!zend_hash_add_ptr(&ce->constants_table, name, c)) {
Expand Down
33 changes: 33 additions & 0 deletions Zend/zend_API.h
Expand Up @@ -278,6 +278,12 @@ typedef struct _zend_fcall_info_cache {
#define CE_STATIC_MEMBERS(ce) \
((zval*)ZEND_MAP_PTR_GET((ce)->static_members_table))

#define CE_CONSTANTS_TABLE(ce) \
zend_class_constants_table(ce)

#define CE_DEFAULT_PROPERTIES_TABLE(ce) \
zend_class_default_properties_table(ce)

#define ZEND_FCI_INITIALIZED(fci) ((fci).size != 0)

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

ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type);
ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_type);

static zend_always_inline HashTable *zend_class_constants_table(zend_class_entry *ce) {
if ((ce->ce_flags & ZEND_ACC_HAS_AST_CONSTANTS)
&& (ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
zend_class_mutable_data *mutable_data =
(zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data);
if (mutable_data && mutable_data->constants_table) {
return mutable_data->constants_table;
} else {
return zend_separate_class_constants_table(ce);
}
} else {
return &ce->constants_table;
}
}

static zend_always_inline zval *zend_class_default_properties_table(zend_class_entry *ce) {
if ((ce->ce_flags & ZEND_ACC_HAS_AST_PROPERTIES)
&& (ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
zend_class_mutable_data *mutable_data =
(zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data);
return mutable_data->default_properties_table;
} else {
return ce->default_properties_table;
}
}

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

0 comments on commit 4b79dba

Please sign in to comment.