轉載自鳥哥博客 , 原文地址: https://www.laruence.com/2020/06/27/5963.html
PHP8 alpha1已經在昨天發布,相信關于JIT是大家最關心的,它到底怎么用,有什么要注意的,以及性能提升到底咋樣?
首先,我們來看一張圖:

左圖是PHP8之前的Opcache流程示意圖(zend引擎每次都先解釋后執行), 右圖是PHP8中的Opcache示意圖(zend引擎直接執行機器碼), 可以看出幾個關鍵點:
- Opcache會做opcode層面的優化,比如圖中的倆條opcode合并為一條
- PHP8的JIT目前是在Opcache之中提供的
- JIT在Opcache優化之后的基礎上,結合Runtime的資訊再次優化,直接生成機器碼
- JIT不是原來Opcache優化的替代,是增強
- 目前PHP8只支持x86架構的CPU ( 我在編譯的時候有發現 )
事實上JIT共用了很多原來Opcache做優化的基礎資料結構,比如data flow graph, call graph, SSA等,關于這部分,后續如果有時間,可以單獨在寫一個文章來介紹,今天就只是著重在使用層面,
下載安裝好以后,除掉原有的opcache配置以外,對于JIT我們需要添加如下配置到php.ini:
opcache.jit=1205 opcache.jit_buffer_size=64M
opcache.jit這個配置看起來稍微有點復雜,我來解釋下, 這個配置由4個獨立的數字組成,從左到右分別是(請注意,這個是基于目前alpha1的版本設定,一些配置可能會隨著后續版本做微調):
- 第一個數字是否在生成機器碼點時候使用AVX指令, 需要CPU支持
0: 不使用 1: 使用
第二個數字暫存器分配策略:
0: 不使用暫存器分配 1: 區域(block)域分配 2: 全域(function)域分配
第三個數字是JIT觸發策略
0: PHP腳本載入的時候就JIT 1: 當函式第一次被執行時JIT 2: 在一次運行后,JIT呼叫次數最多的百分之(opcache.prof_threshold * 100)的函式 3: 當函式/方法執行超過N(N和opcache.jit_hot_func相關)次以后JIT 4: 當函式方法的注釋中含有@jit的時候對它進行JIT 5: 當一個Trace執行超過N次(和opcache.jit_hot_loop, jit_hot_return等有關)以后JIT
第四個數字是JIT優化策略,數值越大優化力度越大
0: 不JIT 1: 做opline之間的跳轉部分的JIT 2: 內斂opcode handler呼叫 3: 基于型別推斷做函式級別的JIT 4: 基于型別推斷,程序呼叫圖做函式級別JIT 5: 基于型別推斷,程序呼叫圖做腳本級別的JIT
基于此,我們可以大概得到如下幾個結論:
- opcache.jit的配置項盡量使用12x5型的配置,此時應該是效果最優的
- 對于x, 如果是腳本級別的,推薦使用0, 如果是Web服務型的,可以根據測驗結果選擇3或5
- @jit的形式,在有了attributes以后,可能變為<<jit>>
現在,我們來測驗下啟用和不啟用JIT的時候,Zend/bench.php的差異,首先是不啟用(php -d opcache.jit_buffer_size=0 Zend/bench.php):
不啟用的結果:
simple 0.008 simplecall 0.004 simpleucall 0.004 simpleudcall 0.004 mandel 0.035 mandel2 0.055 ackermann(7) 0.020 ary(50000) 0.004 ary2(50000) 0.003 ary3(2000) 0.048 fibo(30) 0.084 hash1(50000) 0.013 hash2(500) 0.010 heapsort(20000) 0.027 matrix(20) 0.026 nestedloop(12) 0.023 sieve(30) 0.013 strcat(200000) 0.006 ------------------------ Total 0.387
根據上面的介紹,我們選擇opcache.jit=1205, 因為bench.php是腳本(php -d opcache.jit_buffer_size=64M -d opcache.jit=1205 Zend/bench.php):
啟用的結果是:
simple 0.002 simplecall 0.001 simpleucall 0.001 simpleudcall 0.001 mandel 0.010 mandel2 0.011 ackermann(7) 0.010 ary(50000) 0.003 ary2(50000) 0.002 ary3(2000) 0.018 fibo(30) 0.031 hash1(50000) 0.011 hash2(500) 0.008 heapsort(20000) 0.014 matrix(20) 0.015 nestedloop(12) 0.011 sieve(30) 0.005 strcat(200000) 0.004 ------------------------ Total 0.157
可見,對于Zend/bench.php, 相比不開啟JIT,開啟了以后,耗時降低將近60%,性能提升將近2倍,
對于大家研究學習來說,可以通過opcache.jit_debug來觀測JIT后生成的匯編結果,比如對于:
function simple() { $a = 0; for ($i = 0; $i < 1000000; $i++) $a++; }
我們通過php -d opcache.jit=1205 -dopcache.jit_debug=0x01 可以看到:
JIT$simple: ; (/tmp/1.php) sub $0x10, %rsp xor %rdx, %rdx jmp .L2 .L1: add $0x1, %rdx .L2: cmp $0x0, EG(vm_interrupt) jnz .L4 cmp $0xf4240, %rdx jl .L1 mov 0x10(%r14), %rcx test %rcx, %rcx jz .L3 mov $0x1, 0x8(%rcx) .L3: mov 0x30(%r14), %rax mov %rax, EG(current_execute_data) mov 0x28(%r14), %edi test $0x9e0000, %edi jnz JIT$$leave_function mov %r14, EG(vm_stack_top) mov 0x30(%r14), %r14 cmp $0x0, EG(exception) mov (%r14), %r15 jnz JIT$$leave_throw add $0x20, %r15 add $0x10, %rsp jmp (%r15) .L4: mov $0x45543818, %r15 jmp JIT$$interrupt_handler
大家可以嘗試閱讀這段匯編,比如其中針對i的遞增,可以看到優化力度很大,比如因為i是區域變數直接分配在暫存器中,i的范圍推斷不會大于1000000,所以不需要判斷是否整數溢位等等,
而如果我們采用opcache.jit=1005, 如前面的介紹,也就是不使用暫存器分配,可以得到如下結果:
JIT$simple: ; (/tmp/1.php) sub $0x10, %rsp mov $0x0, 0x50(%r14) mov $0x4, 0x58(%r14) jmp .L2 .L1: add $0x1, 0x50(%r14) .L2: cmp $0x0, EG(vm_interrupt) jnz .L4 cmp $0xf4240, 0x50(%r14) jl .L1 mov 0x10(%r14), %rcx test %rcx, %rcx jz .L3 mov $0x1, 0x8(%rcx) .L3: mov 0x30(%r14), %rax mov %rax, EG(current_execute_data) mov 0x28(%r14), %edi test $0x9e0000, %edi jnz JIT$$leave_function mov %r14, EG(vm_stack_top) mov 0x30(%r14), %r14 cmp $0x0, EG(exception) mov (%r14), %r15 jnz JIT$$leave_throw add $0x20, %r15 add $0x10, %rsp jmp (%r15) .L4: mov $0x44cdb818, %r15 jmp JIT$$interrupt_handler
可以看到針對i的部分,現在是在記憶體操作,并沒有使用暫存器,
再如果我們采用opcache.jit=1201, 我們可以得到如下結果:
JIT$simple: ; (/tmp/1.php) sub $0x10, %rsp call ZEND_QM_ASSIGN_NOREF_SPEC_CONST_HANDLER add $0x40, %r15 jmp .L2 .L1: call ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED_HANDLER cmp $0x0, EG(exception) jnz JIT$$exception_handler .L2: cmp $0x0, EG(vm_interrupt) jnz JIT$$interrupt_handler call ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ_HANDLER cmp $0x0, EG(exception) jnz JIT$$exception_handler cmp $0x452a0858, %r15d jnz .L1 add $0x10, %rsp jmp ZEND_RETURN_SPEC_CONST_LABEL
這就只是簡單的內斂部分opcode handler的呼叫了,
你也可以嘗試各種opcache.jit的策略結合debug的配置,來觀測結果的不同,也可以嘗試各種opcache.jit_debug的配置,比如0xff,將會有更多的輔助資訊輸出,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/13938.html
標籤:PHP
