搜尋
首頁後端開發php教程php中關於opcode優化的深入理解(圖文)

1.概述

PHP(本文所述案例PHP版本均為7.1.3)作為一門動態腳本語言,其在zend虛擬機執行過程為:讀入腳本程式字串,經由詞法分析器將其轉換為單字符號,接著語法分析器從中發現語法結構後生成抽象語法樹,再經靜態編譯器生成opcode,最後經解釋器模擬機器指令來執行每一條opcode。

在上述整個環節中,產生的opcode可以應用編譯最佳化技術如死程式碼刪除、條件常數傳播、函數內聯等各種最佳化來精簡opcode,達到提高程式碼的執行效能的目的。

PHP擴展opcache,針對生成的opcode基於共享記憶體支援了快取最佳化。在此基礎上又加入了opcode的靜態編譯最佳化。這裡所述最佳化通常採用優化器(Optimizer)來管理,編譯原理中,一般用最佳化遍(Opt pass)來描述每一個最佳化。

整體上說,優化遍分兩種:

  • 一種是分析pass,是提供資料流、控制流分析資訊為轉換pass提供輔助資訊;

  • 一種是轉換pass,它會改變產生程式碼,包含增刪指令、改變替換指令、調整指令順序等,通常每一個pass前後可dump出生成程式碼的變化。

本文基於編譯原理,結合opcache擴充提供的最佳化器,以PHP編譯基本單位op_array、PHP執行最小單位opcode為出發點。介紹編譯最佳化技術在Zend虛擬機器的應用,梳理各個最佳化遍是如何一步步優化opcode來提升程式碼執行效能的。最後結合PHP語言虛擬機器執行給予幾點展望。

2.幾個概念說明

1)靜態編譯/解釋執行/即時編譯

靜態編譯(static compilation),也稱事前編譯(ahead-of- time compilation),簡稱AOT。即把原始碼編譯成目標程式碼,執行時在支援目標程式碼的平台上執行。

動態編譯(dynamic compilation),相對於靜態編譯而言,指」在執行時進行編譯」。通常情況下採用解釋器(interpreter)編譯執行,它是指一條一條的解釋執行原始語言。

JIT編譯(just-in-time compilation),即時編譯,狹義指某段程式碼即將第一次被執行時進行編譯,而後則不用編譯直接執行,它為動態編譯的一種特例。

上述三類不同編譯執行流程,可大體如下圖來描述:php中關於opcode優化的深入理解(圖文)

2)資料流/控制流

編譯最佳化需要從程式中獲取足夠的信息,這是所有編譯最佳化的根基。

編譯器前端產生的結果可以是語法樹也可以是某種低階中間程式碼。但無論結果什麼形式,它對程式做什麼、如何做仍然沒有提供太多資訊。編譯器將發現每個流程內控制流程層次結構的任務留給控制流程分析,將決定與資料處理相關的全域資訊任務留給資料流分析。

  • 控制流是取得程式控制結構資訊的形式化分析方法,它為資料流分析、依賴分析的基礎。控制的一個基本模型是控制流程圖(Control Flow Graph,CFG)。單一過程的控制流分析有使用必經結點找循環、區間分析兩種途徑。

  • 資料流從程式碼收集程式的語意訊息,並透過代數的方法在編譯時確定變數的定義和使用。資料的一個基本模型是資料流程圖(Data Flow Graph,DFG)。通常的資料流程分析是基於控制樹的分析(Control-tree-based data-flow analysis),演算法分為區間分析與結構分析兩種。

3)op_array

類似於C語言的堆疊幀(stack frame)概念,即執行程式的基本單位(一幀),一般為一次函數調用的基本單位。此處,一個函數或方法、整個PHP腳本檔、傳給eval表示PHP程式碼的字串都會被編譯成一個op_array。

實作上op_array為一個包含程式運行基本單位的所有資訊的結構體,當然opcode數組為此結構最為重要的字段,不過除此之外還包含變數類型、註解資訊、異常捕獲訊息、跳轉資訊等。

4)opcode

解釋器執行(ZendVM)程序即是執行一個基本單位op_array內的最小最佳化opcode,依序遍歷執行,執行目前opcode,會預先取下一條opcode ,直到最後一個RETRUN這個特殊的opcode返回退出。

