PHP 引擎產生的 PHP 操作碼很大程度上受您編寫程式碼的方式影響。不僅僅在於完成任務的語句數量。顯然這很重要,我認為這對你來說是顯而易見的。
不太明顯的是,即使程式碼的語法也可以完全改變產生的操作碼,導致機器的 CPU 執行完全相同的程式碼產生大量開銷。
在過去的幾年裡,我的 SaaS 產品有了很大的發展,它讓我有機會越來越深入地研究優化技術,以盡可能高效地運行我的工作負載。
我看到的結果令人印象深刻,並且在釋放自由現金流以繼續開發我的 SaaS 之旅方面幫助了我很多。
此時,我的 SaaS 產品內的 PHP 進程每天在具有 2vCPU 和 8GB 記憶體的機器上處理超過 12 億個(帶 B)資料包。 我使用 AWS 自動縮放組在出現不可預測的峰值時擁有更大的靈活性,但它很少添加第二台機器(每週一/兩次)。
想要了解更多技術文章,您可以在 Linkedin 或 X 上關注我。
最近我還寫了 Inspector 伺服器到 ARM 實例的遷移:https://inspector.dev/inspector-adoption-of-graviton-arm-instances-and-what-results-weve-seen/
下面進入文章主題吧。我想你會發現它很有趣。
PHP opcode 代表操作碼,是指您所寫的 PHP 原始碼編譯後,由 PHP 引擎執行的低階指令。
在PHP 中,程式碼編譯發生在運行時,基本上,當您的程式碼第一次被PHP 引擎取得時,它會被編譯成機器友好的程式碼並緩存,因此引擎不會再次編譯相同的程式碼,並且然後被處死。
這是過程的簡單表示:
快取 PHP 操作碼可以讓您在執行程式碼的過程中節省三個步驟:解析原始 PHP 程式碼、標記化和編譯。
第一次產生操作碼後,它會儲存在記憶體中,以便在後續請求中重複使用。這減少了 PHP 引擎每次執行時重新編譯相同 PHP 程式碼的需要,節省了大量的 CPU 和記憶體消耗。
PHP 中最常用的操作碼快取是 OPCache,從 PHP 5.5 到最新版本預設包含它。它效率高且得到廣泛支持。
快取預編譯腳本字節碼需要在每次部署後使快取失效。因為如果更改的檔案在快取中具有字節碼版本,PHP 將繼續運行舊版本的程式碼。直到您清除操作碼緩存,以便再次編譯新程式碼並產生新的快取項目。
要了解不同的語法如何影響腳本的操作碼,我們需要一種方法來取得 PHP 引擎產生的編譯程式碼。
有兩種取得操作碼的方法。
如果您的電腦上啟用了 OPCache 擴展,您可以使用其本機函數來取得特定 php 檔案的操作碼:
// Force compilation of a script opcache_compile_file(__DIR__.'/yourscript.php'); // Get OPcache status $status = opcache_get_status(); // Inspect the script's entry in the cache print_r($status['scripts'][__DIR__.'/yourscript.php']);
VLD 是一個受歡迎的 PHP 擴展,它可以反彙編已編譯的 PHP 程式碼並輸出操作碼。它是了解 PHP 如何解釋和執行程式碼的強大工具。安裝後,您可以使用帶有 -d 選項的 php 命令來執行啟用了 VLD 的 PHP 腳本:
php -d vld.active=1 -d vld.execute=0 yourscript.php
輸出將包含有關已編譯操作碼的詳細信息,包括每個操作、其關聯的程式碼行等等。
3v4l 是一個非常有用的線上工具,它允許您查看在編輯器中輸入的 PHP 程式碼產生的操作碼。它基本上是一個安裝了 VLD 的 PHP 伺服器,因此它可以抓取 VLD 輸出並向您顯示瀏覽器中的操作碼。
由於它是免費分發的,我們將使用這個線上工具進行下一步分析。
3v4l 非常適合理解我們使用的程式碼語法如何以好或壞的方式影響產生的 PHP 操作碼。讓我們開始將下面的程式碼貼到 3v4l 中。保留配置“所有支援的版本”,然後按一下“評估”。
<?php namespace App; strlen('ciao');
執行程式碼後,底部會出現一個選項卡選單。導覽至 VLD 標籤以視覺化對應的操作碼。
line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 5 0 E > INIT_NS_FCALL_BY_NAME 'App%5CSpace%5Cstrlen' 1 SEND_VAL_EX 'ciao' 2 DO_FCALL 0 3 > RETURN 1
請注意,第一個操作是 INIT_NS_FCALL_BY_NAME。解釋器使用目前檔案的命名空間建構函數的名稱。但它並不存在於 AppExample 命名空間中,那麼它是如何運作的呢?
解譯器將檢查該函數是否存在於目前命名空間中。如果沒有,它會嘗試呼叫對應的核心函數。
這裡我們有機會告訴解釋者避免這種雙重檢查,直接執行核心函數。
嘗試在strlen前面加上一個反斜線(),然後點選「eval」:
<?php namespace App; \strlen('ciao');
在 VLD 標籤中,您現在只需一條語句即可看到操作碼。
line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 5 0 E > > RETURN 1
這是因為你傳達了函數的確切位置,所以它不需要考慮任何後備。
如果不喜歡使用反斜杠,您可以像從根命名空間導入任何其他類別一樣導入該函數:
// Force compilation of a script opcache_compile_file(__DIR__.'/yourscript.php'); // Get OPcache status $status = opcache_get_status(); // Inspect the script's entry in the cache print_r($status['scripts'][__DIR__.'/yourscript.php']);
PHP 引擎還有很多內部自動功能來提前產生評估靜態表達式的最佳化操作碼。這是 PHP 自 7.x 版本以來效能大幅提升的最重要原因之一
了解這些動態確實可以幫助您減少資源消耗並降低成本。一旦我進行了這項研究,我就開始在整個程式碼中使用這些技巧。
讓我向您展示一個使用 PHP 常數的範例。在 3v4l 中執行此腳本:
php -d vld.active=1 -d vld.execute=0 yourscript.php
看一下 PHP 操作碼的前兩行:
<?php namespace App; strlen('ciao');
FETCH_CONSTANT 嘗試從目前命名空間取得 PHP_OS 的值,它將尋找全域命名空間,因為這裡不存在該值。然後 IS_IDENTICAL 指令執行 IF 語句。
現在嘗試將反斜線加到常數:
line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 5 0 E > INIT_NS_FCALL_BY_NAME 'App%5CSpace%5Cstrlen' 1 SEND_VAL_EX 'ciao' 2 DO_FCALL 0 3 > RETURN 1
正如您在操作碼中看到的,引擎不需要嘗試獲取常數,因為現在它在哪裡很清楚,並且由於它是一個靜態值,因此它已經在內存中了。
IF 語句也消失了,因為 IS_IDENTITCAL 語句的另一端是靜態字串('Linux'),因此 IF 可以標記為“true”,而無需在每次執行時解釋它的開銷。
這就是為什麼您有很大的權力來影響 PHP 程式碼的最終效能。
我希望這是一個有趣的話題,正如我在文章開頭提到的那樣,我從使用這種策略中獲得了很多好處,事實上它們也在我們的包中使用。
您可以在這裡看到我如何在PHP 套件中使用這些技巧來優化其效能的範例:https://github.com/inspector-apm/inspector-php/blob/master/src/Inspector.php # L302
想要了解更多技術文章,您可以在 Linkedin 或 X 上關注我。
Inspector是一款專為軟體開發人員設計的程式碼執行監控工具。您無需在伺服器層級安裝任何內容,只需安裝 Laravel 或 Symfony 軟體套件即可開始使用。
如果您正在尋找 HTTP 監控、資料庫查詢見解以及將警報和通知轉發到您首選訊息傳遞環境的能力,請免費嘗試 Inspector。註冊您的帳戶。
或在網站上了解更多:https://inspector.dev
以上是PHP 操作碼 – 無需更改程式碼即可提高應用程式效能的詳細內容。更多資訊請關注PHP中文網其他相關文章!