PHP載入大檔案時require和file_get_contents的效能對比

RichardXu發表於2014-11-07

在開發過程中發現,用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系統呼叫,就降低了他的效能。

相關文章