週記8

PHP的一些問題

PHP-FPM 運行模式

php-fpm的進程數能夠根據設置分爲動態和靜態。php

  • 靜態:直接開啓指定數量的php-fpm進程,再也不增長或者減小;
  • 動態:開始的時候開啓必定數量的php-fpm進程,當請求量變大的時候,動態的增長php-fpm進程數到上限,當空閒的時候自動釋放空閒的進程數到一個下限。

這裏先說一下涉及到這個的幾個參數吧,他們分別是pm、pm.max_children、pm.start_servers、pm.min_spare_servers和pm.max_spare_servers。
pm表示使用那種方式,有兩個值能夠選擇,就是static(靜態)或者dynamic(動態)。在更老一些的版本中,dynamic被稱做apache-like。這個要注意看配置文件給出的說明了。
下面4個參數的意思分別爲:html

  • pm.max_children:靜態方式下開啓的php-fpm進程數量。
  • pm.start_servers:動態方式下的起始php-fpm進程數量。
  • pm.min_spare_servers:動態方式下的最小php-fpm進程數量。
  • pm.max_spare_servers:動態方式下的最大php-fpm進程數量。

那麼,對於咱們的服務器,選擇哪一種執行方式比較好呢?事實上,跟Apache同樣,咱們運行的PHP程序在執行完成後,或多或少會有內存泄露的問題。這也是爲何開始的時候一個php-fpm進程只佔用3M左右內存,運行一段時間後就會上升到20-30M的緣由了。因此,動態方式由於會結束掉多餘 的進程,能夠回收釋放一些內存,因此推薦在內存較少的服務器或者VPS上使用。具體最大數量根據 內存/20M 獲得。好比說512M的VPS,建議pm.max_spare_servers設置爲20。至於pm.min_spare_servers,則建議根據服務器的負載狀況來設置,比較合適的值在5~10之間。web

而後對於比較大內存的服務器來講,設置爲靜態的話會提升效率。由於頻繁開關php-fpm進程也會有時滯,因此內存夠大的狀況下開靜態效果會更好。數量也能夠根據 內存/30M 獲得。好比說2GB內存的服務器,能夠設置爲50;4GB內存能夠設置爲100等。算法

Nginx不支持對外部程序的直接調用或者解析,全部的外部程序(包括PHP)必須經過FastCGI接口來調用。FastCGI接口在Linux下是socket(這個socket能夠是文件socket,也能夠是ip socket)。爲了調用CGI程序,還須要一個FastCGI的wrapper(wrapper能夠理解爲用於啓動另外一個程序的程序),這個wrapper綁定在某個固定socket上,如端口或者文件socket。當Nginx將CGI請求發送給這個socket的時候,經過FastCGI接口,wrapper接收到請求,而後派生出一個新的線程,這個線程調用解釋器或者外部程序處理腳本並讀取返回數據;接着,wrapper再將返回的數據經過FastCGI接口,沿着固定的socket傳遞給Nginx;最後,Nginx將返回的數據發送給客戶端。apache

總結一下 fpm運行是多線程模型 分爲靜態和動態兩種模式編程

靜態更加適合內存良好的機器api

動態更適合內存小的機器數組

靜態的分配原理是 機器內存 / 30緩存

動態的分配原則是 機器內存 / 20性能優化

動態能動態的開閉worker 能夠避免一些 內存泄漏問題~

OPcache

PHP(本文所述案例PHP版本均爲7.1.3)做爲一門動態腳本語言,其在zend虛擬機執行過程爲:讀入腳本程序字符串,經由詞法分析器將其轉換爲單詞符號,接着語法分析器從中發現語法結構後生成抽象語法樹,再經靜態編譯器生成opcode,最後經解釋器模擬機器指令來執行每一條opcode。

在上述整個環節中,生成的opcode能夠應用編譯優化技術如死代碼刪除、條件常量傳播、函數內聯等各類優化來精簡opcode,達到提升代碼的執行性能的目的。

PHP擴展opcache,針對生成的opcode基於共享內存支持了緩存優化。在此基礎上又加入了opcode的靜態編譯優化。這裏所述優化一般採用優化器(Optimizer)來管理,編譯原理中,通常用優化遍(Opt pass)來描述每個優化。

總體上說,優化遍分兩種:

一種是分析pass,是提供數據流、控制流分析信息爲轉換pass提供輔助信息;

一種是轉換pass,它會改變生成代碼,包括增刪指令、改變替換指令、調整指令順序等,一般每個pass先後可dump出生成代碼的變化。

本文基於編譯原理,結合opcache擴展提供的優化器,以PHP編譯基本單位op_array、PHP執行最小單位opcode爲出發點。介紹編譯優化技術在Zend虛擬機中的應用,梳理各個優化遍是如何一步步優化opcode來提升代碼執行性能的。最後結合PHP語言虛擬機執行給出幾點展望。

