【PHP7源碼分析】初探PHP字符串類型中的引用計數

做者:王澍php

背景介紹

  • 字符串類型也是咱們平時經常使用的類型,因爲字符串的特性,爲了節省內存一般相同字符串變量會共用一塊內存空間,經過引用計數來標記有多變量使用這塊內存。
  • 可是,通過GDB追蹤發現,並非全部字符串都是正常在操做引用計數,有正常累加的,有時候爲0,又有時候爲1。爲了一探究竟,因而簡單分析了一下各類賦值狀況。

環境狀況

  • 系統版本:Ubuntu 16.04.3 LTS
  • PHP版本:PHP 7.1.0
  • gdb版本:GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1

1、基礎變量

PHP中zval是全部變量的基礎。(zend_type.h 121行)ubuntu

clipboard.png

其中zend_value存儲了具體數據,結構如圖: (zend_type.h 101行)api

clipboard.png

  • zend_value爲一個聯合體,總體佔用8字節。
  • u1爲一個聯合體,存儲了類型所須要的必要數據,佔用4字節。
  • u2位一個聯合體,存儲了一些額外數據,好比hash碰撞時的next,佔用4字節。

整個zval結構體,佔用16字節,就支持了php全部類型。數組

clipboard.png

PHP7中用如此簡單而巧妙的zval存儲了全部類型數據,那麼一個不肯定長度的字符串又如何能存儲在一個16字節的zval中呢?數據結構

2、字符串變量

<?php
$a = "hello world";
echo $a;

經過GDB調試能夠看到:性能

clipboard.png

type = 6,對照類型的定義,能夠看到類型是IS_STRING (zend_type.h 303行)編碼

clipboard.png

因爲咱們的字符串長度不必定,因此單靠zval的16個字節是沒法直接存儲的,因而經過value中的str指向真正存儲字符串的內存地址。經過打印咱們能夠看到,這個地址的類型是zend_stringspa

clipboard.png

一、zend_string結構體

先看一下它的數據結構,如圖 (zend_type.h 169行)3d

clipboard.png

zend_string結構體中的gc
頭部先是gc,能夠看一下其餘複雜類型,頭部都有一個gc,它的做用是什麼?
看看gc的數據結構,如圖:指針

clipboard.png

  • 第一個是refcount,記錄了被引用的次數。
  • 第二個u是一個聯合體,能夠看到與zval的u1很像,關鍵是記錄了type。

那麼做用就比較好猜想了,在程序執行gc或其餘操做的時候,對於任意一個複雜類型,指針頭部就是gc,裏面不光有引用計數,而且能經過u.v.type肯定該複雜類型的真正類型。

zend_string結構體中的h
從名字能夠猜想,這是字符串的hash,空間換時間的思想,把計算好的hash保存下來,提升性能。

zend_string中的len
比較明顯,它存儲了字符串的長度。

zend_string中的val[1]
這種寫法是c裏面的柔性數組,這裏存儲了整個字符串,經過這個方式保證字符串所在的內存地址是與該結構體內存地址緊密相連的,減小了去另一個塊內存取值的時間。
(ps:留個小疑問,gdb就能夠追蹤到。柔性數組是否佔內存空間?zend_string的對齊後是什麼結構?總體佔多大?)

二、zend_string實際內容

瞭解過結構自己,能夠打印一下內容來看看,如圖

clipboard.png

該地址內存儲的確實是hello world,爲何gc中的refcount是0呢?

緣由是這樣的:

  • 常量字符串,在PHP代碼中的固定字符串,在編譯階段存儲在全局變量表中,又叫作字面量表,請求結束後纔會被銷燬,因此refcount一直爲0。
  • 臨時字符串,發生在虛擬機執行opcode時計算出來的字符串,存儲在臨時變量區,有正常的refcount。

修改一下代碼,看一下臨時字符串

<?php
$a = "hello world".time();
echo $a;

打印一下這個變量的zval,refcount爲1,如圖

clipboard.png

3、字符串的引用計數

1.臨時字符串直接賦值

對於臨時字符串,應該是每有一個被賦值的變量時,該zend_string中的引用計數+1,而且在引用計數爲0時,釋放這塊內存。
<?php

