>  기사  >  백엔드 개발  >  PHP7 커널 객체에 대한 심층적인 이해

PHP7 커널 객체에 대한 심층적인 이해

Guanhui
Guanhui앞으로
2020-05-15 11:10:053727검색

PHP7 커널 객체에 대한 심층적인 이해

PHP5

늘 그렇듯이 먼저 PHP5의 zend_object를 검토하도록 안내하겠습니다(이 부분은 이전 기사에서도 다루었으므로 익숙하다면 건너뛰어도 됩니다). 관심이 있으시면 제가 10년 전에 PHP 원리를 깊이 이해하기 위해 객체를 작성하기 전에 읽어보셔도 됩니다.

PHP5에서 객체의 정의는 다음과 같습니다.

typedef struct _zend_object {
    zend_class_entry *ce;
    HashTable *properties;
    zval **properties_table;
    HashTable *guards;
} zend_object;

여기서 ce는 이 객체가 속한 클래스를 저장합니다. 속성, 속성_테이블은 선언된 속성이고 속성은 동적 속성입니다. 예:

<?php
class Foo {
    public $a = &#39;defaul property&#39;;
}
$a = New Foo();
$a->b = &#39;dynamic property&#39;;

Foo의 정의에서 공개 $a를 선언했기 때문에 $a는 알려진 선언 속성이며 저장된 위치를 포함한 가시성입니다. Properties_table에서는 선언 이후입니다.

그리고 $a->b는 우리가 동적으로 추가하는 속성입니다. 이는 선언된 속성에 속하지 않습니다.

사실 유형에서도 알 수 있듯이, Properties_table은 zval*의 배열이고, Properties는 Hashtable입니다.

guard는 __isset/__get/__set와 같은 매직 메서드를 호출할 때 중첩 보호에 주로 사용됩니다.

일반적으로 zend_object(이하 객체)는 실제로 PHP5에서 상대적으로 특별한 존재입니다. PHP5에서는 리소스와 객체만 참조로 전달되는데, 이는 할당 및 전달 시 스스로 전달된다는 의미입니다. 이 중 Zval의 참조 카운팅을 사용하는 것 외에도 개체 및 리소스도 독립적인 카운팅 시스템을 사용합니다.

zval에서 객체와 다른 유사한 문자열의 차이점도 확인할 수 있습니다.

typedef union _zvalue_value {
    long lval;
    double dval;
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;
    zend_object_value obj;
} zvalue_value;

문자열과 배열의 경우 zval은 포인터를 직접 저장하는 반면 객체의 경우 zend_object_value 구조입니다.

typedef unsigned int zend_object_handle;

typedef struct _zend_object_value {
    zend_object_handle handle;
    const zend_object_handlers *handlers;
} zend_object_value;

실제로 객체를 얻으려면 전역 객체 버킷에서 검색하기 위한 int 인덱스인 zend_object_handle을 사용해야 합니다.

ZEND_API void *zend_object_store_get_object_by_handle(zend_object_handle handle TSRMLS_DC)
{
    return EG(objects_store).object_buckets[handle].bucket.obj.object;
}

그리고 EG(objects_store).object_buckets는 저장된 배열입니다. 작성자:

typedef struct _zend_object_store_bucket {
    zend_bool destructor_called;
    zend_bool valid;
    zend_uchar apply_count;
    union _store_bucket {
        struct _store_object {
            void *object;
            zend_objects_store_dtor_t dtor;
            zend_objects_free_object_storage_t free_storage;
            zend_objects_store_clone_t clone;
            const zend_object_handlers *handlers;
            zend_uint refcount;
            gc_root_buffer *buffered;
        } obj;
        struct {
            int next;
        } free_list;
    } bucket;
} zend_object_store_bucket;

그 중, zend_object_store_bucket.bucket.obj.object는 실제 zend_object 포인터를 저장합니다. 이는 void *라는 점에 유의하세요. 이는 확장된 사용자 정의 개체 중 다수도 여기에 저장할 수 있기 때문입니다.

