摘要:插入一個元素時先將元素按先后順序插入數組,位置是,再根據的哈希值映射到散列表中的某個位置,將存入這個位置查找時先在散列表中映射到,得到在數組的位置,再從數組中取出元素。目前只有兩種類型會使用這種機制。
1.變量結構
typedef struct _zval_struct zval; typedef union _zend_value { zend_long lval; //int整形 double dval; //浮點型 zend_string *str; //string字符串 zend_array *arr; //array數組 zend_object *obj; //object對象 zend_resource *res; //resource資源類型 zend_reference *ref; //引用類型,通過&$var_name定義的 } zend_value; struct _zval_struct { zend_value value; //變量實際的value union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, //變量類型 zend_uchar type_flags, //類型掩碼,不同的類型會有不同的幾種屬性,內存管理會用到 zend_uchar const_flags, zend_uchar reserved) } v; uint32_t type_info; //上面4個值的組合值,可以直接根據type_info取到4個對應位置的值 } u1; union { uint32_t var_flags; uint32_t next; //哈希表中解決哈希沖突時用到 uint32_t cache_slot; uint32_t lineno; uint32_t num_args; uint32_t fe_pos; uint32_t fe_iter_idx; } u2; };
2.變量類型
#define IS_UNDEF 0 #define IS_NULL 1 #define IS_FALSE 2 #define IS_TRUE 3 #define IS_LONG 4 #define IS_DOUBLE 5 #define IS_STRING 6 #define IS_ARRAY 7 #define IS_OBJECT 8 #define IS_RESOURCE 9 #define IS_REFERENCE 10
其中undef、true、false、null沒有value,直接根據type區分,而long、double的值則直接存在value中,其他類型為指針
3.字符串
typedef struct _zend_string zend_string; struct _zend_string { zend_refcounted_h gc; //變量引用信息,比如當前value的引用數 size_t len; //字符串長度,通過這個值保證二進制安全 char val[1]; //字符串內容,變長struct,分配時按len長度申請內存 };
4.數組
typedef struct _zend_array HashTable; typedef struct _zend_array zend_array; typedef struct _Bucket { zval val; //存儲的具體value,這里嵌入了一個zval,而不是一個指針 zend_ulong h; //哈希值 zend_string *key; //key值 } Bucket; struct _zend_array { zend_refcounted_h gc; //引用計數信息 uint32_t nTableMask; //計算bucket索引時的掩碼,用于散列表的計算nIndex Bucket *arData; //bucket數組 uint32_t nNumUsed; //已用bucket數 uint32_t nNumOfElements; //已有元素數,nNumOfElements <= nNumUsed,因為刪除的并不是直接從arData中移除 uint32_t nTableSize; //數組的大小,為2^n,默認為8 uint32_t nInternalPointer; //數值索引,用于HashTable遍歷 zend_long nNextFreeElement;//下一個空閑可用位置的數字索引 dtor_func_t pDestructor;//析構函數,銷毀時調用的函數指針 };
HashTable主要依賴arData實現元素的存儲、索引。插入一個元素時先將元素按先后順序插入Bucket數組,位置是idx,再根據key的哈希值映射到散列表中的某個位置nIndex,將idx存入這個位置;查找時先在散列表中映射到nIndex,得到value在Bucket數組的位置idx,再從Bucket數組中取出元素。
$arr["a"] = 1; $arr["b"] = 2; $arr["c"] = 3; $arr["d"] = 4; unset($arr["c"]);
哈希碰撞:當出現沖突時將原value的位置保存到新value的zval.u2.next中,然后將新value代替原value位置
擴容:PHP散列表的大小為2^n,插入時如果容量不夠則首先檢查已刪除元素所占比例,如果達到閾值,則將已刪除元素移除,重建索引,如果未到閾值則進行擴容操作,擴大為當前大小的2倍,將當前Bucket數組復制到新的空間,然后重建索引。
重建散列表:當刪除元素達到一定數量或擴容后都需要重建散列表,因為value在Bucket位置移動了或哈希數組nTableSize變化了導致key與value的映射關系改變,重建過程實際就是遍歷Bucket數組中的value,然后重新計算映射值更新到散列表,移除已刪除的value,將后面未刪除的value依次前移
5.引用
引用是PHP中比較特殊的一種類型,它實際是指向另外一個PHP變量,對它的修改會直接改動實際指向的zval,可以簡單的理解為C中的指針,在PHP中通過&操作符產生一個引用變量,也就是說不管以前的類型是什么,&首先會創建一個zend_reference結構,其內嵌了一個zval,這個zval的value指向原來zval的value(如果是布爾、整形、浮點則直接復制原來的值),然后將原zval的類型修改為IS_REFERENCE,原zval的value指向新創建的zend_reference結構。
typedef struct _zend_reference zend_reference; struct _zend_reference { zend_refcounted_h gc; zval val; };
6.引用計數
typedef struct _zend_refcounted_h { uint32_t refcount; union { struct { ZEND_ENDIAN_LOHI_3( zend_uchar type, zend_uchar flags, uint16_t gc_info) } v; uint32_t type_info; } u; } zend_refcounted_h;
$a = "time:" . time(); //$a -> zend_string_1(refcount=1) $b = $a; //$a,$b -> zend_string_1(refcount=2) $c = $b; //$a,$b,$c -> zend_string_1(refcount=3) unset($b); //$b = IS_UNDEF $a,$c -> zend_string_1(refcount=2)
并不是所有的數據類型都會用到引用計數,long、double直接都是硬拷貝,只有value是指針的那幾種類型(除interned string,immutable array)才能用到引用計數。可由zval.u1.type_flag判斷
7.寫時復制
$a = array(1,2); $b = &$a; $c = $a; //發生分離 $b[] = 3;
事實上只有string、array兩種支持,
8.垃圾回收
PHP變量的回收主要有兩種:主動銷毀、自動銷毀。主動銷毀指的就是 unset ,而自動銷毀就是PHP的自動管理機制,在return時減掉局部變量的refcount,即使沒有顯式的return,PHP也會自動給加上這個操作,另外一個就是寫時復制時會斷開原來value的指向,這時候也會檢查斷開后舊value的refcount。
$a = [1]; $a[] = &$a; unset($a);
unset($a)之前引用關系:
unset($a)之后:
可以看到,unset($a)之后由于數組中有子元素指向$a,所以refcount > 0,無法通過簡單的gc機制回收,這種變量就是垃圾,垃圾回收器要處理的就是這種情況,目前垃圾只會出現在array、object兩種類型中,所以只會針對這兩種情況作特殊處理:當銷毀一個變量時,如果發現減掉refcount后仍然大于0,且類型是IS_ARRAY、IS_OBJECT則將此value放入gc可能垃圾雙向鏈表中,等這個鏈表達到一定數量后啟動檢查程序將所有變量檢查一遍,如果確定是垃圾則銷毀釋放。
目前只有object、array兩種類型會使用這種機制。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/28543.html
摘要:局部變量中局部變量分配在結構上,每次執行都會生成一個新的,局部變量在執行之初分配,然后在執行結束時釋放,這是局部變量的生命周期。 1.局部變量 PHP中局部變量分配在zend_execute_data結構上,每次執行zend_op_array都會生成一個新的zend_execute_data,局部變量在執行之初分配,然后在執行結束時釋放,這是局部變量的生命周期。 讀寫操作:局部變量通過...
摘要:代碼的編譯的解析過程任務就是將代碼轉化為數組,代碼里的所有信息都保存在數組中,然后將數組交給引擎執行,就是內核具體執行的命令,比如賦值加減操作函數調用等,每一條都對應一個處理,這些是提前定義好的函數。 1.PHP代碼的編譯 PHP的解析過程任務就是將PHP代碼轉化為opcode數組,代碼里的所有信息都保存在opcode數組中,然后將opcode數組交給zend引擎執行,opcode就是...
摘要:中專門為解決線程安全的問題抽象出了一個線程安全資源管理器,實現原理比較簡單既然共用資源這么困難那么就干脆不共用,各線程不再共享同一份全局變量,而是各復制一份,使用數據時各線程各取自己的副本,互不干擾。 1.線程安全資源管理器 PHP的SAPI多數是單線程環境,比如cli、fpm、cgi,每個進程只啟動一個主線程,這種模式下是不存在線程安全問題的,但是也有多線程的環境,比如Apache,...
1.EG(executor_globals/zend_executor_globals) PHP整個生命周期中最主要的一個結構,是一個全局變量,在main執行前分配(非ZTS下),直到PHP退出,它記錄著當前請求全部的信息 showImg(https://segmentfault.com/img/bV8fW0?w=960&h=777); 2.EX(execute_data/zend_execut...
摘要:引擎中定義了很多內部函數供用戶在中使用,比如等等,除了引擎中定義的內部函數,擴展中也提供了大量內部函數,我們也可以靈活的通過擴展自行定制。頭部是一個與完全相同的結構函數指針,展開 1.函數的存儲結構 typedef union _zend_function zend_function; union _zend_function { zend_uchar typ...
閱讀 3457·2021-11-25 09:43
閱讀 2605·2021-09-22 15:54
閱讀 590·2019-08-30 15:55
閱讀 974·2019-08-30 15:55
閱讀 1998·2019-08-30 15:55
閱讀 1741·2019-08-30 15:53
閱讀 3465·2019-08-30 15:52
閱讀 2039·2019-08-30 12:55