這裡的opcode某種程度也類似於靜態編譯器裡的中間表示(類似於LLVM IR),通常也採用三位址碼的形式,即包含一個運算符,兩個操作數及一個運算結果。其中兩個操作數均包含類型資訊。此處型別資訊有五種,分別為:

  • 編譯變數(Compiled Variable,簡稱CV),編譯時變數即為php腳本定義的變數。

  • 內部可重複使用變數(VAR),供ZendVM使用的暫存變量,可與其它opcode共用。

  • 內部不可重複使用變數(TMP_VAR),供ZendVM使用的暫存變量,不可與其它opcode共用。

  • 常數(CONST),只讀常數,值不可被更改。

  • 無用變數(UNUSED)。由於opcode採用三位址碼,並非每一個opcode都有操作數字段,缺省時用此變數補位欄位。

類型資訊與運算元一起,供執行器匹配選擇特定已編譯好的C函數庫模板,模擬產生機器指令來執行。

opcode在ZendVM中以zend_op結構體來表徵,其主體結構如下:
php中關於opcode優化的深入理解(圖文)

3.opcache optimizer優化器

#PHP腳本經過詞法分析、語法分析產生抽象語法樹結構後,再經靜態編譯生成opcode。它作為向不同的虛擬機器執行指令的公共平台,依賴不同的虛擬機器具體實作(然對於PHP來說,大部分是指ZendVM)。

在虛擬機器執行opcode之前,如果對opcode進行最佳化可得到執行效率更高的程式碼,pass的作用就是優化opcode,它作用於opcde、處理opcode、分析opcode、尋找最佳化的機會並修改opcode產生更高執行效率的程式碼。

1)ZendVM優化器簡介

在Zend虛擬機器(ZendVM)中,opcache的靜態程式碼最佳化器即為zend opcode optimization。

為觀察最佳化效果及方便除錯,它也提供了最佳化與偵錯選項:

  • optimizationlevel (opcache.optimizationlevel=0xFFFFFFFFFF )最佳化級別,預設開啟大部分最佳化遍,使用者也透過傳入命令列參數控制關閉

  • #optdebuglevel (opcache.opt# debuglevel=-1) 調試級別,缺省不打開,但提供了各優化前後opcode的變換過程

執行靜態優化所需的腳本上下文資訊則封裝在結構zend_script中,如下:

typedef struct _zend_script {  
    zend_string   *filename;        //文件名
    zend_op_array  main_op_array;   //栈帧
    HashTable      function_table;  //函数单位符号表信息
    HashTable      class_table;     //类单位符号表信息
} zend_script;

上述三個內容資訊即作為輸入參數傳遞給優化器供其分析優化。當然與通常的PHP擴充類似,它與opcode快取模組一起(zend_accel)構成了opcache擴充。其在快取加速器內嵌入了三個內部API:

  • zendoptimizerstartup 啟動優化器

  • zendoptimizescript 最佳化器實作最佳化的主邏輯

  • zendoptimizershutdown 最佳化器產生的資源清理

#關於opcode緩存,也是opcode非常重要的最佳化。其基本應用原理是大體如下:

雖然PHP作為動態腳本語言,它並不會直接呼叫GCC/LLVM這樣的整套編譯器工具鏈,也不會呼叫Javac這樣的純前端編譯器。但每次請求執行PHP腳本時,都經歷過詞法、語法、編譯為opcode、VM執行的完整生命週期。

除去執行外的前三個步驟基本上就是一個前端編譯器的完整過程,然而這個編譯過程並不會快。如果重複執行相同的腳本,前三個步驟編譯耗時將嚴重限制運作效率,而每次編譯產生的opcode則沒有變化。因此可在第一次編譯時把opcode快取到某個地方,opcache擴充就是將其快取到共享記憶體(Java則是儲存到檔案),下次執行相同腳本時直接從共享記憶體中取得opcode,從而省去編譯時間。

opcache擴充的opcode 快取流程大致如下:
php中關於opcode優化的深入理解(圖文)由於本文主要集中討論靜態最佳化遍,關於快取最佳化的具體實作此處不展開。

2)ZendVM最佳化器原理

依「鯨書」(《高階編譯器設計與實作》)所述,一個最佳化編譯器較為合理的最佳化遍順序如下:php中關於opcode優化的深入理解(圖文)
上圖中涉及的最佳化從簡單的常數、死程式碼到循環、分支跳轉,從函數呼叫到過程間優化,從預取、快取到軟流水、暫存器分配,當然也包含資料流、控制流分析。