또한 zend_object_store_bueckt.bucket.obj.refcount도 확인했는데, 이는 제가 방금 이야기한 객체 자체의 참조 카운트입니다. 즉, zval에는 자체 참조 카운트가 있고 객체에도 자체 참조 카운트가 있습니다. .

<?php
$o1 = new Stdclass();
//o1.refcount == 1, object.refcount == 1
$o2 = $o1;
//o1.refcount == o2.refcoun == 2; object.refcount = 1;
$o3 = &$o2;
//o3.isref == o2.isref==1
//o3.refcount == o2.refcount == 2
//o1.isref == 0; o1.refcount == 1
//object.refcount == 2

이러한 방식으로 객체는 COW 메커니즘이 일반 zval과 다르다는 것을 보장할 수 있으며 객체가 참조를 전역적으로 전달할 수 있음을 보장할 수 있습니다.

zval에서 실제 객체를 얻으려면 먼저 zval.value.obj.handle을 얻은 다음 이 인덱스를 사용하여 EG(objects_store)를 쿼리해야 하는데 이는 상대적으로 비효율적이라는 것을 알 수 있습니다.

또 다른 일반적인 작업의 경우 zval 개체의 클래스를 가져올 때 함수도 호출해야 합니다.

#define Z_OBJCE(zval) zend_get_class_entry(&(zval) TSRMLS_CC)

PHP7

PHP7에서는 이전 기사에서와 마찬가지로 PHP7 커널의 ZVAL을 깊이 이해합니다. zval은 zend_object 객체의 포인터를 직접 저장합니다:

struct _zend_object {
    zend_refcounted_h gc;
    uint32_t          handle;
    zend_class_entry *ce;
    const zend_object_handlers *handlers;
    HashTable        *properties;
    zval              properties_table[1];
};

그리고 EG(objects_store)는 단순히 zend_object** 및 기타 포인터를 저장합니다:

typedef struct _zend_objects_store {
    zend_object **object_buckets;
    uint32_t top;
    uint32_t size;
    int free_list_head;
} zend_objects_store;

그리고 이전 COW 예에서 IS_OBJECT의 경우 IS_TYPE_COPYABLE을 사용하여 구별합니다. 즉, COW가 발생할 때 이 유형에 대해 IS_TYPE_COPYABLE을 설정하지 않으면 "복사"가 발생하지 않습니다.

#define IS_ARRAY_EX  (IS_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COLLECTABLE | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT))
#define IS_OBJECT_EX (IS_OBJECT | ((IS_TYPE_REFCOUNTED | IS_TYPE_COLLECTABLE) << Z_TYPE_FLAGS_SHIFT))

위와 같이 ARRAY에 대해서는 IS_TYPE_REFCOUNTED, IS_TYPE_COLLECTABLE, IS_TYPE_COPYABLE이 정의되어 있지만 OBJECT에 대해서는 IS_TYPE_COPYABLE이 정의되어 있는 것을 알 수 있습니다. 누락되었습니다.

SEPARATE_ZVAL:

#define SEPARATE_ZVAL(zv) do {                          \
        zval *_zv = (zv);                               \
        if (Z_REFCOUNTED_P(_zv) ||                      \
            Z_IMMUTABLE_P(_zv)) {                       \
            if (Z_REFCOUNT_P(_zv) > 1) {                \
                if (Z_COPYABLE_P(_zv) ||                \
                    Z_IMMUTABLE_P(_zv)) {               \
                    if (!Z_IMMUTABLE_P(_zv)) {          \
                        Z_DELREF_P(_zv);                \
                    }                                   \
                    zval_copy_ctor_func(_zv);           \
                } else if (Z_ISREF_P(_zv)) {            \
                    Z_DELREF_P(_zv);                    \
                    ZVAL_DUP(_zv, Z_REFVAL_P(_zv));     \
                }                                       \
            }                                           \
        }                                               \
    } while (0)

