php-fpm的進程數能夠根據設置分爲動態和靜態。php
這裏先說一下涉及到這個的幾個參數吧,他們分別是pm、pm.max_children、pm.start_servers、pm.min_spare_servers和pm.max_spare_servers。
pm表示使用那種方式,有兩個值能夠選擇,就是static(靜態)或者dynamic(動態)。在更老一些的版本中,dynamic被稱做apache-like。這個要注意看配置文件給出的說明了。
下面4個參數的意思分別爲:html
那麼,對於咱們的服務器,選擇哪一種執行方式比較好呢?事實上,跟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 能夠避免一些 內存泄漏問題~
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
相似於C語言的棧幀(stack frame)概念,即一個運行程序的基本單位(一幀),通常爲一次函數調用的基本單位。此處,一個函數或方法、整個PHP腳本文件、傳給eval表示PHP代碼的字符串都會被編譯成一個op_array。
實現上op_array爲一個包含程序運行基本單位的全部信息的結構體,固然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 -> lex -> tokens -> parse -> simple 表達式 -> compile -> opcode -> zendvm -> exec
1)Scanning(Lexing) ,將PHP代碼轉換爲語言片斷(Tokens)。
2)Parsing, 將Tokens轉換成簡單而有意義的表達式。
3)Compilation, 將表達式編譯成Opocdes。
4)Execution, 順次執行Opcodes,每次一條,從而實現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 -a