PHP 8 确认支持 JIT
介绍
今日JIT的案例
提案
我们建议将JIT包含在PHP 8中,并提供额外的工作来提高其性能和可用性。
此外,我们建议考虑在PHP 7.4中包含JIT作为实验性功能(默认情况下禁用)。
PHP JIT实现为OPcache的几乎独立部分。它可以在PHP编译时和运行时启用/禁用。启用时,PHP文件的本机代码存储在OPcache共享内存的附加区域中,op_array→opcodes []。处理程序保留指向JIT编码的入口点的指针。这种方法根本不需要引擎修改。
我们使用DynAsm(为LuaJIT项目开发)来生成本机代码。它是一个非常轻量级和高级的工具,但它确实承担了目标汇编语言的良好和非常低级的开发知识。在过去,我们尝试过LLVM,但它的代码生成速度几乎慢了100倍,因此使用起来非常昂贵。目前,我们在POSIX平台和Windows上支持x86和x86_64 CPU。DynAsm还支持ARM。ARM64,MIPS,MIPS64和PPC,理论上我们应该能够支持PHP部署中常用的所有平台(给予足够的努力)。
PHP JIT不引入任何其他IR(中间表示)表单。它直接从PHP字节代码和SSA静态分析框架(opcache优化器的一部分)收集的信息生成本机代码。通常为每个PHP字节码指令单独生成代码。只考虑几种组合(例如比较+条件跳转)。
如果PHP变量的类型(在SSA中)被精确推断为LONG或DOUBLE,并且无法间接访问,则JIT可以将其值直接存储在CPU寄存器中,从而避免存储器和负载。PHP JIT线性扫描寄存器分配算法,结合高速和合理的质量。
JIT的质量可以在https://gist.github.com/dstogov/12323ad13d3240aee8f1上发布的Mandelbrot基准测试中得到证明,它可以将性能提高4倍以上(0.011秒对PHP 7.4的0.046秒)。
以下是为上面的PHP函数生成的完整汇编代码,主循环代码在.L5和.L7之间可见:
函数迭代($ x ,$ y )
{
$ cr = $ y - 0.5 ;
$ ci = $ x ;
$ zr = 0.0 ;
$ zi = 0.0 ;
$ i = 0 ;
while (true ) {
$ i ++;
$ temp = $ zr * $ zi ;
$ zr2 = $ zr * $ zr ;
$ zi2 = $ zi * $ zi ;
$ ZR = $ zr2 - $ zi2 + $ cr ;
$ zi = $ temp + $ temp + $ ci ;
if ($ zi2 + $ zr2 > BAILOUT )
返回 $ i ;
if ($ i > MAX_ITERATIONS )
返回 0 ;
}
}
JIT $ Mandelbrot :: iterate : ; (/home/dmitry/php/bench/b.php)
子 $ 0×10 , %ESP
CMP $ 为0x1 , 为0x1C (%ESI )
JB 。L14
jmp 。L1
。ENTRY1 :
sub $ 0x10 , %esp
。L1 :
CMP $ 0X2 , 为0x1C (%ESI )
JB 。L15
mov $0xec3800f0 , %edi
jmp 。L2
。ENTRY2 :
sub $ 0x10 , %esp
。L2 :
CMP $ 0x5的, 0x48 (%ESI )
JNZ 。L16
vmovsd 0x40 (%esi ), %xmm1
vsubsd 0xec380068 , %xmm1 , %xmm1
。L3 :
mov 0x30 (%ESI ), %eax中
MOV 0x34 (%ESI ), %EDX
MOV %eax中, 0x60的(%ESI )
MOV %EDX , 0x64 (%ESI )
MOV 0x38 (%ESI ), %EDX
MOV %EDX , 0x68 (%ESI )
测试 $ 0x1 , %dh
jz 。L4
添加 $ 0x1 , (%eax )
。L4 :
vxorps %xmm2 , %xmm2 , %xmm2
vxorps %xmm3 , %xmm3 , %xmm3
xor %edx , %edx
。L5 :
cmp $ 0x0 , EG ( vm_interrupt )
jnz 。L18
加 $ 0x1 , %edx
vmulsd %xmm3 , %xmm2 , %xmm4
vmulsd %xmm2 , %xmm2 , %xmm5
vmulsd %xmm3 , %xmm3 , %xmm6
vsubsd %xmm6 , %xmm5 , %xmm7
vaddsd %xmm7 , %xmm1 , %xmm2
vaddsd %xmm4 , %xmm4 , %xmm4
CMP $ 0x5的, 0x68 (%ESI )
JNZ 。L19
vaddsd 0x60 (%esi ), %xmm4 , %xmm3
。L6 :
vaddsd %xmm5 , %xmm6 , %xmm6
vucomisd 0xec3800a8 , %xmm6
jp 。L13
jbe 。L13
mov 0x8 (%esi ), %ecx
test %ecx , %ecx
jz 。L7
MOV %EDX , (%ECX )
MOV $ 为0x4 , 0x8中(%ECX )
。L7 :
测试 $ 为0x1 , 0x39 (%ESI )
JNZ 。L21
。L8 :
测试 $ 为0x1 , ×49 (%ESI )
JNZ 。L23
。L9 :
测试 $ 为0x1 , 0×69 (%ESI )
JNZ 。L25
。L10 :
movzx 0x1a (%esi ), %ecx
test $ 0x496 , %ecx
jnz JIT $$ leave_function
mov 0x20 (%esi ), %eax
mov %eax , EG ( current_execute_data )
测试 $ 0x40 , %ecx
jz 。L12
mov 0x10 (%esi ), %eax
sub $ 0x1 , (%eax )
jnz 。L11
mov %eax , %ecx
调用 zend_objects_store_del
jmp 。L12
。L11 :
mov 0x4 (%eax ), %ecx
和 $0xfffffc10 , %ecx
cmp $ 0x10 , %ecx
jnz 。L12
mov %eax , %ecx
调用 gc_possible_root
。L12 :
mov %esi , EG ( vm_stack_top )
mov 0x20 (%esi ), %esi
cmp $ 0x0 , EG ( exception )
mov (%esi ), %edi
jnz JIT $$ leave_throw
添加 $ 0x1c , %edi
添加 $ 0x10 , %esp
jmp (%edi )
。L13 :
cmp $ 0x3e8 , %edx
jle 。L5
mov 0x8 (%esi ), %ecx
test %ecx , %ecx
jz 。L7
mov $为0x0 , (%ECX )
MOV $ 为0x4 , 0x8中(%ECX )
JMP 。L7
。L14 :
mov %edi , (%esi )
mov %esi , %ecx
call zend_missing_arg_error
jmp JIT $$ exception_handler
。L15 :
mov %edi , (%esi )
mov %esi , %ecx
调用 zend_missing_arg_error
jmp JIT $$ exception_handler
。L16 :
CMP $ 为0x4 , 0x48 (%ESI )
JNZ 。L17
vcvtsi2sd 0x40 (%esi ), %xmm1 , %xmm1
vsubsd 0xec380068 , %xmm1 , %xmm1
jmp 。L3
。L17 :
mov %edi , (%esi )
lea 0x50 (%esi ), %ecx
lea 0x40 (%esi ), %edx
sub $ 0xc , %esp
push $ 0xec380068
call sub_function
add $ 0xc , %esp
cmp $ 0x0 , EG ( exception )
jnz JIT $$ exception_handler
vmovsd 0x50 (%esi ), %xmm1
jmp 。L3
。L18 :
mov $ 0xec38017c , %edi
jmp JIT $$ interrupt_handler
。L19 :
CMP $ 为0x4 , 0x68 (%ESI )
JNZ 。L20
vcvtsi2sd 0x60 (%esi ), %xmm3 , %xmm3
vaddsd %xmm4 , %xmm3 , %xmm3
jmp 。L6
。L20 :
MOV $ 0xec380240 , (%ESI )
LEA 0x80的(%ESI ), %ECX
vmovsd %XMM4 , 0xe0的(%ESI )
MOV $ 0x5的, 0xe8 (%ESI )
LEA 0xe0的(%ESI ), %edx
sub $ 0xc , %esp
lea 0x60 (%esi ), %eax
push %eax
call add_function
add $ 0xc , %esp
cmp $ 0x0 , EG ( exception )
jnz JIT $$ exception_handler
vmovsd 0x80 (%esi ), %xmm3
jmp 。L6
。L21 :
mov 0x30 (%esi ), %ecx
sub $ 0x1 , (%ecx )
jnz 。L22
MOV $ 为0x1 , 0x38 (%ESI )
MOV $ 0xec3802b0 , (%ESI )
调用 rc_dtor_func
JMP 。L8
。L22 :
mov 0x4 (%ecx ), %eax
和 $0xfffffc10 , %eax
cmp $ 0x10 , %eax
jnz 。L8
调用 gc_possible_root
jmp 。L8
。L23 :
mov 0x40 (%esi ), %ecx
sub $ 0x1 , (%ecx )
jnz 。L24
MOV $ 为0x1 , 0x48 (%ESI )
MOV $ 0xec3802b0 , (%esi )
调用 rc_dtor_func
jmp 。L9
。L24 :
mov 0x4 (%ecx ), %eax
和 $ 0xfffffc10 , %eax
cmp $ 0x10 , %eax
jnz 。L9
调用 gc_possible_root
jmp 。L9
。L25 :
mov 0x60 (%esi ), %ecx
sub $ 0x1 , (%ecx )
jnz 。L26
MOV $ 为0x1 , 0x68 (%ESI )
MOV $ 0xec3802b0 , (%ESI )
调用 rc_dtor_func
JMP 。L10
。L26 :
mov 0x4 (%ecx ), %eax
和 $ 0xfffffc10 , %eax
cmp $ 0x10 , %eax
jnz 。L10
调用 gc_possible_root
jmp 。L10
向后不兼容的变化
建议的PHP版本
到现有扩展
对Opcache
新常数
php.ini默认值
性能