php的變數使用起來非常方便,其基本結構是底層實現的zval,php7採用了全新的zval,由此帶來了非常大的效能提公升,本文重點分析php7的zval的改變。
typedef struct _zval_struct zval
typedef union _zvalue_value str;
hashtable *ht; // 用於陣列(長度8位元組)
zend_object_value obj; // 用於物件(12位元組)
zend_ast *ast; // 用於常量表示式(長度8位元組)
} zvalue_value;
typedef struct _zval_gc_info u; // (長度8位元組)
} zval_gc_info;
所以在php裡面,給乙個變數賦值,實際上會轉換成這樣來執行
<?php
$var = 123
=>
zval.value = 123
zval.type = is_long
zval.refcount__gc= 0
zval.is_ref__gc = 0
...
struct _zval_struct ww; // 長度8位元組
} value; // 因為是聯合體,所以實際上整個value只用了8位元組
union v; // 總共長度是4位元組
uint32_t type_info; // 其實就是v的值位運算結果(長度4位元組)
} u1; // u1也是聯合體,總共長度4位元組
union u2; // u2也是聯合體,總共長度4位元組
};
zvalue的型別
zvalue.u1.type
/* regular data types */
#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
/* constant expressions */
#define is_constant_ast 11
/* internal types (偽型別)*/
#define is_indirect 13
#define is_ptr 14
#define _is_error 15
/* fake types used only for type hinting (z_type(zv) can not use them) 內部型別*/
#define _is_bool 16
#define is_callable 17
#define is_iterable 18
#define is_void 19
#define _is_number 20
字串的實現struct _zend_string ;
zval.value->gc.u.flags 這個標記代表了下面幾種不同型別的字串
is_str_persistent(通過malloc分配的)
is_str_interned(php**裡寫的一些字面量,比如函式名、變數值)
is_str_permanent(永久值,生命週期大於request)
is_str_constant(常量)
is_str_constant_unqualified
整數的實現
整數是標量,在容器中zval直接儲存
$a = 666;
// $a = zval_1(u1.v.type=is_long,value.lval=666)
$b = $a;
// $a = zval_1(u1.v.type=is_long,value.lval=666)
// $b = zval_2(u1.v.type=is_long,value.lval=666)
unset($a);
// $a = zval_1(u1.v.type=is_undef,value.lval=666)
陣列的全貌陣列的基本結構是基於key value的 hashtable,同時是乙個雙向鍊錶。熟悉資料結構的都知道,對乙個字串hash的時候有可能產生雜湊衝突,php是怎麼解決的?當發生衝突的時候,php在該對映後面會加上一條鍊錶,雜湊衝突後就會從鍊錶中找值。使用了雙向鍊錶的好處是,我們對陣列最常用的操作就是遍歷陣列,通過雙向鍊錶,我們可以很方便進行遍歷。你可能會問,那如果僅僅是這樣,單向鍊錶不也解決了嗎?還節省點空間。實際上,之所以用雙向鍊錶的乙個原因,是因為鍊錶在刪除元素的時候,就必須找到上乙個元素,把它的指標指向到下下個元素,雙向鍊錶已經儲存了上乙個元素的指標,而單向鍊錶就必須遍歷整個hashtable,時間複雜度將會是很差的o(n)。這個是php陣列的大概樣子,後面會專門寫一篇來概述是陣列hashtable的實現。
資源型別
php中很多依賴外部的操作都是資源型別,比如檔案資源 socket連線資源,資源型別的定義如下
struct _zend_resource
物件型別struct _zend_object ;
properties 是乙個hashtable ,key 物件的屬性 ,value是物件在properties_table 陣列中的偏移量,值真正的位置是在properties_table 陣列中。
引用型別
php的引用型別是比較特殊的一種型別,可以通過 & 操作符可以產生乙個引用變數,假如把 $b = &a; $b 的值改變的時候,$a 的值也跟著改變。
struct _zend_reference ;
$a = "time:" . time(); //$a -> zend_string_1(refcount=1)
$b = &$a; //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=1)
$c = $b; //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=2)
//$c -> zend_string_1(refcount=2)
$a 賦值字串,zend_string_1 的引用計數記為1。
把$a的引用賦值給$b,zend_string_1 結構的引用計數不變,產生了乙個中間結構體zend_reference_1,該結構體的引用計數為2。
$b 賦值給$c ,zend_reference_1引用計數不變,zend_string_1引用計數記為2。
中間結構體zend_reference_1存在的好處是,zend_string只需要存乙份,減少空間的浪費以及申請空間帶來的額外開銷
什麼是記憶體對齊
比如資料匯流排有32位,它訪存只能4個位元組4個位元組地進行。 0-3,4-7,8-11,12-15,…… 即使我們需要的資料只佔乙個位元組,也是一次讀取4個位元組。 乙個位元組的資料不管位址是什麼,都能通過一次訪存讀取出來。 而如果要讀取的資料是乙個位元組以上,比如兩個位元組, 如果該資料的記憶體位址是0x03,則需要兩次才能讀取該資料, 第一次讀0x00-0x03,第二次讀0x04-0x07。 這個資料就跨越了訪存邊界。而相對cpu的運算來說,訪存是非常慢的,所以要儘量減少訪存次數。 為了減少跨越訪存邊界的資料引起的訪存開銷, 所以編譯器會進行記憶體對齊,即把變數的位址做一些偏移, 目的是一次訪存就讀出資料,不然的話也要以盡可能少地訪存次數讀出資料。如上乙個例子中那樣,整型成員i的位址做4個位元組的偏移, 而sample物件的位址也會做4位元組邊界的對齊, 這樣i的位址始終是4的倍數,從而使得i不跨越訪存邊界, 能一次讀出它的值。
typedef struct sample1;
sample1佔多少空間呢?仍然是8個位元組。 a在第0個位元組,b在第1個位元組,i佔4-7位元組。 這是記憶體對齊的原則,占用盡量少的記憶體。 如果在b之後,還有char型別的成員c和d,同樣是佔8個位元組。 a,b,c,d在0-3位元組。引用
讀《PHP7 核心剖析》
自己的書,在書上做筆記了。一本不錯的書,寫的算是詳細了。書中簡要介紹了各種功能的 c 語言實現的方法名稱。一,php 基礎架構 二,sapi 3 種執行 php 的方式。三,資料型別 四,記憶體管理 執行緒安全 五,php 編譯與執行 六,函式 七,物件導向 類的自動載入,255頁 autoload...
PHP7核心剖析 PHP 函式
今天看一下php7中的函式相關的一點兒知識。首先來說,php的函式分為使用者自定義函式與內部函式兩類,先看一下使用者自定義函式。通過前面的文章,我們知道了php在編譯執行的時候,會將php指令碼解釋稱opline指令。那同樣的,php中函式也是被編譯成了opline指令集合zend op array...
國內php7安裝源 原始碼安裝PHP7
2.解壓 tar xzf php 7.2.4.tar.bz2 3.安裝 cd php 7.2.4 configure prefix data server php7 配置安裝路徑 make 編譯 make install 安裝 4.配置全域性環境 如果執行php v命令無效則進行這項配置 開啟配置檔...