當然,目前opcode優化器並沒有實現上述所有優化遍,也沒有必要實現機器相關的低層中間表示優化如寄存器分配。

opcache最佳化器接收到上述腳本參數資訊後,找到最小編譯單位。以此為基礎,根據優化pass宏及其對應的最佳化等級宏,即可實現對某一個pass的註冊控制。

註冊的最佳化中,依一定順序組織串聯各優化,包含常數優化、冗餘nop刪除、函數呼叫優化的轉換pass,及資料流分析、控制流分析、呼叫關係分析等分析pass 。

zendoptimizescript及實際的最佳化註冊zend_optimize流程如下:

zend_optimize_script(zend_script *script,  
      zend_long optimization_level, zend_long debug_level)
    |zend_optimize_op_array(&script->main_op_array, &ctx);
        遍历二元操作符的常量操作数,由运行时转化为编译时(反向pass2)
        实际优化pass,zend_optimize
        遍历二元操作符的常量操作数,由编译时转化为运行时(pass2)
    |遍历op_array内函数zend_optimize_op_array(op_array, &ctx);
    |遍历类内非用户函数zend_optimize_op_array(op_array, &ctx);
       (用户函数设static_variables)
    |若使用DFA pass & 调用图pass & 构建调用图成功
         遍历二元操作符的常量操作数,由运行时转化为编译时(反向pass2)
         设置函数返回值信息,供SSA数据流分析使用
         遍历调用图的op_array,做DFA分析zend_dfa_analyze_op_array
         遍历调用图的op_array,做DFA优化zend_dfa_optimize_op_array
         若开调试,遍历dump调用图的每一个op_array(优化变换后)
         若开栈矫正优化,矫正栈大小adjust_fcall_stack_size_graph
         再次遍历调用图内的的所有op_array,
           针对DFA pass变换后新产生的常量场景,常量优化pass2再跑一遍
         调用图op_array资源清理
    |若开栈矫正优化
          矫正栈大小main_op_array
          遍历矫正栈大小op_array
    |清理资源

该部分主要调用了SSA/DFA/CFG这几类用于opcode分析pass,涉及的pass有BB块、CFG、DFA(CFG、DOMINATORS、LIVENESS、PHI-NODE、SSA)。

用于opcode转换的pass则集中在函数zend_optimize内,如下:

zend_optimize  
|op_array类型为ZEND_EVAL_CODE,不做优化
|开debug,    可dump优化前内容
|优化pass1,  常量替换、编译时常量操作变换、简单操作转换
|优化pass2    常量操作转换、条件跳转指令优化
|优化pass3    跳转指令优化、自增转换
|优化pass4    函数调用优化(主要为函数调用优化)
|优化pass5    控制流图(CFG)优化
 |构建流图
 |计算数据依赖
 |划分BB块(basic block,简称BB,数据流分析基本单位)
 |BB块内基于数据流分析优化
 |BB块间跳转优化
 |不可到达BB块删除 
 |BB块合并
 |BB块外变量检查 
 |重新构建优化后的op_array(基于CFG)
 |析构CFG     
|优化pass6/7  数据流分析优化
 |数据流分析(基于静态单赋值SSA)
  |构建SSA
  |构建CFG  需要找到对应BB块序号、管理BB块数组、计算BB块后继BB、标记可到达BB块、计算BB块前驱BB
  |计算Dominator树
  |标识循环是否可简化(主要依赖于循环回边)
  |基于phi节点构建完SSA  def集、phi节点位置、SSA构造重命名
  |计算use-def链
  |寻找不当依赖、后继、类型及值范围值推断
 |数据流优化  基于SSA信息,一系列BB块内opcode优化
 |析构SSA
|优化pass9    临时变量优化
|优化pass10   冗余nop指令删除
|优化pass11   压缩常量表优化

还有其他一些优化遍如下:

优化pass12   矫正栈大小
优化pass15   收集常量信息
优化pass16   函数调用优化,主要是函数内联优化

除此之外,pass 8/13/14可能为预留pass id。由此可看出当前提供给用户选项控制的opcode转换pass有13个。但是这并不计入其依赖的数据流/控制流的分析pass。

