PHP 的一些底层知识
本篇內(nèi)容比較干澀,請(qǐng)自備礦泉水
文章分6個(gè)主題進(jìn)行講解
PHP運(yùn)行機(jī)制和原理
掃描 -> 解析 -> 編譯 -> 執(zhí)行 -> 輸出
執(zhí)行步驟
- 掃描
對(duì)代碼進(jìn)行詞法和語法分析,將內(nèi)容切割成一個(gè)個(gè)片段 (token)
- 解析
將代碼片段篩掉空格注釋等,將剩下的token 轉(zhuǎn)成有意義的表達(dá)式
- 編譯
將表達(dá)式編譯成中間碼 (opcode)
- 執(zhí)行
將中間碼一條條執(zhí)行
- 輸出
將執(zhí)行結(jié)果輸出到緩沖區(qū)
代碼切割
$code = <<<EOF <?php echo 'hello world'l; $data = 1+1; echo $data; EOF;print_r(token_get_all($code));執(zhí)行結(jié)果
Array ([0] => Array([0] => 376[1] => <?php[2] => 1)[1] => Array([0] => 319[1] => echo[2] => 2)[2] => Array([0] => 379[1] =>[2] => 2)[3] => Array([0] => 318[1] => 'hello world'[2] => 2)[4] => Array([0] => 310[1] => l[2] => 2)[5] => ;[6] => Array([0] => 379[1] =>[2] => 2)[7] => =[8] => Array([0] => 379[1] =>[2] => 3)[9] => Array([0] => 308[1] => 1[2] => 3)[10] => +[11] => Array([0] => 308[1] => 1[2] => 3)[12] => ;[13] => Array([0] => 379[1] =>[2] => 3)[14] => Array([0] => 319[1] => echo[2] => 4)[15] => Array([0] => 379[1] =>[2] => 4)[16] => ; )觀察上面可以得到三個(gè)信息
Token id 是Zend內(nèi)部token對(duì)應(yīng)碼, 定義于zend_language_parser.h
提高PHP執(zhí)行效率
- HHVM 為何速度快
通過虛擬機(jī)(類似java) 直接將PHP轉(zhuǎn)換成二進(jìn)制字節(jié)碼運(yùn)行,執(zhí)行時(shí)不用每次都去解析。
PHP底層變量數(shù)據(jù)結(jié)構(gòu)
使用 zval 結(jié)構(gòu)體保存,下面代碼在 Zend/zend.h 定義
typedef union _zvalue_value {/* 下面定義描述了PHP的8大數(shù)據(jù)類型 */long lval; // 長(zhǎng)整型 布爾型double dval; // 浮點(diǎn)型 struct { // 字符串型char *val;int len; // strlen 返回這個(gè)值} str; // NULL 類型表示本身為空 HashTable *ht; // 數(shù)組使用哈希表實(shí)現(xiàn) zend_object_value obj; // 對(duì)象類型 } zvalue_value;struct _zval_struct {zvalue_value value; /* 變量的值 */zend_uint refcount__gc;zend_uchar type; /* 變量的類型 */zend_uchar is_ref__gc };typedef struct _zval_struct zval;變量類型的定義,下面代碼在 Zend/zend_types.h 定義
typedef unsigned int zend_uint; typedef unsigned char zend_uchar;PHP數(shù)據(jù)8大類型統(tǒng)一通過 zvalue_value 聯(lián)合體存儲(chǔ)
聯(lián)合體自身為空 描述 null long 描述 int bool double 描述 float str 描述 string HashTable 描述 數(shù)字?jǐn)?shù)組和關(guān)聯(lián)數(shù)組 zend_object_value 描述 對(duì)象和資源PHP變量類型描述使用 zend_uchar type 描述
#define IS_NULL 0 #define IS_LONG 1 #define IS_DOUBLE 2 #define IS_BOOL 3 #define IS_ARRAY 4 #define IS_OBJECT 5 #define IS_STRING 6 #define IS_RESOURCE 7 #define IS_CONSTANT 8 #define IS_CONSTANT_ARRAY 9例如 $a=3 結(jié)構(gòu)體如下(偽代碼)
struct {zvalue_value = 3;refcount__gc = 1;type = IS_LONG;is_ref__gc = 0; }$a 就像指針一樣指向上面的結(jié)構(gòu)體
PHP傳值賦值中的COW特性
在 _zval_struct 數(shù)據(jù)結(jié)構(gòu)中還有下面兩個(gè)成員
- zend_uint refcount__gc 表示被引用多少次,每次引用+1
- zend_uchar is_ref__gc 表示普通變量還是引用變量
下面通過編寫代碼了解引用機(jī)制
此處我使用的是 php5.4,需要安裝 xdebug 來查看變量引用
注意使用 php7.2 測(cè)試的時(shí)候引用數(shù)會(huì)一直為0
安裝 xdebug 點(diǎn)擊下載
編譯生成 xdebug.so
yum -y install php-devel tar xf xdebug-2.8.0alpha1.tgz cd xdebug-2.8.0alpha1 phpize find /usr/ -name "php-config" ./configure --with-php-config=/usr/bin/php-config make && make install ls /usr/lib64/php/modules/配置 xdebug
php --ini echo 'zend_extension=/usr/lib64/php/modules/xdebug.so' >> /etc/php.ini systemctl restart php72-php-fpm.service php -m | grep xdebug編寫測(cè)試代碼
$a = 3; xdebug_debug_zval('a');輸出
a: (refcount=1, is_ref=0)=3
- refcount 引用數(shù)為1
- is_ref 為0表示普通變量
- =3 表示值為3
開始引用
$a = 3; $b = $a;xdebug_debug_zval('a'); xdebug_debug_zval('b');輸出
a: (refcount=2, is_ref=0)=3
b: (refcount=2, is_ref=0)=3
賦予新值
$a = 3; $b = $a; $b = 5;xdebug_debug_zval('a'); xdebug_debug_zval('b');輸出
a: (refcount=1, is_ref=0)=3
b: (refcount=1, is_ref=0)=5
傳遞地址
$a = 3; $b = &$a; xdebug_debug_zval('a'); xdebug_debug_zval('b');輸出
a: (refcount=2, is_ref=1)=3
b: (refcount=2, is_ref=1)=3
is_ref 該變量從普通變量轉(zhuǎn)成引用變量
賦予新值
$a = 3; $b = &$a; $c = $a;$b = 5; xdebug_debug_zval('a'); xdebug_debug_zval('b'); xdebug_debug_zval('c');a: (refcount=2, is_ref=1)=5
b: (refcount=2, is_ref=1)=5
c: (refcount=1, is_ref=0)=3
總結(jié)
變量之間傳值是通過引用賦值形式,無需開辟新的空間,節(jié)省資源
當(dāng)一個(gè)變量的值發(fā)生改變時(shí),會(huì)復(fù)制一份來存新的值,取消引用,稱為 copy on write (COW)
引用變量不會(huì)觸發(fā)COW
PHP垃圾回收機(jī)制
什么是垃圾
上海人: 你算什么垃圾?
如果一個(gè)zval 沒有任何變量引用它,那它就是垃圾
?: (refcount=0, is_ref=0)=5
為啥要清理垃圾?
有人說php線程結(jié)束時(shí)會(huì)銷毀所有變量,關(guān)閉所有句柄資源,不是自動(dòng)的嘛,為啥要清理
- 如果php 短時(shí)間內(nèi)處理多個(gè)大文件時(shí)(如1G的電影),處理完不回收繼續(xù)處理下一個(gè),會(huì)造成內(nèi)存溢出
- 如果php 是個(gè)守護(hù)進(jìn)程或者長(zhǎng)時(shí)間運(yùn)行的腳本,不回收垃圾,慢慢積累會(huì)造成內(nèi)存溢出
如何清理垃圾
- 找垃圾
通過 get_defined_vars 查看所有已定義變量
底層代碼 zend_globals.h 定義了存儲(chǔ)所有變量的兩個(gè)哈希表
struct _zend_executor_globals {...HashTable *active_symbol_table; //局部變量符號(hào)表HashTable symbol_table; //全局變量符號(hào)表... }找到所有已定義的變量后,尋找哪些變量引用數(shù)為0
struct _zval_struct{...zend_uint refcount__gc;zend_uchar is_ref__gc;... }- 清理垃圾
如上面將 refcount__gc 為0的變量清除,這個(gè)思路是 PHP5.2版本之前的做法了
PHP5.3后用 引用計(jì)數(shù)系統(tǒng)中同步周期回收 算法來清除
其實(shí)新算法也是基于 refcount__gc 來回收,那么為什么要用新算法呢?
我們知道 refcount__gc 為0的一定是垃圾
但是并不是所有的垃圾 refcount__gc 都為0
也有 refcount__gc 不為0 的垃圾,如下實(shí)驗(yàn)可以產(chǎn)生不為0的垃圾
一個(gè)例子
$a = ['a']; $a[] = &$a; //引用自己 xdebug_debug_zval('a');輸出
a: (refcount=2, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='a',
1 => (refcount=2, is_ref=1)=...
)
第二元素: ... 代表遞歸,引用數(shù)2,是一個(gè)指針引用變量
官方提供的一張圖
此時(shí)刪掉 $a
$a = ['a']; $a[] = &$a;unset($a); xdebug_debug_zval('a');輸出
a: no such symbol
因?yàn)?$a 被刪了,所以xdebug打印不出來,那么此時(shí)理論結(jié)構(gòu)如下
(refcount=1, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='a',
1 => (refcount=1, is_ref=1)=...
)
此時(shí)這個(gè) zval 已經(jīng)沒有符號(hào) (symbol) 引用了,但是它因?yàn)樽约阂米约?refcount 為1,所以它是一個(gè)奇葩的垃圾
對(duì)于此情況php腳本結(jié)束時(shí),會(huì)自動(dòng)清理,當(dāng)結(jié)束前會(huì)占用空間
因此 5.2 版本之前的垃圾清理思路不能覆蓋這種情況
引用計(jì)數(shù)系統(tǒng)中同步周期回收算法 (Concurrent Cycle Collection in Reference Counted System)
繼續(xù)以上面代碼為例進(jìn)行說明
新算法說明:
將 $a 作為疑似垃圾變量,進(jìn)行模擬刪除 (refcount--),然后模擬恢復(fù),恢復(fù)條件是有其他變量引用該值時(shí)才進(jìn)行模擬恢復(fù) (refcount++)
這樣沒能恢復(fù)成功的就是垃圾了,把它刪除即可。
例如上面的奇葩垃圾:
(refcount=1, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='a',
1 => (refcount=1, is_ref=1)=...
)
模擬刪除后變成:
(refcount=0, is_ref=1)=array (
0 => (refcount=0, is_ref=0)='a',
1 => (refcount=0, is_ref=1)=...
)
然后模擬恢復(fù):
因?yàn)闆]有類似 $a 這種 symbol 取指向該zval,所以恢復(fù)不來
何時(shí)清除
通過上面的算法疑似垃圾會(huì)存放到一個(gè)區(qū)域(垃圾站),只有垃圾站滿了才會(huì)立刻清除。 注意前提是開啟垃圾回收
開啟垃圾回收兩種方式
php.ini 下的 zend.enable_gc = On 默認(rèn)開啟
通過 gc_enable() 和 gc_disable() 來打開或關(guān)閉垃圾回收
可以直接使用 gc_collect_cycles() 函數(shù)強(qiáng)制執(zhí)行周期回收
最后說了那么多,其實(shí)只需要了解其中的原理,整個(gè)過程不需要PHP開發(fā)人員參與,只需要調(diào)用 gc_enable() 或 gc_collect_cycles() 即可實(shí)現(xiàn)自動(dòng)回收
PHP中數(shù)組底層分析
先復(fù)習(xí)一下數(shù)組特性
PHP 數(shù)組鍵的特性
$arr = [1 => 'a','1' => 'b',1.5 => 'c',true => 'd', ];print_r($arr);Array
(
[1] => d
)
key 可以是 integer 或 string
value 可以是任意類型
key 有如下特性
- 數(shù)字字符串會(huì)被轉(zhuǎn)成整型 '1' => 1
- 浮點(diǎn)型和布爾型轉(zhuǎn)成整型 1.3 =》 1
- null會(huì)被當(dāng)做空字符串 null => ''
- 鍵名不可以使用對(duì)象和數(shù)組
- 相同鍵名后面覆蓋前面
訪問數(shù)組元素
5.4 版本后可以使用如下
function getArr(){ return [1,2,3,4]; } echo getArr()[2];刪除數(shù)組元素
$a = [1,2,3,4]; foreach ($a as $k => $v) {unset($a[$k]); }$a[] = 5;print_r($a);Array
(
[4] => 5
)
- 刪除不會(huì)重置索引
數(shù)組遍歷
數(shù)組內(nèi)部實(shí)現(xiàn)
實(shí)現(xiàn)使用兩個(gè)結(jié)構(gòu) HashTable 和 bucket
- 什么是 HashTable
哈希表,通過關(guān)鍵字直接訪問內(nèi)存存儲(chǔ)位置的數(shù)據(jù)結(jié)構(gòu)。
通過把關(guān)鍵字進(jìn)行哈希函數(shù)計(jì)算,得到映射到表中的位置使得: 查找,插入,修改,刪除均在O(1)完成
下面代碼在 Zend/zend_types.h
typedef struct _zend_array HashTable;struct _zend_array {zend_refcounted_h gc;union {struct {ZEND_ENDIAN_LOHI_4(zend_uchar flags,zend_uchar nApplyCount,zend_uchar nIteratorsCount,zend_uchar consistency)} v;uint32_t flags;} u;uint32_t nTableMask; Bucket *arData;uint32_t nNumUsed;uint32_t nNumOfElements;uint32_t nTableSize; uint32_t nInternalPointer;zend_long nNextFreeElement;dtor_func_t pDestructor; };舊版結(jié)構(gòu)體
typedef struct _hashtable {uint nTableSize;uint nTableMask;uint nNumOfElements;ulong nNextFreeElement;Bucket *pInternalPointer;Bucket *pListHead;Bucket *pListTail;Bucket **arBuckets;unsigned char nApplyCount; };| nTableSize | Bucket大小,最小為8,以2x增長(zhǎng) |
| nTableMask | 索引優(yōu)化 nTableSize-1 |
| nNumOfElements | 元素個(gè)數(shù) 使用count()函數(shù)直接返回這個(gè) |
| nNextFreeElement | 下一個(gè)索引位置 foreach使用 |
| pInternalPointer | 當(dāng)前遍歷的指針,foreach比for快的原因,reset current函數(shù)使用 |
| pListHead | 存儲(chǔ)數(shù)組頭部指針 |
| pListTail | 存儲(chǔ)數(shù)組尾部指針 |
| arBuckets | 實(shí)際存儲(chǔ)容器 |
| arData | Bucket數(shù)據(jù) |
| nApplyCount | 記錄被遞歸次數(shù),防止死循環(huán)遞歸 |
| h | 對(duì)char *key進(jìn)行hash后的值,或是用戶指定數(shù)字索引值 |
| nKeyLength | 哈希關(guān)鍵字長(zhǎng)度,若為索引數(shù)字則為0 |
| pData | 指向value 一般是用戶數(shù)據(jù)的副本,若為指針數(shù)據(jù)則指向指針 |
| pDataPtr | 如果是指針數(shù)據(jù),指針會(huì)指向真正value,上面指向此 |
| pListNext | 整個(gè)hash表下個(gè)元素 |
| pListLast | 整個(gè)hash表上個(gè)元素 |
| pNext | 同一個(gè)hash的下一個(gè)元素 |
| pLast | 同一個(gè)hash的上一個(gè)元素 |
| arKey | 保存當(dāng)前key對(duì)應(yīng)的字符串 |
foreach 遍歷先從 HashTable 的 pListHead -> pListNext
pNext 和 pLast 用于hash沖突同一個(gè)hash不同個(gè)bucket之間指針
PHP數(shù)組函數(shù)分類
建議體驗(yàn)一下下面的函數(shù),不用記住,只是留個(gè)印象,當(dāng)你需要用的時(shí)候會(huì)聯(lián)想起來的,而不用自己去實(shí)現(xiàn)
遍歷
- prev
- next
- current
- end
- reset
- each
排序
- sort
- rsort
- asort
- ksort
- krsort
- uasort
- uksort
查找
- in_array
- array_search
- array_key_exists
分合
- array_slice
- array_splice
- implode
- explode
- array_combine
- array_chunk
- array_keys
- array_values
- array_columns
集合
- array_merge
- array_diff
- array_diff_*
- array_intersect
- array_intersect_*
隊(duì)列/棧
- array_push
- array_pop
- array_shift
其他
- array_fill
- array_flip
- array_sum
- array_reverse
轉(zhuǎn)載請(qǐng)指明出處 https://www.cnblogs.com/demonxian3/p/11327522.html
轉(zhuǎn)載于:https://www.cnblogs.com/demonxian3/p/11327522.html
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的PHP 的一些底层知识的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 系统管理11作业
- 下一篇: PHP 的一些开发规范