OPcache 緩存的最小粒度 我認爲是 單個PHP 文件

使用下列推薦設置來得到較好的性能:

opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1
opcache.save_comments=0

你也能夠禁用 opcache.save_comments 而且啓用 opcache.enable_file_override。 須要提醒的是,在生產環境中使用上述配置以前,必須通過嚴格測試。 由於上述配置存在一個已知問題,它會引起一些框架和應用的異常, 尤爲是在存在文檔使用了備註註解的時候。

靜態編譯 解釋執行 即時編譯

靜態編譯(static compilation),也稱事前編譯(ahead-of-time compilation),簡稱AOT。即把源代碼編譯成目標代碼,執行時在支持目標代碼的平臺上運行。

動態編譯(dynamic compilation),相對於靜態編譯而言,指」在運行時進行編譯」。一般狀況下采用解釋器(interpreter)編譯執行,它是指一條一條的解釋執行源語言。

JIT編譯(just-in-time compilation),即即時編譯,狹義指某段代碼即將第一次被執行時進行編譯,然後則不用編譯直接執行,它爲動態編譯的一種特例。 PHP8 HHVM

OP_ARRAY

相似於C語言的棧幀(stack frame)概念,即一個運行程序的基本單位(一幀),通常爲一次函數調用的基本單位。此處,一個函數或方法、整個PHP腳本文件、傳給eval表示PHP代碼的字符串都會被編譯成一個op_array。

實現上op_array爲一個包含程序運行基本單位的全部信息的結構體,固然opcode數組爲該結構最爲重要的字段,不過除此以外還包含變量類型、註釋信息、異常捕獲信息、跳轉信息等。

OPCODE

解釋器執行(ZendVM)過程便是執行一個基本單位op_array內的最小優化opcode,按順序遍歷執行,執行當前opcode,會預取下一條opcode,直到最後一個RETRUN這個特殊的opcode返回退出。

這裏的opcode某種程度也相似於靜態編譯器裏的中間表示(相似於LLVM IR),一般也採用三地址碼的形式,即包含一個操做符,兩個操做數及一個運算結果。其中兩個操做數均包含類型信息。此處類型信息有五種,分別爲:

編譯變量(Compiled Variable,簡稱CV),編譯時變量即爲php腳本中定義的變量。

內部可重用變量(VAR),供ZendVM使用的臨時變量,可與其它opcode共用。

內部不可重用變量(TMP_VAR),供ZendVM使用的臨時變量,不可與其它opcode共用。

常量(CONST),只讀常量,值不可被更改。

無用變量(UNUSED)。因爲opcode採用三地址碼,不是每個opcode均有操做數字段,缺省時用該變量補齊字段。

類型信息與操做符一塊兒,供執行器匹配選擇特定已編譯好的C函數庫模板,模擬生成機器指令來執行。

參考資料:https://tech.youzan.com/understanding-opcode-optimization-in-php/

PHP 腳本執行過程

PHP -> lex -> tokens -> parse -> simple 表達式 -> compile -> opcode -> zendvm -> exec

1)Scanning(Lexing) ,將PHP代碼轉換爲語言片斷(Tokens)。

2)Parsing, 將Tokens轉換成簡單而有意義的表達式。

3)Compilation, 將表達式編譯成Opocdes。

4)Execution, 順次執行Opcodes,每次一條,從而實現PHP腳本的功能。

PHP內存模型與執行模型

內存管理通常會包括如下內容:

是否有足夠的內存供咱們的程序使用;

如何從足夠可用的內存中獲取部份內存;

對於使用後的內存,是否能夠將其銷燬並將其從新分配給其它程序使用。

PHP底層對內存的管理, 圍繞着小塊內存列表(free_buckets)、 大塊內存列表(large_free_buckets)和剩餘內存列表(rest_buckets)三個列表來分層進行的。 ZendMM向系統進行的內存申請,並非有須要時向系統即時申請,而是由ZendMM的最底層(heap層)先向系統申請一大塊的內存,經過對上面三種列表的填充,創建一個相似於內存池的管理機制。 在程序運行須要使用內存的時候,ZendMM會在內存池中分配相應的內存供使用。這樣作的好處是避免了PHP向系統頻繁的內存申請操做

PHP對內存的分配,是結合PHP的用途來設計的,PHP通常用於web應用程序的數據支持,單個腳本的運行週期通常比較短(最多達到秒級),內存大塊整塊的申請,自主進行小塊的分配, 沒有進行比較複雜的不相臨地址的空閒內存合併,而是集中再次向系統請求。 這樣作的好處就是運行速度會更快,缺點是隨着程序的運行時間的變長,內存的使用狀況會「愈來愈多」(PHP5.2及更早版本)。 因此PHP5.3以前的版本並不適合作爲守護進程長期運行。

http://www.javashuo.com/article/p-kjkzyrbw-e.html

PHP從下到上是一個4層體系:

