PHP8 alpha1已經(jīng)在昨天發(fā)布,相信關(guān)于JIT是大家最關(guān)心的,它到底怎么用,有什么要注意的,以及性能提升到底咋樣?
首先,我們來看一張圖:

左圖是PHP8之前的Opcache流程示意圖, 右圖是PHP8中的Opcache示意圖, 可以看出幾個(gè)關(guān)鍵點(diǎn):
Opcache會(huì)做opcode層面的優(yōu)化,比如圖中的倆條opcode合并為一條
PHP8的JIT目前是在Opcache之中提供的
JIT在Opcache優(yōu)化之后的基礎(chǔ)上,結(jié)合Runtime的信息再次優(yōu)化,直接生成機(jī)器碼
JIT不是原來Opcache優(yōu)化的替代,是增強(qiáng)
目前PHP8只支持x86架構(gòu)的CPU
事實(shí)上JIT共用了很多原來Opcache做優(yōu)化的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),比如data flow graph, call graph, SSA等,關(guān)于這部分,后續(xù)如果有時(shí)間,可以單獨(dú)在寫一個(gè)文章來介紹,今天就只是著重在使用層面。
下載安裝好以后,除掉原有的opcache配置以外,對于JIT我們需要添加如下配置到php.ini:
10年架構(gòu)師領(lǐng)你架構(gòu)-成長之路-(附面試題(含答案))
(騰訊T3-T4)打造互聯(lián)網(wǎng)PHP架構(gòu)師教程目錄大全,只要你看完,薪資立馬提升2倍(持續(xù)更新)
<pre class="public-DraftStyleDefault-pre" data-offset-key="8k2f0-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
<pre class="Editable-styled" data-block="true" data-editor="8pipe" data-offset-key="8k2f0-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">
opcache.jit=1205
opcache.jit_buffer_size=64M
</pre>
</pre>
opcache.jit這個(gè)配置看起來稍微有點(diǎn)復(fù)雜,我來解釋下, 這個(gè)配置由4個(gè)獨(dú)立的數(shù)字組成,從左到右分別是(請注意,這個(gè)是基于目前alpha1的版本設(shè)置,一些配置可能會(huì)隨著后續(xù)版本做微調(diào)):
- 是否在生成機(jī)器碼點(diǎn)時(shí)候使用AVX指令, 需要CPU支持:
0: 不使用
1: 使用
- 寄存器分配策略:
0: 不使用寄存器分配
1: 局部(block)域分配
2: 全局(function)域分配
- JIT觸發(fā)策略:
0: PHP腳本載入的時(shí)候就JIT
1: 當(dāng)函數(shù)第一次被執(zhí)行時(shí)JIT
2: 在一次運(yùn)行后,JIT調(diào)用次數(shù)最多的百分之(opcache.prof_threshold * 100)的函數(shù)
3: 當(dāng)函數(shù)/方法執(zhí)行超過N(N和opcache.jit_hot_func相關(guān))次以后JIT
4: 當(dāng)函數(shù)方法的注釋中含有@jit的時(shí)候?qū)λM(jìn)行JIT
5: 當(dāng)一個(gè)Trace執(zhí)行超過N次(和opcache.jit_hot_loop, jit_hot_return等有關(guān))以后JIT
- JIT優(yōu)化策略,數(shù)值越大優(yōu)化力度越大:
0: 不JIT
1: 做opline之間的跳轉(zhuǎn)部分的JIT
2: 內(nèi)斂opcode handler調(diào)用
3: 基于類型推斷做函數(shù)級別的JIT
4: 基于類型推斷,過程調(diào)用圖做函數(shù)級別JIT
5: 基于類型推斷,過程調(diào)用圖做腳本級別的JIT
基于此,我們可以大概得到如下幾個(gè)結(jié)論:
盡量使用12x5型的配置,此時(shí)應(yīng)該是效果最優(yōu)的
對于x, 如果是腳本級別的,推薦使用0, 如果是Web服務(wù)型的,可以根據(jù)測試結(jié)果選擇3或5
@jit的形式,在有了attributes以后,可能變?yōu)?lt;<jit>>
現(xiàn)在,我們來測試下啟用和不啟用JIT的時(shí)候,Zend/bench.php的差異,首先是不啟用(php -d opcache.jit_buffer_size=0 Zend/bench.php):
<pre class="public-DraftStyleDefault-pre" data-offset-key="7nuhj-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
<pre class="Editable-styled" data-block="true" data-editor="8pipe" data-offset-key="7nuhj-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">
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
</pre>
</pre>
根據(jù)上面的介紹,我們選擇opcache.jit=1205, 因?yàn)閎ench.php是腳本(php -d opcache.jit_buffer_size=64M -d opcache.jit=1205 Zend/bench.php):
<pre class="public-DraftStyleDefault-pre" data-offset-key="6m541-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
<pre class="Editable-styled" data-block="true" data-editor="8pipe" data-offset-key="6m541-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">
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
</pre>
</pre>
可見,對于Zend/bench.php, 相比不開啟JIT,開啟了以后,耗時(shí)降低將近60%,性能提升將近2倍。
感謝大家一直來支持,這是我準(zhǔn)備的1000粉絲福利
【1000粉絲福利】10年架構(gòu)師分享PHP進(jìn)階架構(gòu)資料,助力大家都能30K
對于大家研究學(xué)習(xí)來說,可以通過opcache.jit_debug來觀測JIT后生成的匯編結(jié)果,比如對于:
<pre class="public-DraftStyleDefault-pre" data-offset-key="ai1ka-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
<pre class="Editable-styled" data-block="true" data-editor="8pipe" data-offset-key="ai1ka-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">
function simple() {
i = 0;
i++)
$a++;
}
</pre>
</pre>
我們通過php -d opcache.jit=1205 -dopcache.jit_debug=0x01 可以看到:
<pre class="public-DraftStyleDefault-pre" data-offset-key="87v3v-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
<pre class="Editable-styled" data-block="true" data-editor="8pipe" data-offset-key="87v3v-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">
JIT0x10, %rsp
xor %rdx, %rdx
jmp .L2
.L1:
add 0x0, EG(vm_interrupt)
jnz .L4
cmp 0x1, 0x8(%rcx)
.L3:
mov 0x30(%r14), %rax
mov %rax, EG(current_execute_data)
mov 0x28(%r14), %edi
test leave_function
mov %r14, EG(vm_stack_top)
mov 0x30(%r14), %r14
cmp leave_throw
add 0x10, %rsp
jmp (%r15)
.L4:
mov interrupt_handler
</pre>
</pre>
大家可以嘗試閱讀這段匯編,比如其中針對i的遞增,可以看到優(yōu)化力度很大,比如因?yàn)閕是局部變量直接分配在寄存器中,i的范圍推斷不會(huì)大于1000000,所以不需要判斷是否整數(shù)溢出等等。
而如果我們采用opcache.jit=1005, 如前面的介紹,也就是不使用寄存器分配,可以得到如下結(jié)果:
<pre class="public-DraftStyleDefault-pre" data-offset-key="4l7l1-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
<pre class="Editable-styled" data-block="true" data-editor="8pipe" data-offset-key="4l7l1-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">
JIT0x10, %rsp
mov 0x4, 0x58(%r14)
jmp .L2
.L1:
add 0x0, EG(vm_interrupt)
jnz .L4
cmp 0x1, 0x8(%rcx)
.L3:
mov 0x30(%r14), %rax
mov %rax, EG(current_execute_data)
mov 0x28(%r14), %edi
test leave_function
mov %r14, EG(vm_stack_top)
mov 0x30(%r14), %r14
cmp leave_throw
add 0x10, %rsp
jmp (%r15)
.L4:
mov interrupt_handler
</pre>
</pre>
可以看到針對i的部分,現(xiàn)在是在內(nèi)存操作,并沒有使用寄存器。
再如果我們采用opcache.jit=1201, 我們可以得到如下結(jié)果:
<pre class="public-DraftStyleDefault-pre" data-offset-key="8f93q-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
<pre class="Editable-styled" data-block="true" data-editor="8pipe" data-offset-key="8f93q-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">
JIT0x10, %rsp
call ZEND_QM_ASSIGN_NOREF_SPEC_CONST_HANDLER
add 0x0, EG(exception)
jnz JIT0x0, EG(vm_interrupt)
jnz JIT0x0, EG(exception)
jnz JIT0x452a0858, %r15d
jnz .L1
add $0x10, %rsp
jmp ZEND_RETURN_SPEC_CONST_LABEL
</pre>
</pre>
這就只是簡單的內(nèi)斂部分opcode handler的調(diào)用了。
你也可以嘗試各種opcache.jit的策略結(jié)合debug的配置,來觀測結(jié)果的不同,也可以嘗試各種opcache.jit_debug的配置,比如0xff,將會(huì)有更多的輔助信息輸出。
好了,JIT的使用就簡單介紹到這里,關(guān)于JIT本身的實(shí)現(xiàn)等細(xì)節(jié),以后有時(shí)間,我再來寫吧。
大家現(xiàn)在就可以去php.net下載PHP8來測試了 :)
大廠2000道面試題(含答案)
PHP面試題匯總,看完這些面試題助力你面試成功,工資必有20-25K
喜歡我的文章就關(guān)注我吧,持續(xù)更新中.....
以上內(nèi)容希望幫助到大家,很多PHPer在進(jìn)階的時(shí)候總會(huì)遇到一些問題和瓶頸,業(yè)務(wù)代碼寫多了沒有方向感,不知道該從那里入手去提升,對此我整理了一些資料,包括但不限于:分布式架構(gòu)、高可擴(kuò)展、高性能、高并發(fā)、服務(wù)器性能調(diào)優(yōu)、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql優(yōu)化、shell腳本、Docker、微服務(wù)、Nginx等多個(gè)知識點(diǎn)高級進(jìn)階干貨需要的可以免費(fèi)分享給大家,需要的可以點(diǎn)擊進(jìn)入暗號:知乎。