3)函数内联pass的实现

通常在函数调用过程中,由于需要进行不同栈帧间切换,因此会有开辟栈空间、保存返回地址、跳转、返回到调用函数、返回值、回收栈空间等一系列函数调用开销。因此对于函数体适当大小情况下,把整个函数体嵌入到调用者(Caller)内部,从而不实际调用被调用者(Callee)是一个提升性能的利器。

由于函数调用与目标机的应用二进制接口(ABI)强相关,静态编译器如GCC/LLVM的函数内联优化基本是在指令生成之前完成。

ZendVM的内联则发生在opcode生成后的FCALL指令的替换优化,pass id为16,其原理大致如下:

| 遍历op_array中的opcode,找到DO_XCALL四个opcode之一
| opcode ZEND_INIT_FCALL
| opcode ZEND_INIT_FCALL_BY_NAMEZ
     | 新建opcode,操作码置为ZEND_INIT_FCALL,计算栈大小,
        更新缓存槽位,析构常量池字面量,替换当前opline的opcode
| opcode ZEND_INIT_NS_FCALL_BY_NAME
     | 新建opcode,操作码置为ZEND_INIT_FCALL,计算栈大小,
        更新缓存槽位,析构常量池字面量,替换当前opline的opcode
| 尝试函数内联
     | 优化条件过滤 (每个优化pass通常有较多限制条件,某些场景下
         由于缺乏足够信息不能优化或出于代价考虑而排除) 
        | 方法调用ZEND_INIT_METHOD_CALL,直接返回不内联
        | 引用传参,直接返回不内联
        | 缺省参数为命名常量,直接返回不内联
     | 被调用函数有返回值,添加一条ZEND_QM_ASSIGN赋值opcode
     | 被调用函数无返回值,插入一条ZEND_NOP空opcode 
     | 删除调用被内联函数的call opcode(即当前online的前一条opcode)

如下示例代码,当调用fname()时,使用字符串变量名fname来动态调用函数foo,而没有使用直接调用的方式。此时可通过VLD扩展查看其生成的opcode,或打开opcache调试选项(opcache.optdebuglevel=0xFFFFFFFF)亦可查看。

function foo() { }  
$fname = 'foo';

开启debug后dump可看出,发生函数调用优化前opcode序列(仅截取片段)为:

ASSIGN CV0($fname) string("foo")  
INIT_FCALL_BY_NAME 0 CV0($fname)  
DO_FCALL_BY_NAME

INIT_FCALL_BY_NAME这条opcode执行逻辑较为复杂,当开启激进内联优化后,可将上述指令序列直接合并成一条DO_FCALL string("foo")指令,省去间接调用的开销。这样也恰好与直接调用生成的opcode一致。

4)如何为opcache opt添加一个优化pass

根据以上描述,可见向当前优化器加入一个pass并不会太难,大体步骤如下:

  • 先向zend_optimize优化器注册一个pass宏(例如添加pass17),并决定其优化级别。

  • 在优化管理器某个优化pass前后调用加入的pass(例如添加一个尾递归优化pass),建议在DFA/SSA分析pass之后添加,因为此时获得的优化信息更多。

  • 实现新加入的pass,进行定制代码转换(例如zendoptimizefunc_calls实现一个尾递归优化)。针对当前已有pass,主要添加转换pass,这里一般也可利用SSA/DFA的信息。不同于静态编译优化一般是在贴近于机器相关的低层中间表示优化,这里主要是在opcode层的opcode/operand相应的一些转换。

  • 实现pass前,与函数内联类似,通常首先收集优化所需信息,然后排除掉不适用该优化的一些场景(如非真正的尾不递归调用、参数问题无法做优化等)。实现优化后,可dump优化前后生成opcode结构的变化是否优化正确、是否符合预期(如尾递归优化最终的效果是变换函数调用为forloop的形式)。

4.一点思考

以下是对基于动态的PHP脚本程序执行的一些看法,仅供参考。