Z_COPYABLE_P가 아닌 경우 쓰기 시간 분리가 발생하지 않습니다.

여기 일부 학생들은 zend_object*가 zval에 직접 저장되었으므로 왜 EG(objects_store)가 필요한가요?라고 물을 수 있습니다.

여기에는 두 가지 주요 이유가 있습니다.

1. 객체에는 순환 참조가 있으므로 PHP 요청이 끝날 때 모든 객체의 소멸자가 호출되도록 해야 합니다. 그러면 살아남은 모든 객체를 빠르게 탐색하는 방법은 무엇입니까? EG(objects_store)는 매우 좋은 선택입니다.

2. PHPNG를 개발할 때 이전 버전과의 호환성을 최대화하려면 객체 핸들을 얻기 위한 인터페이스를 보장해야 하며 이 핸들은 원래 의미를 유지해야 합니다.

하지만 실제로 EG(objects_store)는 더 이상 많이 사용되지 않으며 나중에 제거할 수 있습니다.

좋아요, 다음에는 또 다른 문제가 발생합니다. zend_object의 정의를 다시 살펴보고 마지막에 Properties_table[1]을 살펴보겠습니다. 즉, 이제 개체의 속성과 함께 메모리를 할당하겠습니다. 이는 캐시 친화적입니다. 그러나 한 가지 변경 사항은 이제 zend_object 구조가 더 길어질 수 있다는 것입니다.

내가 PHPNG를 작성할 때 문제가 발생했습니다. PHP5 시대에는 많은 사용자 정의 개체가 다음과 같이 정의되었습니다(예: mysqli).

typedef struct _mysqli_object {
    zend_object         zo;
    void                *ptr;
    HashTable           *prop_handler;
} mysqli_object; /* extends zend_object */

也就是说zend_object都在自定义的内部类的头部,这样当然有一个好处是可以很方便的做cast, 但是因为目前zend_object变成变长了,并且更严重的是你并不知道用户在PHP继承了你这个类以后,他新增了多少属性的定义。

于是没有办法,在写PHPNG的时候,我做了大量的调整如下(体力活):

typedef struct _mysqli_object {
    void                *ptr;
    HashTable           *prop_handler;
    zend_object         zo;
} mysqli_object; /* extends zend_object */

也就是把zend_object从头部,挪到了尾部,那为了可以从zend_object取得自定义对象,我们需要新增定义:

static inline mysqli_object *php_mysqli_fetch_object(zend_object *obj) {
    return (mysqli_object *)((char*)(obj) - XtOffsetOf(mysqli_object, zo));
}

这样类似的代码大家应该可以在很多使用了自定义对象的扩展中看到。

这样一来就规避了这个问题, 而在实际的分配自定义对象的时候,我们也需要采用如下的方法:

obj = ecalloc(1, sizeof(mysqli_object) + zend_object_properties_size(class_type));

这块,大家在写扩展的时候,如果用到自定义的类,一定要注意。

而之前在PHP5中的guard, 我们也知道并不是所有的类都会申明魔术方法,在PHP5中把guard放在object中会在大部分情况下都是浪费内存, 所以在PHP7中会,我们会根据一个类是否申明了魔术方法(IS_OBJ_HAS_GUARDS)来决定要不要分配,而具体的分配地方也放在了properties_table的末尾:

if (GC_FLAGS(zobj) & IS_OBJ_HAS_GUARDS) {
        guards = Z_PTR(zobj->properties_table[zobj->ce->default_properties_count]);
....
}

从而可以在大部分情况下,节省一个指针的内存分配。

最后就是, PHP7中在取一个对象的类的时候,就会非常方便了, 直接zvalu.value.obj->ce即可,一些类所自定的handler也就可以很便捷的访问到了, 性能提升明显。

推荐教程:《PHP7》《PHP教程

위 내용은 PHP7 커널 객체에 대한 심층적인 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 laruence.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제