深入剖析PHP7核心原始碼(二) PHP變數容器

2022-05-17 16:29:26 字數 4519 閱讀 3119

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命令無效則進行這項配置 開啟配置檔...