Zend引擎:Zend總體用純C實現,是PHP的內核部分,它將PHP代碼翻譯(詞法、語法解析等一系列編譯過程)爲可執行opcode的處理並實現相應的處理方法、實現了基本的數據結構(如hashtable、oo)、內存分配及管理、提供了相應的api方法供外部調用,是一切的核心,全部的外圍功能均圍繞Zend實現。

Extensions:圍繞着Zend引擎,extensions經過組件式的方式提供各類基礎服務,咱們常見的各類內置函數(如array系列)、標準庫等都是經過extension來實現,用戶也能夠根據須要實現本身的extension以達到功能擴展、性能優化等目的(如貼吧正在使用的PHP中間層、富文本解析就是extension的典型應用)。

Sapi:Sapi全稱是Server Application Programming Interface,也就是服務端應用編程接口,Sapi經過一系列鉤子函數,使得PHP能夠和外圍交互數據,這是PHP很是優雅和成功的一個設計,經過sapi成功的將PHP自己和上層應用解耦隔離,PHP能夠再也不考慮如何針對不一樣應用進行兼容,而應用自己也能夠針對本身的特色實現不一樣的處理方式。

上層應用:這就是咱們平時編寫的PHP程序,經過不一樣的sapi方式獲得各類各樣的應用模式,如經過webserver實現web應用、在命令行下以腳本方式運行等等。

若是PHP是一輛車,那麼車的框架就是PHP自己,Zend是車的引擎(發動機),Ext下面的各類組件就是車的輪子,Sapi能夠看作是公路,車能夠跑在不一樣類型的公路上,而一次PHP程序的執行就是汽車跑在公路上。所以,咱們須要:性能優異的引擎+合適的車輪+正確的跑道。

PHP53的 gc 改進

http://www.cnblogs.com/leoo2sk/archive/2011/02/27/php-gc.html

是由於一個zval在一個時刻只能表示一種類型的變量。能夠看到_zvalue_value中只有5個字段,可是PHP中算上NULL有8種數據類型,那麼PHP內部是如何用5個字段表示8種類型呢?這算是PHP設計比較巧妙的一個地方,它經過複用字段達到了減小字段的目的。例如,在PHP內部布爾型、整型及資源(只要存儲資源的標識符便可)都是經過lval字段存儲的;dval用於存儲浮點型;str存儲字符串;ht存儲數組(注意PHP中的數組實際上是哈希表);而obj存儲對象類型;若是全部字段所有置爲0或NULL則表示PHP中的NULL,這樣就達到了用5個字段存儲8種類型的值。

在PHP只用於作動態頁面腳本時,這種泄露也許不是很要緊,由於動態頁面腳本的生命週期很短,PHP會保證當腳本執行完畢後,釋放其全部資源。可是PHP發展到目前已經不只僅用做動態頁面腳本這麼簡單,若是將PHP用在生命週期較長的場景中,例如自動化測試腳本或deamon進程,那麼通過屢次循環後積累下來的內存泄露可能就會很嚴重。這並非我在聳人聽聞,我曾經實習過的一個公司就經過PHP寫的deamon進程來與數據存儲服務器交互。

PHP5.3中的垃圾回收算法——Concurrent Cycle Collection in Reference Counted Systems

首先PHP會分配一個固定大小的「根緩衝區」,這個緩衝區用於存放固定數量的zval,這個數量默認是10,000,若是須要修改則須要修改源代碼Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES而後從新編譯。

由上文咱們能夠知道,一個zval若是有引用,要麼被全局符號表中的符號引用,要麼被其它表示複雜類型的zval中的符號引用。所以在zval中存在一些可能根(root)。這裏咱們暫且不討論PHP是如何發現這些可能根的,這是個很複雜的問題,總之PHP有辦法發現這些可能根zval並將它們投入根緩衝區。

當根緩衝區滿額時,PHP就會執行垃圾回收,此回收算法以下:

一、對每一個根緩衝區中的根zval按照深度優先遍歷算法遍歷全部能遍歷到的zval,並將每一個zval的refcount減1,同時爲了不對同一zval屢次減1(由於可能不一樣的根能遍歷到同一個zval),每次對某個zval減1後就對其標記爲「已減」。

二、再次對每一個緩衝區中的根zval深度優先遍歷,若是某個zval的refcount不爲0,則對其加1,不然保持其爲0。

三、清空根緩衝區中的全部根(注意是把這些zval從緩衝區中清除而不是銷燬它們),而後銷燬全部refcount爲0的zval,並收回其內存。

若是不能徹底理解也沒有關係,只需記住PHP5.3的垃圾回收算法有如下幾點特性:

一、並非每次refcount減小時都進入回收週期,只有根緩衝區滿額後在開始垃圾回收。

二、能夠解決循環引用問題。

三、能夠總將內存泄露保持在一個閾值如下。

PHP cli 的交互模式

php -a

相關文章
相關標籤/搜索