深入剖析PHP7核心原始碼(一)- PHP架構與生命週期
阿新 • • 發佈:2019-08-19
PHP7 為什麼這麼快?
- 全新的zval 更節約的空間,棧上分配記憶體
- zend_string 儲存字串的Hash值,陣列查詢的時候不需要進行Hash計算
- 在HashTable桶內直接存資料,減少了記憶體的申請次數,提升了cache命中率和記憶體訪問速度
- zend_parse_parameters改為了巨集實現,效能提升5%
- 增加opcode指令 call_user_function,is_init/string/array,strlen,defined函式變成opcode指令,速度更快
- 排序演算法的改進
PHP7 架構
- Zend 引擎:Zend引擎為PHP提供了基礎服務,包括詞法分析 語法分析 ,AST抽象語法樹編譯 opcodes執行,PHP的變數設計、記憶體管理、程序管理。
- PHP層:綁定了SAPI層並處理與它的通訊,它同時對safe_mode和open_basedir的檢測提供一致的控制層,將fopen()、fread()和fwrite()等使用者空間的函式與檔案和網路I/O聯絡起來。
- SAPI:包括了cli fpm等,把介面對外介面都抽象出來,只要遵守SAPI協議便可以實現一個server。
- 拓展:zend 引擎提供了核心能力和介面規範,在此基礎上可以開發拓展
這裡的拓展分為了兩種,通常在php.ini中,通過extension=載入的擴充套件我們稱為PHP擴充套件,通過zend_extension=載入的擴充套件我們稱為Zend擴充套件,但從原始碼的角度來講,PHP擴充套件應該稱為“模組”(原始碼中以module命名),而Zend擴充套件稱為“擴充套件”(原始碼中以extension命名)。兩者最大的區別在於向引擎註冊的鉤子,向用戶層面提供一些C實現的PHP函式,需要用到zend_module_entry(即作為PHP擴充套件),而需要hook到Zend引擎的話,就得用到zend_extension(即作為Zend擴充套件)。
PHP7執行流程
- 詞法分析,把原始碼切割成多個字串單元(Token)
- 語法分析器把Token轉換成AST抽象語法樹
- 抽象語法樹轉換成opcodes(opcode指令集合)
- 虛擬機器解釋執行執行opcodes(opcode是一組指令標識,對應handler處理函式)
執行例項
詞法分析
<?php
echo "Hello world";
切割成了4部分
<?php => #define T_OPEN_TAG 379 echo => #define T_ECHO 328 空格 => #define T_WHITESPACE 382 "hello world" => #define T_CONSTANT_ENCAPSED_STRING 323
語法分析
單獨存在的詞塊不能完整表達語義,還需要語法分析器,它會檢查語法,匹配Token,對Token進行關聯,組織串聯後的產物就是AST.AST 分為多種型別,對應PHP語法,比如賦值語句,生成的抽象語法樹節點是ZEND_AST_ASSIGN,賦值語句的左右會被作為ZEND_AST_ASSIGN型別節點的孩子(AST是PHP7才加入的,解耦了編譯器和直譯器).
opcodes
opcode是PHP執行過程中的中間程式碼,生成後由虛擬機器執行,生成的opcode是類似下面的樣子
line op
1 ECHO
2 RETURN
原始碼中對應的opcode及handler
ZEND_ECHO // handler:ZEND_ECHO_SPEC_CONST_HANDLER 實現的功能是輸出"hello world"
ZEND_RETURN // handler:ZEND_RETURN_SPEC_CONST_HANDLER
PHP 生命週期
CLI生命週期
- php_module_startup:註冊全域性變數GPC等,載入內部拓展和外部拓展。
- php_request_startup:重置垃圾回收器,初始化執行器,初始化掃描器,設定超時時間等。
- php_execute_script
=> compile_file
=> open_file_for_scanning(讀取PHP程式碼內容,並使詞法分析指標指向第一個位置)
=> zendparse(詞法分析語法分析後生成AST) => init_op_array(初始化op_array)
=> zend_compile_top_stmt(把AST轉為op_array)
=> pass_two(設定op_array對應的zend虛擬機器handler)
=> 生成op_array
=> zend_execute(zend虛擬機器中執行op_array)
- php_request_shutdown:呼叫所有關閉函式,呼叫所有解構函式,輸出緩衝區內容,重置最大執行時間,關閉輸出層(HTTP頭等),釋放所有request的全域性變數
- php_module_shutdown:呼叫module對應的flush函式,清理持久化的符號表,銷燬全域性變數,關閉所有拓展,關閉記憶體管理,關閉輸出output,析構垃圾回收
FPM模式的生命週期
- FPM跟CLI模式不同的是,FPM是常駐記憶體的,所以php_module_startup只在啟動程序的時候做一次初始化,對應的php_module_shutdown也只做一次。
- 進入迴圈,呼叫fcgi_accept_request(accept) 阻塞等待,如果請求進來,則進入php_request_startup,初始化請求,同時加了鎖來防止驚群效應
fcgi.c
...
FCGI_LOCK(req->listen_socket);
req->fd = accept(listen_socket, (struct sockaddr *)&sa, &len);
FCGI_UNLOCK(req->listen_socket);
引用
- PHP7的效能優化總結 https://blog.csdn.net/eebaicai/article/details/83629547
- PHP擴充套件與Zend擴充套件區別 http://yangxikun.github.io/php/2016/07/10/php-zend-extension.html
- 《PHP7 底層設計與原始碼實現》 陳雷等