Press "Enter" to skip to content

PHP 8新特性之JIT简介

PHP8 alpha1已经在昨天发布,相信关于JIT是大家最关心的,它到底怎么用,有什么要注意的,以及性能提升到底咋样?

首先,我们来看一张图:

左图是PHP8之前的Opcache流程示意图, 右图是PHP8中的Opcache示意图, 可以看出几个关键点:

  • 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
    

基于此,我们可以大概得到如下几个结论:

  • 尽量使用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,将会有更多的辅助信息输出。

好了,JIT的使用就简单介绍到这里,关于JIT本身的实现等细节,以后有时间,我再来写吧。

大家现在就可以去php.net下载PHP8来测试了 :)

thanks

29 Comments

  1. Smile
    Smile September 14, 2020

    up

  2. ben
    ben September 9, 2020

    没有携程或者线程php必将淘汰

  3. fang
    fang September 4, 2020

    Event 不好吗? workman 不好吗? 都十几年前的东西了

  4. cyd
    cyd August 13, 2020

    最近做了非x86的php7项目迁移,jit在php8编译时能完整隔离开,

  5. Jack Ho
    Jack Ho August 13, 2020

    其实php都是做网站比较多,网站最重要的是io的速速,而不是计算能力,希望php能加入异步的io和多线程或者协程,这样才是现在php最重要的,个人见解不喜勿喷!

    • shan
      shan September 16, 2020

      以目前PHP情况 加入多线程绝壁是灾难的开始.

  6. twomiao
    twomiao August 5, 2020

    你倒不如直接说语言级别,async await 异步实现呢,现在Python3.4都支持了,C#很早就有了。

  7. jaggle
    jaggle July 25, 2020

    不好意思上面那条评论中,我没有开启opache,开启之后,确实快了很多!

    php.ini

    zend_extension=opcache

    [opcache]
    opcache.jit=1205
    opcache.jit_buffer_size=64M
    ; Determines if Zend OPCache is enabled
    opcache.enable=1

    ; Determines if Zend OPCache is enabled for the CLI version of PHP
    opcache.enable_cli=1

    再次运行结果

    不带JIT
    ➔ /usr/local/opt/php@8.0-alpha/bin/php -d opcache.jit_buffer_size=0 Zend/bench.php

    ————————
    Total 0.487

    带JIT:

    ➔ /usr/local/opt/php@8.0-alpha/bin/php -d opcache.jit_buffer_size=64M -d opcache.jit=1205 Zend/bench.php

    ————————
    Total 0.135

    niceeeee!

  8. jaggle
    jaggle July 25, 2020

    我在MacbookPro上通过源码编译安装好了PHP8.0 alpha3,但是我试了一下带不带JIT好像差别不大(甚至效果更差),很多次基本上都是这样的,请问哪里出问题了,CPU型号:2.7 GHz 双核Intel Core i5

    不带JIT:
    “`
    ➔ /usr/local/opt/php@8.0-alpha/bin/php -d opcache.jit_buffer_size=0 Zend/bench.php
    simple 0.025
    simplecall 0.008
    <?php
    simpleucall 0.030
    simpleudcall 0.037
    mandel 0.127
    mandel2 0.123
    ackermann(7) 0.034
    ary(50000) 0.011
    ary2(50000) 0.005
    ary3(2000) 0.056
    fibo(30) 0.121
    hash1(50000) 0.015
    hash2(500) 0.011
    heapsort(20000) 0.037
    matrix(20) 0.031
    nestedloop(12) 0.041
    sieve(30) 0.019
    strcat(200000) 0.008
    ————————
    Total 0.739
    “`

    带JIT:

    ➔ /usr/local/opt/php@8.0-alpha/bin/php -d opcache.jit_buffer_size=64M -d opcache.jit=1205 Zend/bench.php
    simple 0.025
    simplecall 0.009
    simpleucall 0.029
    simpleudcall 0.034
    mandel 0.115
    mandel2 0.135
    ackermann(7) 0.039
    ary(50000) 0.008
    ary2(50000) 0.006
    ary3(2000) 0.061
    fibo(30) 0.132
    hash1(50000) 0.013
    hash2(500) 0.013
    heapsort(20000) 0.042
    matrix(20) 0.041
    nestedloop(12) 0.048
    sieve(30) 0.020
    strcat(200000) 0.010
    ————————
    Total 0.778

  9. wang
    wang July 24, 2020

    x 是变量。。。哈哈哈哈哈 1205 1215 1225 等等 x是触发策略

  10. Gordon Lim
    Gordon Lim July 23, 2020

    其实我看不懂 “12×5型的配置” 的意思

    是否可以给个设置的范例?

    谢谢

  11. Josen
    Josen July 16, 2020

    国人的骄傲

  12. baagee
    baagee July 3, 2020

    更希望官方实现协程,异步IO,而不是使用swoole

    • 小刘
      小刘 July 4, 2020

      是啊,目前更期待的是这些,而不是JIT

    • Moxyu
      Moxyu July 8, 2020

      如果能将Swoole纳入官方维护的话,还是考虑用Swoole的,现在Swoole在中小型企业不被认可,大部分Swoole能做的事情都让Go或者Java处理了,Swoole现在给我的感觉就是商业化严重,中小型企业不愿意试错,但是不得不说Swoole确实重新定义了PHP。

      • Davis
        Davis July 17, 2020

        “大部分Swoole能做的事情都让Go或者Java处理了”, 这句话深有感触啊!

      • shan
        shan September 16, 2020

        //yield 就是协程的一种 但个人真的不好用,还有底层阻塞的问题等 异步编程参照JS的进化 callback->yield->await+async RUST直接采用最后await+async+future
        //swoole的封装个人感觉有些粗暴,代码质量 额,跟PHP官方代码确实有些差距,php C层面如果可以把异步操作弄个统一的收归,官方可以弄个牛逼点yield的调度器,顺带加个channel 觉得就没使用swoole的必要了
        //要快速开发的,我还是用PHP,对于极端的接口 我用 actix-web 我知道很多PHP转GO的.萝卜白菜各有所爱. 作为PHP转RUST的表示用GO还是算了吧,没感觉有什么大优势

    • FlyingHail
      FlyingHail July 28, 2020

      我更希望 PHP 能支持多线程+协程,类似 go 的模型。swoole 也只是单线程+协程,为了利用多核就只能多进程,这样除了协程间的 channel 通信,还需要进程间通信。本来用PHP就是为了写起来快,简单,通信这里却更复杂了。

  13. gxheart
    gxheart July 3, 2020

    up

  14. Bourne
    Bourne July 2, 2020

    jit 特性,可不可以使用php写extension?是真的吗?

  15. Emrys.Liu
    Emrys.Liu June 29, 2020

    -dopcache.jit=0xxx 会报 warning:Invalid “opcache.jit” setting. Should be “disable”, “on”, “off” or 4-digit number in Unknown on line 0

  16. brczo
    brczo June 28, 2020

    up2

  17. Ben
    Ben June 27, 2020

    up

Leave a Reply

Your email address will not be published. Required fields are marked *