PHP Opcode: 無需更改代碼即可提升應用性能

以下為內容翻譯成繁體中文:

PHP引擎生成的PHP操作碼(opcode)很大程度上受到您編寫代碼的方式的影響,不僅僅是完成任務所需的語句數量。這顯然很重要,我認為您也很明白。

可能不太明顯的是,即使代碼的語法也可以完全改變生成的操作碼,從而給機器的CPU帶來很大的開銷來執行完全相同的代碼。

在過去幾年中,我的SaaS產品有了很大的發展,這讓我有機會更深入地探索優化技術,以盡可能高效地運行我的工作負載。

我看到的結果令人印象深刻,並在很大程度上幫助我解鎖了自由現金流,以繼續發展我的SaaS之路。

此時,我SaaS產品中的PHP進程每天處理超過12億(十億)個數據包,運行在一台擁有2個虛擬CPU和8GB內存的機器上。我使用AWS自動伸縮組來提高彈性,以應對不可預測的峰值,但很少需要添加第二台機器(一週一到兩次)。

讓我們進入本文的主題。我相信您會發現它非常有趣。

什麼是PHP操作碼(Opcode)?

PHP操作碼(Opcode)代表操作碼,指的是在PHP引擎執行您編寫的PHP源代碼後生成的底層指令。

在PHP中,代碼編譯發生在運行時:基本上,當您的代碼第一次被PHP引擎接受時,它將被編譯成這種機器友好的代碼,並被緩存(以便引擎不需要再次編譯相同的代碼),然後再執行。

这是一个簡單的過程表示:

PHP 指令碼快取

快取 PHP 指令碼讓您在執行代碼的过程中節省三個步驟:解析原始 PHP 代碼、符號化,以及編譯。

一旦第一次生成了指令碼,它就会被儲存在記憶體中,以便在隨後的請求中重用。這減少 PHP 引擎每次執行相同 PHP 代碼時都需要重新編譯的需求,節省了大量的 CPU 和記憶體消耗。

PHP 中最常使用的指令碼快取是 OPCache,從 PHP 5.5 版本到最近版本都 default 涵蓋。它非常高效且广受支持。

快取前置編譯腳本的字節碼需要在每次部署後失效。這是因為如果更改的文件中有字節碼版本在快取中,PHP 會繼續運行旧版本的代碼,直到您清除指令碼快取,所以新的代碼將再次編譯生成新的快取項。

如何調查 PHP 指令碼

為了了解不同的語法如何影響腳本的指令碼,我們需要一種方法來捕獲 PHP 引擎生成的编译代碼。

有兩種方法可以獲得指令碼。

OPCache 原生功能

如果您在您的電腦上启用了 OPCache 擴展,您可以使用其原生功能來獲取特定 PHP 文件的指令碼:

PHP

 

VLD (Vulcan Logic Disassembler) PHP 擴展

VLD 是一款流行的 PHP 擴展,用於解開編譯的 PHP 程式碼並輸出 opcode。它是理解 PHP 是如何解釋和執行您的程式碼的强大工具。一旦安裝,您可以通過使用带有 -d 選項的 php 命令來運行一個啟用了 VLD 的 PHP 腳本:

Shell

 

輸出將包括關於編譯 opcode 的詳細信息,包括每個操作、相關的程式碼行等。

使用 3v4l(EVAL 的缩寫)

3v4l 是一個非常實用的线上工具,允許您查看由您在編輯器中輸入的 PHP 代码生成的 opcode。它基本上是一個搭載了 VLD 的 PHP 服務器,因此它可以捕捉 VLD 输出的 opcode 并在瀏覽器中展示给您。

因為它是免费的,我們將使用這個线上工具進行下一個分析。

如何生成高效的 PHP Opcode

3v4l 對於理解我們使用的程式語法如何影響生成的 PHP opcode 非常好。讓我們開始將代碼粘贴到 3v4l 中。保持配置為“所有受支持的版本”,並點擊“eval”。

PHP

 

執行代碼後,底部的标签選單將會出現。導航到 VLD  標籤以查看對應的 Opcode。

Shell

 

注意,第一次操作是 INIT_NS_FCALL_BY_NAME。解释器使用当前文件的命名空间构建函数名称,但在 App\Example 命名空间中不存在——那么它是如何工作的?

解释器将检查函数是否存在于当前命名空间中。如果不存在,它将尝试调用相应的核心函数。

在这里,我们有机会告诉解释器避免这种双重检查,并直接执行核心函数。

尝试在 strlen 之前添加一个反斜杠(\)并点击“eval”:

PHP

 

VLD 标签中,你现在可以看到只有一个语句的opcode。

因为你已经传达了函数的确切位置,所以它不需要考虑任何回退。

如果你不喜欢使用反斜杠,你可以像导入其他类一样从根命名空间导入函数:

PHP

 

利用自动opcode优化

PHP引擎还有许多内部自动化机制,用于提前生成优化opcode评估静态表达式。这是自PHP 7.x版本以来性能大幅提升的最重要原因之一。

了解这些动态确实可以帮助你减少资源消耗和降低成本。自从我进行这项研究以来,我一直在代码中使用这些技巧。

让我给你一个使用PHP常量的示例。将这个脚本运行到3v4l中:

PHP

 

查看PHP opcode的第一二行:

FETCH_CONSTANT 嘗試從當前命名空間中取得 PHP_OS 的值,若在此處不存在,它將查看全局命名空間。然後,IS_IDENTICAL 指令執行 IF 語句。

現在嘗試向常數中添加反斜杠:

PHP

 

如您在 opcode 中所見,引擎不需要嘗試取得常數,因為現在很清楚地知道它在哪裡,而且由於它是一個靜態值,所以它已經在記憶體中。

此外,IF 語句消失了的,因為IS_IDENTICAL語句的另一邊是一個靜態字符串(‘Linux’),所以IF 可以用“true”來標記,而無需在每次執行時解讀它。

这就是为什么您有很高的權力影響 PHP 代碼的最終性能。

結論

我希望這是一個有趣的話題。如文章開頭我所提到的,我從使用這種策略中獲得了很多好處,實際上,它們也用在我们的套件中。

您可以在這裡看到一個示例,展示我如何使用這些技巧來優化我們 PHP 套件的性能。

如果您想了解更多關於建立 Developer-driven 公司的挑戰,您可以跟隨我在 LinkedIn 上。

Source:
https://dzone.com/articles/php-opcode-improve-application-performance