由于LLVM从前端到后端,从静态编译到jit整个工具链框架的支持,使得许多语言虚拟机都尝试整合。当前PHP7时代的ZendVM官方还没采用,原因之一虚拟机opcode承载着相当复杂的分析工作。相比于静态编译器的机器码每一条指令通常只干一件事情(通常是CPU指令时钟周期),opcode的操作数(operand)由于类型不固定,需要在运行期间做大量的类型检查、转换才能进行运算,这极度影响了执行效率。即使运行时采用jit,以byte code为单位编译,编译出的字节码也会与现有解释器一条一条opcode处理类似,类型需要处理、也不能把zval值直接存在寄存器。

以函数调用为例,比较现有的opcode执行与静态编译成机器码执行的区别,如下图:php中關於opcode優化的深入理解(圖文)

类型推断

在不改变现有opcode设计的前提下,加强类型推断能力,进而为opcode的执行提供更多的类型信息,是提高执行性能的可选方法之一。

多层opcode

既然opcode承担如此复杂的分析工作,能否将其分解成多层的opcode归一化中间表示( intermediate representation, IR)。各优化可选择应用哪一层中间表示,传统编译器的中间表示依据所携带信息量、从抽象的高级语言到贴近机器码,分成高级中间表示(HIR) 、中级中间表示(MIR)、低级中间表示(LIR)。

pass管理

關於opcode的最佳化pass管理,如前文鯨書圖所述,應該仍有改進空間。雖然目前分析依賴的有資料流/控制流分析,但仍缺少諸如過程間的分析優化,pass管理如運行順序、運行次數、註冊管理、複雜pass分析的資訊dump等相對於llvm等成熟框架仍有較大差距。

JIT

ZendVM實現大量的zval值、類型轉換等操作,這些可藉助LLVM編譯成機器碼用於運行時,但代價是編譯時間極速膨脹。當然也可採用libjit。

以上是php中關於opcode優化的深入理解(圖文)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
PHP行動:現實世界中的示例和應用程序PHP行動:現實世界中的示例和應用程序Apr 14, 2025 am 12:19 AM

PHP在電子商務、內容管理系統和API開發中廣泛應用。 1)電子商務:用於購物車功能和支付處理。 2)內容管理系統:用於動態內容生成和用戶管理。 3)API開發:用於RESTfulAPI開發和API安全性。通過性能優化和最佳實踐,PHP應用的效率和可維護性得以提升。

PHP:輕鬆創建交互式Web內容PHP:輕鬆創建交互式Web內容Apr 14, 2025 am 12:15 AM

PHP可以輕鬆創建互動網頁內容。 1)通過嵌入HTML動態生成內容,根據用戶輸入或數據庫數據實時展示。 2)處理表單提交並生成動態輸出,確保使用htmlspecialchars防XSS。 3)結合MySQL創建用戶註冊系統,使用password_hash和預處理語句增強安全性。掌握這些技巧將提升Web開發效率。

PHP和Python:比較兩種流行的編程語言PHP和Python:比較兩種流行的編程語言Apr 14, 2025 am 12:13 AM

PHP和Python各有優勢,選擇依據項目需求。 1.PHP適合web開發,尤其快速開發和維護網站。 2.Python適用於數據科學、機器學習和人工智能,語法簡潔,適合初學者。

PHP的持久相關性:它還活著嗎?PHP的持久相關性:它還活著嗎?Apr 14, 2025 am 12:12 AM

PHP仍然具有活力,其在現代編程領域中依然佔據重要地位。 1)PHP的簡單易學和強大社區支持使其在Web開發中廣泛應用;2)其靈活性和穩定性使其在處理Web表單、數據庫操作和文件處理等方面表現出色;3)PHP不斷進化和優化,適用於初學者和經驗豐富的開發者。

PHP的當前狀態:查看網絡開發趨勢PHP的當前狀態:查看網絡開發趨勢Apr 13, 2025 am 12:20 AM

PHP在現代Web開發中仍然重要,尤其在內容管理和電子商務平台。 1)PHP擁有豐富的生態系統和強大框架支持,如Laravel和Symfony。 2)性能優化可通過OPcache和Nginx實現。 3)PHP8.0引入JIT編譯器,提升性能。 4)雲原生應用通過Docker和Kubernetes部署,提高靈活性和可擴展性。

PHP與其他語言:比較PHP與其他語言:比較Apr 13, 2025 am 12:19 AM

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP與Python:核心功能PHP與Python:核心功能Apr 13, 2025 am 12:16 AM

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

PHP:網絡開發的關鍵語言PHP:網絡開發的關鍵語言Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境