PHP加載大文件時require和file_get_contents的性能對比

在開發過程中發現,用require來加載一個很大(幾百K,甚至幾兆)的配置文件時,會造成響應超時。如果把這個配置文件的內容序列化後,用file_get_contents獲取文件然後反序列化的方法來加載,就會快很多。

經過近兩週的研究,大概知道了其中的原因。

首先,還從PHP的流程說起,PHP其實有兩個流程,一個是啓動的流程,一個是響應請求的流程。PHP作爲Apache的一個模塊,向Apache註冊了兩個函數,一個是Aapche啓動的時候運行的函數:sapi_startup;一個是Apache接收到請求的時候調用的函數:php_handler

啓動的流程:

Apache啓動 

    ->  sapi_startup

         -> php_module_startup (PHP啓動總開關)

             -> zend_startup (啓動Zend引擎,包括初始化全局變量,初始化 compile 和 execute 函數


相應請求的流程:

Apache收到請求

    ->  sapi_startup

         -> zend_activate (包括初始化編譯器、初始化執行器、啓動掃描器)

             -> zend_compiler (語法分析、語意分析、生成opcode)

                 -> zend_execute (執行每個opcode)

                     -> zend_deactive(清理本次請求用到的數據)

如果遇到 require 或者 include 之類的函數時,會 從 zend_execute 階段重新回到 zend_compiler 階段,開始解釋PHP,執行PHP的過程。

除了 zend_compiler 和 zend_execute 階段之外,require 和 file_get_contents 的開銷基本是一樣的。

而且我們服務器上安裝了apc擴展,就是說 zend_compiler 階段可以認爲兩者也是一樣的。

那他們的性能九差在zend_execute階段了。

首先,讓我們用vld擴展查看一下兩個文件生成的opcode的數量,因爲這個是execute的輸入。

結果顯示,require 生成的opcode數量爲2萬多個,大多是 ADD_ARRAY_ELEMENT,就是構造數據;而file_get_contents生成的opcode只有6個;

然後再來對比執行的效率:

這兩個函數的執行可以分成兩部分:讀取文件和構造配置文件裏面的數組;

先說讀取文件,require讀取的機制是,以8192字節大小的buffer循環將文件讀入內存;而file_get_contents使用的是mmap,直接將文件映射到了虛擬內存當中。這樣的話,require會比file_get_contents多出大量的系統調用。而file_get_contents無需作這麼多用戶態和內核態的切換工作。這一步,file_get_contents勝出一籌;

再來看構造數組,require構造的機制是生成2萬多個opcode,然後一次執行這些opcode;而file_get_contents使用的是unserialize函數,他對傳入的文本進行解析,然後逐級構造成數組。他們構造數組的思路是一樣的,但是require每增加一級數據的開銷要比unserialize大;這一局也是 file_get_contents 略優;

但是,file_get_contents 在PHP內部是函數調用,而require是一個內置的opcode,所以調用file_get_contents時的開銷要比require略大;

所以,小文件的時候,file_get_contents 讀取文件時 內存映射的優勢發揮不出來,兩者部分伯仲;大文件的時候,由於require要2K2K的循環調用read系統調用,就降低了他的性能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章