认识PHP 7虚拟机(3)

### 自定义Zend执行引擎的生成 zend_vm_gen.php支持传入参数--without-specializer,当使用该参数时,每个OPCode只会生成一个与之对应的Handler,该Handler中会对操作数做类型判断,然后再对操作数进行读写。 另一个参数是--with-vm-kind=CALL|SWITCH|GOTO,CALL是默认参数。 前面已提到执行引擎是通过一个while循环执行OPCode,每个OPCode中将opline增加1(通常情况下),然后回到while循环中,继续执行下一个OPCode,直到遇到ZEND_RETURN。 如果使用GOTO执行策略: /* GOTO策略下,execute_ex是一个超大的函数 */ ZEND_API void execute_ex(zend_execute_data *ex) { /* 省略 */ while (1) { /* 省略 */ goto *(void**)(OPLINE->handler); /* 省略 */ } /* 省略 */ } 这里的goto并没有直接使用符号名,其实是goto一个特殊的用法:Labels as Values。 执行引擎中的跳转 当PHP脚本中出现if语句时,是如何跳转到相应的OPCode然后继续执行的?看下面简单的例子: $a = 8; if ($a == 9) { echo "foo"; } else { echo "bar"; } number of ops: 7 compiled vars: !0 = $a line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 2 0 E > ASSIGN !0, 8 3 1 IS_EQUAL ~2 !0, 9 2 > JMPZ ~2, ->5 4 3 > ECHO 'foo' 4 > JMP ->6 6 5 > ECHO 'bar' 6 > > RETURN 1 当$a != 9时,JMPZ会使当前执行跳转到第5个OPCode,否则JMP会使当前执行跳转到第6个OPCode。其实就是对当前的opline赋值为跳转目标OPCode的地址。 ### 一些性能Tips 这部分内容将展示如何通过查看生成的OPCode优化PHP代码。 echo a concatenation 示例代码: $foo = 'foo'; $bar = 'bar'; echo $foo . $bar; #### OPArray: number of ops: 5 compiled vars: !0 = $foo, !1 = $bar line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 2 0 E > ASSIGN !0, 'foo' 3 1 ASSIGN !1, 'bar' 5 2 CONCAT ~4 !0, !1 3 ECHO ~4 4 > RETURN 1 $a和$b的值会被ZEND_CONCAT连接后存储到一个临时变量~4中,然后再echo输出。 CONCAT操作需要分配一块临时的内存,然后做内存拷贝,echo输出后,又要回收这块临时内存。如果把代码改为如下可消除CONCAT: $foo = 'foo'; $bar = 'bar'; echo $foo , $bar; #### OPArray: number of ops: 5 compiled vars: !0 = $foo, !1 = $bar line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 2 0 E > ASSIGN !0, 'foo' 3 1 ASSIGN !1, 'bar' 5 2 ECHO !0 3 ECHO !1 4 > RETURN 1 define()和const PHP 5.3引入了const关键字。 简单地说: * define()是一个函数调用 * conast是关键字,不会产生函数调用,要比define()轻量许多 define('FOO', 'foo'); echo FOO; number of ops: 7 compiled vars: none line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 2 0 E > INIT_FCALL 'define' 1 SEND_VAL 'FOO' 2 SEND_VAL 'foo' 3 DO_ICALL 3 4 FETCH_CONSTANT ~1 'FOO' 5 ECHO ~1 6 > RETURN 1 如果使用const: const FOO = 'foo'; echo FOO; number of ops: 4 compiled vars: none line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 2 0 E > DECLARE_CONST 'FOO', 'foo' 3 1 FETCH_CONSTANT ~0 'FOO' 2 ECHO ~0 3 > RETURN 1 然而const在使用上有一些限制: * const关键字定义常量必须处于最顶端的作用区域,这就意味着不能在函数内,循环内以及if语句之内用const 来定义常量 * const的操作数必须为IS_CONST类型 动态函数调用 尽量不要使用动态的函数名去调用函数: function foo() { } foo(); number of ops: 4 compiled vars: none line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 2 0 E > NOP 3 1 INIT_FCALL 'foo' 2 DO_UCALL 3 > RETURN 1 NOP表示不做任何操作,只是将当前opline指向下一条OPCode,编译器产生这条指令是由于历史原因。为何到PHP7还不移除它呢= = 看看使用动态的函数名去调用函数: function foo() { } $a = 'foo'; $a(); number of ops: 5 compiled vars: !0 = $a line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 2 0 E > NOP 3 1 ASSIGN !0, 'foo' 4 2 INIT_DYNAMIC_CALL !0 3 DO_FCALL 0 4 > RETURN 1 不同点在于INIT_FCALL和INIT_DYNAMIC_CALL,看下两个函数的源码: static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_FCALL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE zval *fname = EX_CONSTANT(opline->op2); zval *func; zend_function *fbc; zend_execute_data *call; fbc = CACHED_PTR(Z_CACHE_SLOT_P(fname)); /* 看下是否已经在缓存中了 */ if (UNEXPECTED(fbc == NULL)) { func = zend_hash_find(EG(function_table), Z_STR_P(fname)); /* 根据函数名查找函数 */ if (UNEXPECTED(func == NULL)) { SAVE_OPLINE(); zend_throw_error(NULL, "Call to undefined function %s()", Z_STRVAL_P(fname)); HANDLE_EXCEPTION(); } fbc = Z_FUNC_P(func); CACHE_PTR(Z_CACHE_SLOT_P(fname), fbc); /* 缓存查找结果 */ } call = zend_vm_stack_push_call_frame_ex( opline->op1.num, ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL, NULL); call->prev_execute_data = EX(call); EX(call) = call; ZEND_VM_NEXT_OPCODE(); } static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_DYNAMIC_CALL_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { /* 200多行代码,就不贴出来了,会根据CV的类型(字符串、对象、数组)做不同的函数查找 */ } 很显然INIT_FCALL相比INIT_DYNAMIC_CALL要轻量许多。
联系我们

邮箱 626512443@qq.com
电话 18611320371(微信)
QQ群 235681453

Copyright © 2015-2022

备案号:京ICP备15003423号-3