$a = "hello world".time();
echo $a;
$b = $a;
echo $b;
當爲$a賦值完成時,$a,在棧上第一個位置,類型爲6,IS_STRING,取value中的str,地址爲:**0x7ffff4402c30**,能夠看到內容,zend_string引用計數爲1。

clipboard.png

當爲$b賦值完成時,$b在棧上第二個位置,類型爲6,IS_STRING,取value中的str一樣地址爲:**0x7ffff4402c30**,zend_string引用計數爲2。

clipboard.png

大體引用狀況能夠畫出:

clipboard.png

2.引用賦值

對於變量直接賦值,上面已經畫出了引用關係,那麼若是是引用類型呢?
<?php

$a = "hello world".time();
echo $a;
$b = &$a;
echo $b;
當爲$a賦值完成時,$a,在棧上第一個位置,類型爲6,IS_STRING,取value中的str,地址爲:**0x7ffff4402c30**,能夠看到內容,zend_string引用計數爲1。

clipboard.png

當$b賦值爲引用類型時,$b在棧上第二個位置,類型爲10 , IS_REFERENCE,取value中的ref能夠看到內容。

clipboard.png

這時$a的類型是否發生改變?是否仍是字符串類型?直接打印$a看一下。這時$a的類型變成了10,IS_REFERENCE,打印value中的ref,地址與$b的ref相同!

clipboard.png

在$b引用$a的時候,$a與$b都變成引用類型,該引用類型指向了中有一個zval,類型爲6,IS_STRING,value中的str指向了一個zend_string,而且zend_string引用計數爲1.

clipboard.png

大體引用狀況如圖:

clipboard.png

4、字符串變量特殊值

<?php
$a = 「string」;
$b = 「double」;
 
echo $a;
echo $b;

以咱們上面的結論,$a與$b都屬於常量字符串。

打印$a的zend_string,如圖

clipboard.png

打印$b的zend_string,如圖

clipboard.png

可見$b符合預期,可是$a顛覆了以上的理論。
那問題出在了哪?

通過GDB的追蹤,能夠看到a和b都在棧上,而且都是string類型。
可是,其中value中的str地址有很大不同。
首先看變量a
在棧的第一個位置,str的值是 0x11522c0

clipboard.png

其次看變量b
在棧上第二個位置,str的值是 0x7ffff4401880

clipboard.png

瞭解PHP的內存分配的話,能夠看出b的字符串,分配在了 0x7ffff440000 這個chunk上,屬於第一個page頁,0x7ffff4401000

而a的字符串很顯然不是這個規則,他沒有分配在chunk上,而是很特殊的一個地址。
因此string這個字符串,不是_emalloc分配的。

那麼採用個笨辦法,我把 0x11522c0強轉 (zend_string *)0x11522c0 ,而後看裏的值何時放進去的。

PHP版本 7.1.0
第一個節點: php_cli.c中的 1345行

sapi_module->startup(sapi_module)

第二個節點: php_cli.c 中的424 行

php_module_startup(sapi_module, NULL, 0)

第三個節點: main.c 中的 2123行

zend_startup(&zuf, NULL);

第四個節點: zend.c 中的768行

zend_interned_strings_init();

很接近了哦
第五個節點: zend_string.c中的103

zend_intern_known_strings(known_strings, (sizeof(known_strings)

在這裏打印一下,know_strings,這裏能夠看到,file,line,function,class,object等等,以及string在這裏就初始化了!

clipboard.png

對應聲明的地址在 zend_string.h 383行

clipboard.png

這裏尚未初始化字面量,因而這些字符串與字面量的狀況有些不同。

小結

一樣是字符串在PHP中有不少種不一樣狀況。

  • 1.代碼中直接硬編碼的字符串,在字面量表中,引用計數一直爲0,直到整個腳本執行完畢後,纔會銷燬。
  • 2.在執行階段計算出來的字符串,臨時字符串,引用計數正常計算,每一個引用都會加1。在引用計數爲0時回收內存。
  • 3.引用類型的字符串,多個變量引用計數計算在引用類型(zend_reference)上。字符串被zend_reference引用,引用計數爲1。
  • 4.特殊的字符串,在PHP初始化時建立的,整個腳本執行完畢後纔會銷燬,引用計數一直爲1。
相關文章
相關標籤/搜索