1. 程式人生 > >PHP5和7中的zval全介紹,記憶體管理,型別,引用計數(1)

PHP5和7中的zval全介紹,記憶體管理,型別,引用計數(1)

轉自:http://0x1.im/blog/php/Internal-value-representation-in-PHP-7-part-1.html

本文第一部分和第二均翻譯自Nikita Popov(nikic,PHP 官方開發組成員,柏林科技大學的學生) 的部落格。為了更符合漢語的閱讀習慣,文中並不會逐字逐句的翻譯。

要理解本文,你應該對 PHP5 中變數的實現有了一些瞭解,本文重點在於解釋 PHP7 中 zval 的變化。

由於大量的細節描述,本文將會分成兩個部分:第一部分主要描述 zval(zend value) 的實現在 PHP5 和 PHP7 中有何不同以及引用的實現。第二部分將會分析單獨型別(strings、objects)的細節。

PHP5 中的 zval

PHP5 中 zval 結構體定義如下:

typedef struct _zval_struct {
    zvalue_value value;
    zend_uint refcount__gc;
    zend_uchar type;
    zend_uchar is_ref__gc;
} zval;

如上,zval 包含一個 value、一個 type 以及兩個 __gc 字尾的欄位。value 是個聯合體,用於儲存不同型別的值:

typedef union _zvalue_value {
    long lval
; // 用於 bool 型別、整型和資源型別 double dval; // 用於浮點型別 struct { // 用於字串 char *val; int len; } str; HashTable *ht; // 用於陣列 zend_object_value obj; // 用於物件 zend_ast *ast; // 用於常量表達式(PHP5.6 才有) } zvalue_value
;

C 語言聯合體的特徵是一次只有一個成員是有效的並且分配的記憶體與需要記憶體最多的成員匹配(也要考慮記憶體對齊)。所有成員都儲存在記憶體的同一個位置,根據需要儲存不同的值。當你需要 lval 的時候,它儲存的是有符號整形,需要 dval 時,會儲存雙精度浮點數。

需要指出的是是聯合體中當前儲存的資料型別會記錄到 type 欄位,用一個整型來標記:

#define IS_NULL     0      /* Doesn't use value */
#define IS_LONG     1      /* Uses lval */
#define IS_DOUBLE   2      /* Uses dval */
#define IS_BOOL     3      /* Uses lval with values 0 and 1 */
#define IS_ARRAY    4      /* Uses ht */
#define IS_OBJECT   5      /* Uses obj */
#define IS_STRING   6      /* Uses str */
#define IS_RESOURCE 7      /* Uses lval, which is the resource ID */
/* Special types used for late-binding of constants */
#define IS_CONSTANT 8
#define IS_CONSTANT_AST 9

PHP5 中的引用計數

在PHP5中,zval 的記憶體是單獨從堆(heap)中分配的(有少數例外情況),PHP 需要知道哪些 zval 是正在使用的,哪些是需要釋放的。所以這就需要用到引用計數:zval 中 refcount__gc 的值用於儲存 zval 本身被引用的次數,比如 $a = $b = 42 語句中,42 被兩個變數引用,所以它的引用計數就是 2。如果引用計數變成 0,就意味著這個變數已經沒有用了,記憶體也就可以釋放了。

注意這裡提及到的引用計數指的不是 PHP 程式碼中的引用(使用 &),而是變數的使用次數。後面兩者需要同時出現時會使用『PHP 引用』和『引用』來區分兩個概念,這裡先忽略掉 PHP 的部分。

一個和引用計數緊密相關的概念是『寫時複製』:對於多個引用來說,zaval 只有在沒有變化的情況下才是共享的,一旦其中一個引用改變 zval 的值,就需要複製(”separated”)一份 zval,然後修改複製後的 zval。

下面是一個關於『寫時複製』和 zval 的銷燬的例子:

<?php
$a = 42;   // $a         -> zval_1(type=IS_LONG, value=42, refcount=1)
$b = $a;   // $a, $b     -> zval_1(type=IS_LONG, value=42, refcount=2)
$c = $b;   // $a, $b, $c -> zval_1(type=IS_LONG, value=42, refcount=3)

// 下面幾行是關於 zval 分離的
$a += 1;   // $b, $c -> zval_1(type=IS_LONG, value=42, refcount=2)
           // $a     -> zval_2(type=IS_LONG, value=43, refcount=1)

unset($b); // $c -> zval_1(type=IS_LONG, value=42, refcount=1)
           // $a -> zval_2(type=IS_LONG, value=43, refcount=1)

unset($c); // zval_1 is destroyed, because refcount=0
           // $a -> zval_2(type=IS_LONG, value=43, refcount=1)

引用計數有個致命的問題:無法檢查並釋放迴圈引用(使用的記憶體)。為了解決這問題,PHP 使用了迴圈回收的方法。當一個 zval 的計數減一時,就有可能屬於迴圈的一部分,這時將 zval 寫入到『根緩衝區』中。當緩衝區滿時,潛在的迴圈會被打上標記並進行回收。

因為要支援迴圈回收,實際使用的 zval 的結構實際上如下:

typedef struct _zval_gc_info {
    zval z;
    union {
        gc_root_buffer       *buffered;
        struct _zval_gc_info *next;
    } u;
} zval_gc_info;

zval_gc_info 結構體中嵌入了一個正常的 zval 結構,同時也增加了兩個指標引數,但是共屬於同一個聯合體u,所以實際使用中只有一個指標是有用的。buffered 指標用於儲存 zval 在根緩衝區的引用地址,所以如果在迴圈回收執行之前 zval 已經被銷燬了,這個欄位就可能被移除了。next 在回收銷燬值的時候使用,這裡不會深入。

修改動機

下面說說關於記憶體使用上的情況,這裡說的都是指在 64 位的系統上。首先,由於 str 和 obj 佔用的大小一樣, zvalue_value 這個聯合體佔用 16 個位元組(bytes)的記憶體。整個 zval 結構體佔用的記憶體是 24 個位元組(考慮到記憶體對齊),zval_gc_info 的大小是 32 個位元組。綜上,在堆(相對於棧)分配給 zval 的記憶體需要額外的 16 個位元組,所以每個 zval 在不同的地方一共需要用到 48 個位元組(要理解上面的計算方式需要注意每個指標在 64 位的系統上也需要佔用 8 個位元組)。

在這點上不管從什麼方面去考慮都可以認為 zval 的這種設計效率是很低的。比如 zval 在儲存整型的時候本身只需要 8 個位元組,即使考慮到需要存一些附加資訊以及記憶體對齊,額外 8 個位元組應該也是足夠的。

在儲存整型時本來確實需要 16 個位元組,但是實際上還有 16 個位元組用於引用計數、16 個位元組用於迴圈回收。所以說 zval 的記憶體分配和釋放都是消耗很大的操作,我們有必要對其進行優化。

從這個角度思考:一個整型資料真的需要儲存引用計數、迴圈回收的資訊並且單獨在堆上分配記憶體嗎?答案是當然不,這種處理方式一點都不好。

這裡總結一下 PHP5 中 zval 實現方式存在的主要問題:

  • zval 總是單獨從堆中分配記憶體;
  • zval 總是儲存引用計數和迴圈回收的資訊,即使是整型這種可能並不需要此類資訊的資料;
  • 在使用物件或者資源時,直接引用會導致兩次計數(原因會在下一部分講);
  • 某些間接訪問需要一個更好的處理方式。比如現在訪問儲存在變數中的物件間接使用了四個指標(指標鏈的長度為四)。這個問題也放到下一部分討論;
  • 直接計數也就意味著數值只能在 zval 之間共享。如果想在 zval 和 hashtable key 之間共享一個字串就不行(除非 hashtable key 也是 zval)。

PHP7 中的 zval

在 PHP7 中 zval 有了新的實現方式。最基礎的變化就是 zval 需要的記憶體不再是單獨從堆上分配,不再自己儲存引用計數。複雜資料型別(比如字串、陣列和物件)的引用計數由其自身來儲存。這種實現方式有以下好處:

  • 簡單資料型別不需要單獨分配記憶體,也不需要計數;
  • 不會再有兩次計數的情況。在物件中,只有物件自身儲存的計數是有效的;
  • 由於現在計數由數值自身儲存,所以也就可以和非 zval 結構的資料共享,比如 zval 和 hashtable key 之間;
  • 間接訪問需要的指標數減少了。

我們看看現在 zval 結構體的定義(現在在 zend_types.h 檔案中):

struct _zval_struct {
	zend_value        value;			/* value */
	union {
		struct {
			ZEND_ENDIAN_LOHI_4(
				zend_uchar    type,			/* active type */
				zend_uchar    type_flags,
				zend_uchar    const_flags,
				
            
           

相關推薦

PHP57zval介紹記憶體管理型別引用計數1

轉自:http://0x1.im/blog/php/Internal-value-representation-in-PHP-7-part-1.html 本文第一部分和第二均翻譯自Nikita Popov(nikic,PHP 官方開發組成員,柏林科技大學的學生)

雖然今天angular5發布了但我還是吧這篇angularjs1+webpack的文章發出來吧哈哈哈

attrs ctrl ron 封裝 oot 註意 tro 設置 adc   本文為原創,轉載請註明出處: cnzt 文章:cnzt-p http://www.cnblogs.com/zt-blog/p/7779384.html 寫在前面:   因

dubbo其實很簡單就是一個遠端服務呼叫的框架1

dubbo專題」dubbo其實很簡單,就是一個遠端服務呼叫的框架(1) 一、dubbo是什麼? 1)本質:一個Jar包,一個分散式框架,,一個遠端服務呼叫的分散式框架。 既然是新手教學,肯定很多同學不明白什麼是分散式和遠端服務呼叫,為什麼要分散式,為什麼要遠端呼叫。我簡單畫個對比圖說明(

給定一個句子只包含字母空格 將句子的單詞位置反轉單詞用空格分割, 單詞之間只有一個空格前後沒有空格。 比如: 1 “hello xiao mi”-> “mi xiao hello”

題目描述 給定一個句子(只包含字母和空格), 將句子中的單詞位置反轉,單詞用空格分割, 單詞之間只有一個空格,前後沒有空格。 比如: (1) “hello xiao mi”-> “mi xiao hello” 輸入描述: 輸入資料有多組,每組佔一行,包含一個句子(句子長度小於100

java檔案操作 1——判別指定檔案是否存在讀取檔案修改時間大小讀取文字檔案內容向文字檔案寫入指定內容

任務要求: 完成一個java application應用程式,判別指定路徑下指定檔名的檔案是否存在。 如果指定檔案存在,讀取並分別顯示其修改時間和檔案大小等屬性。 以文字方式開啟某一指定路徑指定檔名的文字檔案,讀取其內容並顯示。 以文字方式向某

eclipseidea啟動springboot專案的時候不載入控制器訪問頁面出現404

eclipse和idea中啟動的時候不載入控制器的原因,訪問頁面出現404現象第一類第二類 現象 控制檯中顯示控制器的原因有多種,我把它們的原因分為兩大類:一類是因為註解沒有的方式錯誤或者在controll所在的java檔案沒有載入。另外一類是直接啟動sprin

NET快取框架CacheManager在混合式開發框架的應用1-CacheManager的介紹使用 基於C#的MongoDB資料庫開發應用4--Redis的安裝及使用

在我們開發的很多分散式專案裡面(如基於WCF服務、Web API服務方式),由於資料提供涉及到資料庫的相關操作,如果客戶端的併發數量超過一定的數量,那麼資料庫的請求處理則以爆發式增長,如果資料庫伺服器無法快速處理這些併發請求,那麼將會增加客戶端的請求時間,嚴重者可能導致資料庫服務或者應用服務直接癱瘓。快取方案

.NET快取框架CacheManager在混合式開發框架的應用1-CacheManager的介紹使用 基於C#的MongoDB資料庫開發應用4--Redis的安裝及使用

在我們開發的很多分散式專案裡面(如基於WCF服務、Web API服務方式),由於資料提供涉及到資料庫的相關操作,如果客戶端的併發數量超過一定的數量,那麼資料庫的請求處理則以爆發式增長,如果資料庫伺服器無法快速處理這些併發請求,那麼將會增加客戶端的請求時間,嚴重者可能導致資料庫服務或者應用服務直接癱瘓。快取方案

在程式定義一個基類Person類由這個基類派生出Teacher教師Leader(領導)類。採用多繼承的方式由這兩個類派生出Teacher_Leader類。並且滿足以下要求:

#include using namespace std; #include #include class Person { public: Person(char* name, int age, char *gender, char * address, long phone); void show(v

Mongodb 學習筆記 7通過純mongo語句將資料庫的秒級時間戳轉換成Date型別並且裝換成任意時間格式

背景:在公司專案中,我們使用了秒級時間戳,作為時間資訊。但是mongodb自支援的多種時間處理函式,只針對date型別的欄位,於是我嘗試使用了mongo語句,將整型數的時間戳,通過mongo語句,轉換成date型別。 這裡使用的是aggregate(聚合),如下: db.t_merchant

輸入m個學生每個學生有4門課在主調函式輸入學生的相關資訊編寫三個函式: (1)求第一門課的平均分; 2找出有兩門課以上不及格的學生並輸出他們的學號全部成績要求用指標函式實現:fl

  輸入m個學生,每個學生有4門課,在主調函式中輸入學生的相關資訊,編寫三個函式: (1)求第一門課的平均分; (2)找出有兩門課以上不及格的學生,並輸出他們的學號和全部成績,要求用指標函式實現:float*Search(float(*p)[4],int n); (3)找出

JDK1.7HashTable的hash為什麼對素數求餘而不像HashMap一樣對2的N次方求餘?

常用的hash函式是選一個數m取模(餘數),這個數在課本中推薦m是素數,但是經常見到選擇m=2^n,因為對2^n求餘數更快,並認為在key分佈均勻的情況下,key%m也是在[0,m-1]區間均勻分佈的。但實際上,key%m的分佈同m是有關的。證明如下:key%m = key - xm,即key減掉

所有Java代理有關的知識點都彙集於此速進學乾貨。

開發十年,就只剩下這套架構體系了! >>>   

rhel67的服務啟動以及計劃任務

周期 png ont style 查看 and ctrl 如何 編號 rhel6下 服務啟動命令 service servername (start/stop/restart/status)  啟動服務,停止服務,重啟服務,查看服務狀態 /etc/init.d/ser

bootstrap 學習筆記1---介紹bootstrap柵格系統

優先 cal 圖片 應用 尺寸 文件中 lin png ice   學習前端許久,對於布置框架和響應瀏覽器用html 和javascript 寫的有點繁瑣,無意間看到這個框架,覺得挺好用的就開始學習了,但是這個框架上面有很多知識,不是所有的都要學的,故將學習筆記和覺得重點的

HTMLCSS的居中效果1

htm inner height overflow n-1 txt posit splay read HTML和CSS中的居中效果 單行上下左右居中 Html: <div class=”container”></div> CSS:

Nordic nRF52832 學習筆記1 介紹入門與準備工作

例程 盜版 path pdf 規範 準備 但是 依然 可能   近來,物聯網已成為大勢所趨,VR與AR正方興未艾,各種手環、遙控、智能家居也在粉墨登場。技術前沿的領航者們已經快馬加鞭,各種意誌與暗示也在上傳下達。物聯網,無線通訊,移動互聯,將成為新的目標與寵兒。最近開的電賽

在Windows Server 2008 R2 Server上傳視頻遇到的問題

content 修改 strong 報錯 con 節點 fail get tle 上一篇 在Windows Server 2008 R2 Server中,上傳視頻遇到的問題(一)中遇到上傳40M視頻報404,然後修改配置文件節點: <httpRuntime tar

【開源分享:入門到精通ASP.NET MVC+EF6+Bootstrap】從這裏開始一起搭框架1開篇介紹

strong src 擁有 ckeditor 開發 技術分享 mdi 控制 https 框架簡介 這幾年一直在做ASP.NET開發,幾年前做項目都是老老實實一行行的寫代碼,後來發現那些高手基本都會有自己積累起來的代碼庫,現在稱之為開發框架,基礎代碼不用再去堆,