php8 alpha1已经在昨天发布,相信关于jit是大家最关心的,php8 jit是什么,又怎么用,又有什么要注意的,以及性能提升到底咋样?
视频教程推荐:《php编程从入门到精通》
首先,我们来看一张图:
(右图有点错误就是,当jit以后,下次请求的时候,会直接从jit buffer中读取执行,后续我把图改一下)
左图是php8之前的opcache流程示意图, 右图是php8中的opcache示意图, 可以看出几个关键点:
opcache会做opcode层面的优化,比如图中的俩条opcode合并为一条
jit在opcache优化之后的基础上,再次优化,直接生成机器码
php8的jit是在opcache之中提供的
目前php8只支持x86架构的cpu
jit是在原来opcache优化的优化基础之上进行优化的,不是替代
事实上jit共用了很多原来opcache做优化的基础数据结构,比如data flow graph, call graph, ssa等,关于这部分,后续如果有时间,可以单独在写一个文章来介绍,今天就只是着重在使用层面。
下载安装好以后,除掉原有的opcache配置以外,对于jit我们需要添加如下配置到php.ini:
opcache.jit=1205opcache.jit_buffer_size=64m
opcache.jit这个配置看起来稍微有点复杂,我来解释下, 这个配置由4个独立的数字组成,从左到右分别是(请注意,这个是基于目前alpha1的版本设置,一些配置可能会随着后续版本做微调):
是否在生成机器码点时候使用avx指令, 需要cpu支持:0: 不使用1: 使用
寄存器分配策略:0: 不使用寄存器分配1: 局部(block)域分配2: 全局(function)域分配
jit触发策略:0: php脚本载入的时候就jit1: 当函数第一次被执行时jit2: 在一次运行后,jit调用次数最多的百分之(opcache.prof_threshold * 100)的函数3: 当函数/方法执行超过n(n和opcache.jit_hot_func相关)次以后jit4: 当函数方法的注释中含有@jit的时候对它进行jit5: 当一个trace执行超过n次(和opcache.jit_hot_loop, jit_hot_return等有关)以后jit
jit优化策略,数值越大优化力度越大:0: 不jit1: 做opline之间的跳转部分的jit2: 内敛opcode handler调用3: 基于类型推断做函数级别的jit4: 基于类型推断,过程调用图做函数级别jit5: 基于类型推断,过程调用图做脚本级别的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.008simplecall 0.004simpleucall 0.004simpleudcall 0.004mandel 0.035mandel2 0.055ackermann(7) 0.020ary(50000) 0.004ary2(50000) 0.003ary3(2000) 0.048fibo(30) 0.084hash1(50000) 0.013hash2(500) 0.010heapsort(20000) 0.027matrix(20) 0.026nestedloop(12) 0.023sieve(30) 0.013strcat(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.002simplecall 0.001simpleucall 0.001simpleudcall 0.001mandel 0.010mandel2 0.011ackermann(7) 0.010ary(50000) 0.003ary2(50000) 0.002ary3(2000) 0.018fibo(30) 0.031hash1(50000) 0.011hash2(500) 0.008heapsort(20000) 0.014matrix(20) 0.015nestedloop(12) 0.011sieve(30) 0.005strcat(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的范围推断不会大于10000,所以不需要判断是否整数溢出等等。
而如果我们采用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,将会有更多的辅助信息输出。
好了,jit的使用就简单介绍到这里,关于jit本身的实现等细节,以后有时间,我再来写吧。
大家现在就可以去php.net下载php8来测试了 :)
相关推荐:《php》《php7》
以上就是php jit 是什么?php8 新特性之 jit 图文详解的详细内容。