zoukankan      html  css  js  c++  java
  • [转载] PHP 8新特性之JIT简介

    转载自鸟哥博客 , 原文地址: 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,将会有更多的辅助信息输出。

  • 相关阅读:
    Year Outline stat Detail stat 1987--1996----1999 C:UsersATIDocuments00drmmr v2 tafdrmmr1987-20
    atitit 2010 2010 diary log events memorabilia v3 taf .docx No finish , wait to finish 1.6 yLu
    Atitit 标记语言ML(Markup Language) v4 目录 1. 标记语言ML Markup Language 1 1.1. 简介 1 2. 置标语言置标语言通常可以分为三类:标识性的
    Atitit 2001drmmr v1 t05.docx 1.1shoeho kh majyao n chfe ,bg n rjywel ycyi ,shwa leihaivvei yaopao
    Atitit nlp重要节点 v3 目录 1. 语法分析重点 节点余额365个 1 2. nlp词性表 2 2.1. 词语分类13类 2 2.2. 副词 约20个 3 2.3. 代词30个 3 2
    Atitit 提升语法级别4gl 4.5g 4.9g 5g 目录 1. 语言级别表 1 2. 4.9g实现细节 2 2.1. $dollor前导符 2 2.2. Static变量 2 2.3. S
    Atitit 工程师程序员技术级别对应表与主要特征 P1--p6 说明 类别 职称 对应技术标志 P5 高级工程师 工程师类 一般四五年 P6 资深开发 工程师类 78年经历 P7 P7
    Atitit 自然语言与人工语言的语法构建ast的异同点 目录 1. 语言节点gaishu。。 2 1.1. 节点、函数数量大约200个 2 1.2. 关键词节点 是 有 的 3 1.3. 标识符
    Atitit 编程语言的block概念 目录 1. 匿名block 1 1.1. 函数块 方法快 1 1.2. Sp udf块 1 2. 实现block的方式 1 2.1. 早期的语言大多是采用en
    Atitit 效率提升法细则 v3 t028.docx Atitit 提升效率细则 目录 1. 目标 2 1.1. 配置化增加扩展性 尽可能消除编译 方便增加 调整业务逻辑 2 1.2. 统一接口
  • 原文地址:https://www.cnblogs.com/taoshihan/p/13223270.html
Copyright © 2011-2022 走看看