從PHP7開始,大家可能會發現,不少函數不再使用傳統的參數處理方式,而是改用了我們稱之為Fast zend parameters parsing(FAST_ZPP)的新型方式, 比如在PHP7之前,count函數是這樣的:
PHP_FUNCTION(count) { zval *array; long mode = COUNT_NORMAL; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l", &array, &mode) == FAILURE) { return; } .... }
在PHP7以後,變成了:
PHP_FUNCTION(count) { zval *array; zend_long mode = COUNT_NORMAL; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ZVAL(array) Z_PARAM_OPTIONAL Z_PARAM_LONG(mode) ZEND_PARSE_PARAMETERS_END(); ... }
很多PHP擴充開發的同學可能在初次接觸的時候,會覺得很陌生,不要焦慮,讓我慢慢道來 :)
當時在做PHPNG(PHP7的開發項目代号)的開發的時候,我們主要的發現性能提升點的一個方式就是bench各種大型實際項目,來發現占用資源比較大的部分,而最常用benchmark對象之一是wordpress,因為它夠複雜,夠慢,(它也是我們開發JIT的時候對主要bench目标:)) 代表了非OO型代碼類的典型應用, 在實際的benchmark的過程中我們發現,将近有6%的耗時被zend_parse_parameters給占用了。
事實上zend_parameters_parsing确實是一個很龐大的函數:
ZEND_API int zend_parse_parameters(int num_args, const char *type_spec, ...)
它根據type_spec字元串中指定的辨別符,來處理輸入參數,而這個參數符有很多種(具體含義可以參看: README.PARAMETER_PARSING_API):
a A b C d f h H l L o O p P r s S z * + | / !
根據不同的組合來表示我們的PHP函數要接受的參數類型,比如例子中的count, 通過”z|l”表示要接受一個zval類型的參數,和一個可選的long類型的mode參數,當zend_parse_parameters在runtime的時候被調用的時候,就會需要分析這些字元,然後調用對應的邏輯,對于一些本身就很簡單的函數來說,比如count,這個開銷就會顯得很明顯。
再回頭來看這個函數的特點,我們會發現,比如對于count這個例子來說,其實type_spec在編譯期就是确定的常量,也就是說,其實在編譯的時候,我們就應該已經知道了”a|l”應該調用那些對應的參數處理邏輯。
而事實上,當代的編譯器都具備這個基本優化能力, 比如對于如下的代碼:
#include <stdlib.h> #define AAA 1; int main() { int a = AAA; if (a) { abort(); } return 0; }
如果我們嘗試讓編譯優化(-o2)它,并檢查生成的彙編:
main: .LFB18: subq $8, %rsp call [email protected]
大家可以看到,if判斷已經被抹掉了, 因為在編譯時刻, 就能知道a是1, if一定為真。
而FAST_ZPP就是充分借助了這個能力而來的一種新型的參數申明方式, 比如對于Z_PARAM_ZVAL(array)
#define Z_PARAM_ZVAL_EX(dest, check_null, separate) \ if (separate) { \ Z_PARAM_PROLOGUE(separate); \ zend_parse_arg_zval_deref(_arg, &dest, check_null); \ } else { \ ++_i; \ ZEND_ASSERT(_i <= _min_num_args || _optional==1); \ ZEND_ASSERT(_i > _min_num_args || _optional==0); \ if (_optional && UNEXPECTED(_i >_num_args)) break; \ _real_arg++; \ zend_parse_arg_zval(_real_arg, &dest, check_null); \ } #define Z_PARAM_ZVAL(dest) \ Z_PARAM_ZVAL_EX(dest, 0, 0)
在編譯時刻就能被先替換為:
zend_parse_arg_zval(((zval*)execute_data) - 1, &array, 0);
而如果我們進一步審視zend_parse_arg_zval:
static zend_always_inline void zend_parse_arg_zval(zval *arg, zval **dest, int check_null) { *dest = (check_null && (UNEXPECTED(Z_TYPE_P(arg) == IS_NULL) || (UNEXPECTED(Z_ISREF_P(arg)) && UNEXPECTED(Z_TYPE_P(Z_REFVAL_P(arg)) == IS_NULL)))) ? NULL : arg; }
我們會發現它也是一個inline申明的函數,而參數因為是常量,那麼就可以進一步被evaluate成:
zval *array = ((zval*)execute_data) - 1;
怎麼樣,是不是一看就知道會快很多? 沒有type_spec分析,沒有額外的函數調用,直接擷取到參數。
剛剛說到的inline函數可以在編譯時期根據常數的剪枝内聯, 也是用來避免同類函數的重複代碼的很好的方法,在PHP7中也有大量使用,有興趣的可以參看zend_hash.c中的很多相似函數的定義。
當然,這麼做也有一個問題就是, 會增大我們程式的binary size, 這個也很容易了解, 比如對于count來說,本來原來隻是調用一個外部函數,一個call指令就夠了,但現在就會有很多内聯進來的指令。
而binary size變大以後,執行時期的cache miss就會增大,也會影響性能,是以FAST_ZPP我們也不是建議全部使用, 而真是針對實際應用中調用頻率比較大,并且本身函數邏輯較為簡單的函數來使用.
總結一下,一般來說,我們自己寫的擴充函數,并不需要一定使用FAST_ZPP, 因為如果自身是複雜的函數邏輯的, 這點開銷對比起來,其實也還好了。
最後,附上新的FAST_ZPP API和老的參數描述之間的對應如下:
更多PHP内容請通路:
更多PHP内容請通路:
群内已經有管理将知識體系整理好(源碼,學習視訊等資料),歡迎加群免費領取。
這套精品PHP教程絕不是市場上的那些妖豔賤貨可比,作為web開發的佼佼者PHP并不遜色其他語言,加上Swoole後更加是如虎添翼!進軍通信 、物聯網行業開發百度地圖、百度訂單中心、虎牙、戰旗TV等!寒冬裁員期過後正是各大企業擴大招人的時期,現在市場初級程式員泛濫,進階中進階程式員絕對是各大企業急需的人才,這套學習教程适合那些1-5年以内的PHP開發者正處于瓶頸期,想要突破自己進階中進階、架構師!名額有限,先到先得!
騰訊T3-T4标準精品PHP架構師教程目錄大全,隻要你看完保證薪資上升一個台階(持續更新)
部分資料截圖:
還有限時精品福利:
★騰訊進階PHP工程師筆試題目
★億級PV高并發場景訂單的處理
★laravel開發天貓商城元件服務
★戰旗TV視訊直播的架構項目實戰
掃描下面二維碼領取
對PHP後端技術,對PHP架構技術感興趣的朋友,我的官方群點選此處,一起學習,互相讨論。
群内已經有管理将知識體系整理好(源碼,學習視訊等資料),歡迎加群免費領取。
本課程深度對标騰訊T3-T4标準,貼身打造學習計劃為web開發人員進階中進階、架構師提升技術,為自己增值漲薪!加入BAT特訓營還可以獲得内推大廠名額以及GO語言學習權限!!!