php 8 jit,深入理解PHP8 JIT
PHP 8的Just In Time是Opcache擴展的一部分,旨在在運行時將某些操作碼編譯為CPU指令。這意味著使用JIT時,Zend VM不需要解釋某些操作碼,而這些指令將直接作為CPU級指令執行。
PHP 8 JIT
PHP 8將帶來的最受好評的功能之一是Just In Time(JIT)編譯器。許多博客和社區都在談論它,并且肯定會引起很大的轟動,但是到目前為止,我發現關于JIT內部的細節很少。
經過多次研究和放棄后,我決定親自檢查PHP源代碼。結合我對C語言的一點了解以及到目前為止所收集的所有分散信息,我提出了這篇文章,希望它也能幫助您更好地了解PHP的JIT。
簡單來說: 當JIT按預期工作時,您的代碼將不會通過Zend VM執行,而是直接作為一組CPU級指令執行。
這就是JIT核心思想。
但是為了更好地理解它,我們需要考慮php在內部如何工作。不是很復雜,但是需要一些介紹。
PHP代碼如何執行?
我們都知道php是一種解釋語言。但這到底是什么意思?
每當您要執行PHP代碼(例如代碼段或整個Web應用程序)時,都必須經過php解釋器。
最常用的是PHP FPM和CLI解釋器。
他們的工作非常簡單:接收php代碼,對其進行解釋并向后吐出結果。
這通常發生在每種解釋語言上。有些語言可能會刪除一些步驟,但總體思路是相同的。
在PHP中,它是這樣的:
PHP代碼被讀取并轉換為一組關鍵字,即標記(Tokens)。這個過程允許解釋器理解在程序的哪一部分中寫了哪段代碼。這第一步叫做詞法分析或符號化。
有了Tokens后,PHP解釋器將分析這個Tokens集合,并嘗試理解它們。結果通過一個稱為解析的過程生成了一個抽象語法樹(AST)。這個AST是一組指示應該執行哪些操作的節點。例如,“echo 1 + 1”實際上應該表示“打印1 + 1的結果”,或者更實際一些“打印一個操作,操作是1 + 1”。
3 . 有了AST,理解操作和優先級就容易得多了。將這個樹轉換成可以執行的東西需要一個中間表示(IR),在PHP中我們稱之為操作碼。將AST轉換為操作碼的過程稱為編譯。
現在,有了操作碼,剩下就是執行代碼。PHP有一個名為Zend VM的引擎,它能夠接收操作碼列表并執行它們。在執行了所有操作碼之后,Zend VM就存在了,程序就終止了。
執行的過程使用下圖標識就更加清楚了。
非常直接,正如你們所能理解的。但這里有一個瓶頸:如果php代碼變化不是那么頻繁,那么每次執行代碼時對其進行詞法分析又有什么意義呢?
最后我們只關心操作碼,對吧? 沒錯! 這就是為什么存在Opcache擴展。
Opcache擴展
Opcache擴展是隨PHP附帶的,通常沒有什么理由禁用它。如果使用PHP,應該打開Opcache。
它的作用是為操作碼在內存中添加一個共享緩存層。它的工作是從AST中新生成的操作碼并緩存它們,以便進一步執行可以輕松跳過詞法分析和解析階段。
Opcache擴展的流程示意圖:
PHP使用Opcache的解釋流程。如果文件已經被解析,則php會為其獲取緩存的操作碼,而不是再次解析。
opcache完美地跳過了詞法分析,語法解析和編譯步驟。
注意:這就是PHP 7.4的預加載功能的亮點!它使您可以告訴PHP FPM解析代碼庫,將其轉換為操作碼并甚至在執行任何操作之前就對其進行緩存。
JIT即時編譯器有效地做什么?
如果Opcache可以更快地獲取操作碼,這樣它們就可以直接轉到Zend VM,那么JIT應該讓它們在跳過Zend VM的情況下運行。
Zend VM是一個用C編寫的程序,充當操作碼和CPU本身之間的一個層。JIT所做的是在運行時生成編譯后的代碼,這樣php就可以跳過Zend VM直接轉到CPU。理論上講,我們應該從中獲得性能提升。
起初,這對我來說很奇怪,因為為了編譯機器代碼,您需要為每種類型的體系結構編寫一個非常具體的實現。但事實上,它是相當合理的。
PHP的JIT實現使用名為DynASM (Dynamic Assembler)的庫,該庫將一組特定格式的CPU指令映射為許多不同CPU類型的匯編代碼。因此,Just In Time編譯器使用DynASM將操作碼轉換為特定于架構的機器碼。
不過,有一個想法困擾了我很長一段時間……
如果預加載能夠在執行前將php代碼解析為操作碼,而DynASM可以將操作碼編譯為機器碼(正好是及時編譯),那么為什么我們不使用提前編譯的方法直接編譯php呢?!
我從收聽Zeev的那集中得到的一個線索是PHP是弱類型的,這意味著PHP通常不知道變量的類型,直到Zend VM嘗試執行某個操作碼。
這可以通過查看zend_value聯合類型看出,它有許多指針指向一個變量的不同類型表示。無論何時Zend VM嘗試從zend_value中獲取值,它都會使用像ZSTR_VAL這樣的宏來嘗試從值聯合中訪問字符串指針。
例如,這個Zend VM處理程序應該處理一個“小于或等于”(<=)表達式。看看它是如何分支到許多不同的代碼路徑的,只是為了猜測操作數類型。
用機器碼復制這種類型推斷邏輯是不可行的,可能會使事情變得更慢。
在計算類型之后編譯所有內容也不是一個好選擇,因為編譯成機器碼是一項CPU密集型任務。所以在運行時編譯所有東西也是不好的。
JIT即時編譯器是如何工作的?
現在我們知道我們不能在編譯前推斷出足夠好的類型。我們還知道,在運行時進行編譯是昂貴的。JIT對PHP有什么好處?
為了平衡這個等式,PHP的JIT只嘗試編譯一些它認為可以得到回報的操作碼。為此,它對Zend VM正在執行的操作碼進行概要分析,并檢查哪些操作碼可以編譯。(根據您的配置)
當編譯某個操作碼時,它將把執行委托給編譯后的代碼,而不是委托給Zend VM。看起來如下:
PHP的JIT解釋流程。如果已編譯,則操作碼不會通過Zend VM執行。
所以在Opcache擴展中有一些指令檢測某個操作碼是否應該編譯。如果是,編譯器然后使用DynASM將該操作碼轉換為機器碼,并執行新生成的機器碼。
有趣的是,由于當前實現中編譯的代碼有兆字節限制(也是可配置的),因此代碼執行必須能夠在JIT和解釋代碼之間無縫切換。
我仍然不確定編譯部分什么時候有效地發生,但我想我現在真的不想知道。
因此性能的提高可能不會很大
為什么每個人都說大多數php應用程序不會從使用Just In Time編譯器中獲得很大的性能好處,我希望現在能夠清楚地知道這一點。以及為什么Zeev建議對應用程序進行分析并試驗不同的JIT配置是最好的方法。
如果您使用的是PHP FPM,編譯后的操作碼通常會在多個請求之間共享,但這仍然不能改變游戲規則。
這是因為JIT優化了cpu綁定操作,而現在大多數php應用程序都是I/O綁定的。如果你必須訪問磁盤或網絡,不管處理操作是否被編譯。計時也非常類似。
文章翻譯自https://thephp.website/en/issue/php-8-jit/
5.0
02
Post Views:
942
總結
以上是生活随笔為你收集整理的php 8 jit,深入理解PHP8 JIT的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PHP判断文章是否有图片,利用PHP判断
- 下一篇: Php的if自动转换类型,php输出数据