今天小編就為大家帶來一篇介紹PHP內核Object的文章。小編覺得挺實用的,為此分享給大家做個參考。一起跟隨小編過來看看吧。
站在用戶的角度思考問題,與客戶深入溝通,找到嘉興網站設計與嘉興網站推廣的解決方案,憑借多年的經驗,讓設計與互聯網技術結合,創造個性化、用戶體驗好的作品,建站類型包括:成都網站建設、成都做網站、企業官網、英文網站、手機端網站、網站推廣、空間域名、網站空間、企業郵箱。業務覆蓋嘉興地區。
PHP5中,對象的定義如下:
typedef struct _zend_object { zend_class_entry *ce; HashTable *properties; zval **properties_table; HashTable *guards; } zend_object;
其中ce存儲了這個對象所屬的類, 關于properties_table和properties, properties_table是申明的屬性,properties是動態屬性,也就是比如:
<?php class Foo { public $a = 'defaul property'; } $a = New Foo(); $a->b = 'dynamic property';
因為在Foo的定義中,我們申明了public $a, 那么$a就是已知的申明屬性,它的可見性,包括在properties_table中存儲的位置都是在申明后就確定的。
而$a->b, 是我們動態給添加的屬性,它不屬于已經申明的屬性,這個會存儲在properties中。
其實從類型上也能看出來, properties_table是zval*的數組,而properties是Hashtable。
guards主要用在魔術方法調用的時候嵌套保護, 比如__isset/__get/__set。
總體來說, zend_object(以下簡稱object)在PHP5中其實是一種相對特殊的存在, 在PHP5中,只有resource和object是引用傳遞,也就是說在賦值,傳遞的時候都是傳遞的本身,也正因為如此,Object和Resource除了使用了Zval的引用計數以外,還采用了一套獨立自身的計數系統。
這個我們從zval中也能看出object和其他的類似字符串的的不同:
typedef union _zvalue_value { long lval; double dval; struct { char *val; int len; } str; HashTable *ht; zend_object_value obj; } zvalue_value;
對于字符串和數組,zval中都直接保存它們的指針,而對于object卻是一個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;
真正獲取對象是需要通過這個zend_object_handle,也就是一個int的索引去全局的object buckets中查找:
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, 這個既是我剛剛講的object自身的引用計數,也就是zval有一套自己的引用計數,object也有一套引用計數。
<?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
這樣,可以讓object可以保證不同于普通的zval的COW機制,可以保證object可以全局傳引用。
可見,從一個zval到取到實際的object,我們需要首先獲取zval.value.obj.handle, 然后拿著這個索引再去EG(objects_store)查詢,效率比較低下。
對于另外一個常見的操作,就是獲取一個zval對象的類的時候,我們也需要需要調用一個函數:
#define Z_OBJCE(zval) zend_get_class_entry(&(zval) TSRMLS_CC)
PHP7
到了PHP7, 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, 那么就不發生寫時分離。
這里有的同學會問,那既然已經在zval中直接保存了zend_object*了,那為啥還需要EG(objects_store)呢?
這里有2個主要原因:
1. 我們需要在PHP請求結束的時候保證所有的對象的析構函數都被調用,因為object存在循環引用的情況,那如何快速的遍歷所有存活的對象呢? EG(objects_store)是一個很不錯的選擇。
2. 在PHPNG開發的時候,為了保證最大向后兼容,我們還是需要保證獲取一個對象的handle的接口, 并且這個handle還是要保證原有的語義。
但實際上來說呢, 其實EG(objects_store)已經沒啥太大的用處了, 我們是可以在將來去掉它的。
好,接下來出現了另外一個問題,我們再看看zend_object的定義, 注意到末尾的properties_table[1], 也就是說,我們現在會把object的屬性跟對象一起分配內存。這樣做對緩存友好。 但帶來一個變化就是, 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也就可以很便捷的訪問到了, 性能提升明顯。
以上就是PHP內核Object的詳細介紹內容了,看完之后是否有所收獲呢?如果想了解更多相關內容,歡迎關注創新互聯行業資訊!
文章題目:PHP內核Object介紹
路徑分享:http://m.2m8n56k.cn/article48/ipjjep.html
成都網站建設公司_創新互聯,為您提供建站公司、微信小程序、App開發、App設計、網站營銷、網站建設
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:[email protected]。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